1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2008 Sean C. Farley <scf@FreeBSD.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer, 12 * without modification, immediately at the beginning of the file. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/param.h> 30 #include <sys/errno.h> 31 #include <sys/stat.h> 32 33 #include <ctype.h> 34 #include <err.h> 35 #include <fcntl.h> 36 #include <grp.h> 37 #include <inttypes.h> 38 #include <libutil.h> 39 #include <paths.h> 40 #include <stdbool.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <unistd.h> 45 46 static int lockfd = -1; 47 static char group_dir[PATH_MAX]; 48 static char group_file[PATH_MAX]; 49 static char tempname[PATH_MAX]; 50 static int initialized; 51 static size_t grmemlen(const struct group *, const char *, int *); 52 static struct group *grcopy(const struct group *gr, char *mem, const char *, int ndx); 53 54 /* 55 * Initialize statics 56 */ 57 int 58 gr_init(const char *dir, const char *group) 59 { 60 61 if (dir == NULL) { 62 strcpy(group_dir, _PATH_ETC); 63 } else { 64 if (strlen(dir) >= sizeof(group_dir)) { 65 errno = ENAMETOOLONG; 66 return (-1); 67 } 68 strcpy(group_dir, dir); 69 } 70 71 if (group == NULL) { 72 if (dir == NULL) { 73 strcpy(group_file, _PATH_GROUP); 74 } else if (snprintf(group_file, sizeof(group_file), "%s/group", 75 group_dir) > (int)sizeof(group_file)) { 76 errno = ENAMETOOLONG; 77 return (-1); 78 } 79 } else { 80 if (strlen(group) >= sizeof(group_file)) { 81 errno = ENAMETOOLONG; 82 return (-1); 83 } 84 strcpy(group_file, group); 85 } 86 87 initialized = 1; 88 return (0); 89 } 90 91 /* 92 * Lock the group file 93 */ 94 int 95 gr_lock(void) 96 { 97 if (*group_file == '\0') 98 return (-1); 99 100 for (;;) { 101 struct stat st; 102 103 lockfd = flopen(group_file, O_RDONLY|O_NONBLOCK|O_CLOEXEC, 0); 104 if (lockfd == -1) { 105 if (errno == EWOULDBLOCK) { 106 errx(1, "the group file is busy"); 107 } else { 108 err(1, "could not lock the group file"); 109 } 110 } 111 if (fstat(lockfd, &st) == -1) 112 err(1, "fstat() failed"); 113 if (st.st_nlink != 0) 114 break; 115 close(lockfd); 116 lockfd = -1; 117 } 118 return (lockfd); 119 } 120 121 /* 122 * Create and open a presmuably safe temp file for editing group data 123 */ 124 int 125 gr_tmp(int mfd) 126 { 127 char buf[8192]; 128 ssize_t nr; 129 const char *p; 130 int tfd; 131 132 if (*group_file == '\0') 133 return (-1); 134 if ((p = strrchr(group_file, '/'))) 135 ++p; 136 else 137 p = group_file; 138 if (snprintf(tempname, sizeof(tempname), "%.*sgroup.XXXXXX", 139 (int)(p - group_file), group_file) >= (int)sizeof(tempname)) { 140 errno = ENAMETOOLONG; 141 return (-1); 142 } 143 if ((tfd = mkostemp(tempname, 0)) == -1) 144 return (-1); 145 if (mfd != -1) { 146 while ((nr = read(mfd, buf, sizeof(buf))) > 0) 147 if (write(tfd, buf, (size_t)nr) != nr) 148 break; 149 if (nr != 0) { 150 unlink(tempname); 151 *tempname = '\0'; 152 close(tfd); 153 return (-1); 154 } 155 } 156 return (tfd); 157 } 158 159 /* 160 * Copy the group file from one descriptor to another, replacing, deleting 161 * or adding a single record on the way. 162 */ 163 int 164 gr_copy(int ffd, int tfd, const struct group *gr, struct group *old_gr) 165 { 166 char *buf, *end, *line, *p, *q, *r, *tmp; 167 struct group *fgr; 168 const struct group *sgr; 169 size_t len, size; 170 int eof, readlen; 171 char t; 172 173 if (old_gr == NULL && gr == NULL) 174 return(-1); 175 176 sgr = old_gr; 177 /* deleting a group */ 178 if (gr == NULL) { 179 line = NULL; 180 } else { 181 if ((line = gr_make(gr)) == NULL) 182 return (-1); 183 } 184 185 /* adding a group */ 186 if (sgr == NULL) 187 sgr = gr; 188 189 /* initialize the buffer */ 190 if ((buf = malloc(size = 1024)) == NULL) 191 goto err; 192 193 eof = 0; 194 len = 0; 195 p = q = end = buf; 196 for (;;) { 197 /* find the end of the current line */ 198 for (p = q; q < end && *q != '\0'; ++q) 199 if (*q == '\n') 200 break; 201 202 /* if we don't have a complete line, fill up the buffer */ 203 if (q >= end) { 204 if (eof) 205 break; 206 while ((size_t)(q - p) >= size) { 207 if ((tmp = reallocarray(buf, 2, size)) == NULL) { 208 warnx("group line too long"); 209 goto err; 210 } 211 p = tmp + (p - buf); 212 q = tmp + (q - buf); 213 end = tmp + (end - buf); 214 buf = tmp; 215 size = size * 2; 216 } 217 if (p < end) { 218 q = memmove(buf, p, end -p); 219 end -= p - buf; 220 } else { 221 p = q = end = buf; 222 } 223 readlen = read(ffd, end, size - (end - buf)); 224 if (readlen == -1) 225 goto err; 226 else 227 len = (size_t)readlen; 228 if (len == 0 && p == buf) 229 break; 230 end += len; 231 len = end - buf; 232 if (len < size) { 233 eof = 1; 234 if (len > 0 && buf[len -1] != '\n') 235 ++len, *end++ = '\n'; 236 } 237 continue; 238 } 239 240 /* is it a blank line or a comment? */ 241 for (r = p; r < q && isspace(*r); ++r) 242 /* nothing */; 243 if (r == q || *r == '#') { 244 /* yep */ 245 if (write(tfd, p, q -p + 1) != q - p + 1) 246 goto err; 247 ++q; 248 continue; 249 } 250 251 /* is it the one we're looking for? */ 252 253 t = *q; 254 *q = '\0'; 255 256 fgr = gr_scan(r); 257 258 /* fgr is either a struct group for the current line, 259 * or NULL if the line is malformed. 260 */ 261 262 *q = t; 263 if (fgr == NULL || fgr->gr_gid != sgr->gr_gid) { 264 /* nope */ 265 if (fgr != NULL) 266 free(fgr); 267 if (write(tfd, p, q - p + 1) != q - p + 1) 268 goto err; 269 ++q; 270 continue; 271 } 272 if (old_gr && !gr_equal(fgr, old_gr)) { 273 warnx("entry inconsistent"); 274 free(fgr); 275 errno = EINVAL; /* hack */ 276 goto err; 277 } 278 free(fgr); 279 280 /* it is, replace or remove it */ 281 if (line != NULL) { 282 len = strlen(line); 283 if (write(tfd, line, len) != (int) len) 284 goto err; 285 } else { 286 /* when removed, avoid the \n */ 287 q++; 288 } 289 /* we're done, just copy the rest over */ 290 for (;;) { 291 if (write(tfd, q, end - q) != end - q) 292 goto err; 293 q = buf; 294 readlen = read(ffd, buf, size); 295 if (readlen == 0) 296 break; 297 else 298 len = (size_t)readlen; 299 if (readlen == -1) 300 goto err; 301 end = buf + len; 302 } 303 goto done; 304 } 305 306 /* if we got here, we didn't find the old entry */ 307 if (line == NULL) { 308 errno = ENOENT; 309 goto err; 310 } 311 len = strlen(line); 312 if ((size_t)write(tfd, line, len) != len || 313 write(tfd, "\n", 1) != 1) 314 goto err; 315 done: 316 free(line); 317 free(buf); 318 return (0); 319 err: 320 free(line); 321 free(buf); 322 return (-1); 323 } 324 325 /* 326 * Regenerate the group file 327 */ 328 int 329 gr_mkdb(void) 330 { 331 int fd; 332 333 if (chmod(tempname, 0644) != 0) 334 return (-1); 335 336 if (rename(tempname, group_file) != 0) 337 return (-1); 338 339 /* 340 * Make sure new group file is safe on disk. To improve performance we 341 * will call fsync() to the directory where file lies 342 */ 343 if ((fd = open(group_dir, O_RDONLY|O_DIRECTORY)) == -1) 344 return (-1); 345 346 if (fsync(fd) != 0) { 347 close(fd); 348 return (-1); 349 } 350 351 close(fd); 352 return(0); 353 } 354 355 /* 356 * Clean up. Preserves errno for the caller's convenience. 357 */ 358 void 359 gr_fini(void) 360 { 361 int serrno; 362 363 if (!initialized) 364 return; 365 initialized = 0; 366 serrno = errno; 367 if (*tempname != '\0') { 368 unlink(tempname); 369 *tempname = '\0'; 370 } 371 if (lockfd != -1) 372 close(lockfd); 373 errno = serrno; 374 } 375 376 /* 377 * Compares two struct group's. 378 */ 379 int 380 gr_equal(const struct group *gr1, const struct group *gr2) 381 { 382 383 /* Check that the non-member information is the same. */ 384 if (gr1->gr_name == NULL || gr2->gr_name == NULL) { 385 if (gr1->gr_name != gr2->gr_name) 386 return (false); 387 } else if (strcmp(gr1->gr_name, gr2->gr_name) != 0) 388 return (false); 389 if (gr1->gr_passwd == NULL || gr2->gr_passwd == NULL) { 390 if (gr1->gr_passwd != gr2->gr_passwd) 391 return (false); 392 } else if (strcmp(gr1->gr_passwd, gr2->gr_passwd) != 0) 393 return (false); 394 if (gr1->gr_gid != gr2->gr_gid) 395 return (false); 396 397 /* 398 * Check all members in both groups. 399 * getgrnam can return gr_mem with a pointer to NULL. 400 * gr_dup and gr_add strip out this superfluous NULL, setting 401 * gr_mem to NULL for no members. 402 */ 403 if (gr1->gr_mem != NULL && gr2->gr_mem != NULL) { 404 int i; 405 406 for (i = 0; 407 gr1->gr_mem[i] != NULL && gr2->gr_mem[i] != NULL; i++) { 408 if (strcmp(gr1->gr_mem[i], gr2->gr_mem[i]) != 0) 409 return (false); 410 } 411 if (gr1->gr_mem[i] != NULL || gr2->gr_mem[i] != NULL) 412 return (false); 413 } else if (gr1->gr_mem != NULL && gr1->gr_mem[0] != NULL) { 414 return (false); 415 } else if (gr2->gr_mem != NULL && gr2->gr_mem[0] != NULL) { 416 return (false); 417 } 418 419 return (true); 420 } 421 422 /* 423 * Make a group line out of a struct group. 424 */ 425 char * 426 gr_make(const struct group *gr) 427 { 428 const char *group_line_format = "%s:%s:%ju:"; 429 const char *sep; 430 char *line; 431 char *p; 432 size_t line_size; 433 int ndx; 434 435 /* Calculate the length of the group line. */ 436 line_size = snprintf(NULL, 0, group_line_format, gr->gr_name, 437 gr->gr_passwd, (uintmax_t)gr->gr_gid) + 1; 438 if (gr->gr_mem != NULL) { 439 for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) 440 line_size += strlen(gr->gr_mem[ndx]) + 1; 441 if (ndx > 0) 442 line_size--; 443 } 444 445 /* Create the group line and fill it. */ 446 if ((line = p = malloc(line_size)) == NULL) 447 return (NULL); 448 p += sprintf(p, group_line_format, gr->gr_name, gr->gr_passwd, 449 (uintmax_t)gr->gr_gid); 450 if (gr->gr_mem != NULL) { 451 sep = ""; 452 for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) { 453 p = stpcpy(p, sep); 454 p = stpcpy(p, gr->gr_mem[ndx]); 455 sep = ","; 456 } 457 } 458 459 return (line); 460 } 461 462 /* 463 * Duplicate a struct group. 464 */ 465 struct group * 466 gr_dup(const struct group *gr) 467 { 468 return (gr_add(gr, NULL)); 469 } 470 /* 471 * Add a new member name to a struct group. 472 */ 473 struct group * 474 gr_add(const struct group *gr, const char *newmember) 475 { 476 char *mem; 477 size_t len; 478 int num_mem; 479 480 num_mem = 0; 481 len = grmemlen(gr, newmember, &num_mem); 482 /* Create new group and copy old group into it. */ 483 if ((mem = malloc(len)) == NULL) 484 return (NULL); 485 return (grcopy(gr, mem, newmember, num_mem)); 486 } 487 488 /* It is safer to walk the pointers given at gr_mem since there is no 489 * guarantee the gr_mem + strings are contiguous in the given struct group 490 * but compactify the new group into the following form. 491 * 492 * The new struct is laid out like this in memory. The example given is 493 * for a group with two members only. 494 * 495 * { 496 * (char *name) 497 * (char *passwd) 498 * (int gid) 499 * (gr_mem * newgrp + sizeof(struct group) + sizeof(**)) points to gr_mem area 500 * gr_mem area 501 * (member1 *) 502 * (member2 *) 503 * (NULL) 504 * (name string) 505 * (passwd string) 506 * (member1 string) 507 * (member2 string) 508 * } 509 */ 510 /* 511 * Copy the contents of a group plus given name to a preallocated group struct 512 */ 513 static struct group * 514 grcopy(const struct group *gr, char *dst, const char *name, int ndx) 515 { 516 int i; 517 struct group *newgr; 518 519 newgr = (struct group *)(void *)dst; /* avoid alignment warning */ 520 dst += sizeof(*newgr); 521 if (ndx != 0) { 522 newgr->gr_mem = (char **)(void *)(dst); /* avoid alignment warning */ 523 dst += (ndx + 1) * sizeof(*newgr->gr_mem); 524 } else 525 newgr->gr_mem = NULL; 526 if (gr->gr_name != NULL) { 527 newgr->gr_name = dst; 528 dst = stpcpy(dst, gr->gr_name) + 1; 529 } else 530 newgr->gr_name = NULL; 531 if (gr->gr_passwd != NULL) { 532 newgr->gr_passwd = dst; 533 dst = stpcpy(dst, gr->gr_passwd) + 1; 534 } else 535 newgr->gr_passwd = NULL; 536 newgr->gr_gid = gr->gr_gid; 537 i = 0; 538 /* Original group struct might have a NULL gr_mem */ 539 if (gr->gr_mem != NULL) { 540 for (; gr->gr_mem[i] != NULL; i++) { 541 newgr->gr_mem[i] = dst; 542 dst = stpcpy(dst, gr->gr_mem[i]) + 1; 543 } 544 } 545 /* If name is not NULL, newgr->gr_mem is known to be not NULL */ 546 if (name != NULL) { 547 newgr->gr_mem[i++] = dst; 548 dst = stpcpy(dst, name) + 1; 549 } 550 /* if newgr->gr_mem is not NULL add NULL marker */ 551 if (newgr->gr_mem != NULL) 552 newgr->gr_mem[i] = NULL; 553 554 return (newgr); 555 } 556 557 /* 558 * Calculate length of a struct group + given name 559 */ 560 static size_t 561 grmemlen(const struct group *gr, const char *name, int *num_mem) 562 { 563 size_t len; 564 int i; 565 566 if (gr == NULL) 567 return (0); 568 /* Calculate size of the group. */ 569 len = sizeof(*gr); 570 if (gr->gr_name != NULL) 571 len += strlen(gr->gr_name) + 1; 572 if (gr->gr_passwd != NULL) 573 len += strlen(gr->gr_passwd) + 1; 574 i = 0; 575 if (gr->gr_mem != NULL) { 576 for (; gr->gr_mem[i] != NULL; i++) { 577 len += strlen(gr->gr_mem[i]) + 1; 578 len += sizeof(*gr->gr_mem); 579 } 580 } 581 if (name != NULL) { 582 i++; 583 len += strlen(name) + 1; 584 len += sizeof(*gr->gr_mem); 585 } 586 /* Allow for NULL pointer */ 587 if (i != 0) 588 len += sizeof(*gr->gr_mem); 589 *num_mem = i; 590 return(len); 591 } 592 593 /* 594 * Scan a line and place it into a group structure. 595 */ 596 static bool 597 __gr_scan(char *line, struct group *gr) 598 { 599 char *loc; 600 int ndx; 601 602 /* Assign non-member information to structure. */ 603 gr->gr_name = line; 604 if ((loc = strchr(line, ':')) == NULL) 605 return (false); 606 *loc = '\0'; 607 gr->gr_passwd = loc + 1; 608 if (*gr->gr_passwd == ':') 609 *gr->gr_passwd = '\0'; 610 else { 611 if ((loc = strchr(loc + 1, ':')) == NULL) 612 return (false); 613 *loc = '\0'; 614 } 615 if (sscanf(loc + 1, "%u", &gr->gr_gid) != 1) 616 return (false); 617 618 /* Assign member information to structure. */ 619 if ((loc = strchr(loc + 1, ':')) == NULL) 620 return (false); 621 line = loc + 1; 622 gr->gr_mem = NULL; 623 ndx = 0; 624 do { 625 gr->gr_mem = reallocf(gr->gr_mem, sizeof(*gr->gr_mem) * 626 (ndx + 1)); 627 if (gr->gr_mem == NULL) 628 return (false); 629 630 /* Skip locations without members (i.e., empty string). */ 631 do { 632 gr->gr_mem[ndx] = strsep(&line, ","); 633 } while (gr->gr_mem[ndx] != NULL && *gr->gr_mem[ndx] == '\0'); 634 } while (gr->gr_mem[ndx++] != NULL); 635 636 return (true); 637 } 638 639 /* 640 * Create a struct group from a line. 641 */ 642 struct group * 643 gr_scan(const char *line) 644 { 645 struct group gr; 646 char *line_copy; 647 struct group *new_gr; 648 649 if ((line_copy = strdup(line)) == NULL) 650 return (NULL); 651 if (!__gr_scan(line_copy, &gr)) { 652 free(line_copy); 653 return (NULL); 654 } 655 new_gr = gr_dup(&gr); 656 free(line_copy); 657 if (gr.gr_mem != NULL) 658 free(gr.gr_mem); 659 660 return (new_gr); 661 } 662