X-Git-Url: https://git.cameronkatri.com/pw-darwin.git/blobdiff_plain/83c1d785dfb8bceaad5d4f1b9ce2f7cd4cb802a0..fb90d29ec2763027a93943b3f30250bd05afd62c:/libutil/gr_util.c diff --git a/libutil/gr_util.c b/libutil/gr_util.c index fc5e442..465efd9 100644 --- a/libutil/gr_util.c +++ b/libutil/gr_util.c @@ -28,55 +28,372 @@ __FBSDID("$FreeBSD$"); #include +#include +#include +#include +#include +#include #include #include #include +#include #include #include #include #include +#include -static const char GroupLineFormat[] = "%s:%s:%ju:"; +static int lockfd = -1; +static char group_dir[PATH_MAX]; +static char group_file[PATH_MAX]; +static char tempname[PATH_MAX]; +static int initialized; +static size_t grmemlen(const struct group *, const char *, int *); +static struct group *grcopy(const struct group *gr, char *mem, const char *, int ndx); /* - * Compares two struct group's. + * Initialize statics */ int -gr_equal(const struct group *gr1, const struct group *gr2) +gr_init(const char *dir, const char *group) { - int gr1Ndx; - int gr2Ndx; - bool equal; - bool found; - /* Check that the non-member information is the same. */ - equal = strcmp(gr1->gr_name, gr2->gr_name) == 0 && - strcmp(gr1->gr_passwd, gr2->gr_passwd) == 0 && - gr1->gr_gid == gr2->gr_gid; - - /* Check all members in both groups. */ - if (equal) { - for (found = false, gr1Ndx = 0; gr1->gr_mem[gr1Ndx] != NULL; - gr1Ndx++) { - for (gr2Ndx = 0; gr2->gr_mem[gr2Ndx] != NULL; gr2Ndx++) - if (strcmp(gr1->gr_mem[gr1Ndx], - gr2->gr_mem[gr2Ndx]) == 0) { - found = true; - break; - } - if (! found) { - equal = false; + if (dir == NULL) { + strcpy(group_dir, _PATH_ETC); + } else { + if (strlen(dir) >= sizeof(group_dir)) { + errno = ENAMETOOLONG; + return (-1); + } + strcpy(group_dir, dir); + } + + if (group == NULL) { + if (dir == NULL) { + strcpy(group_file, _PATH_GROUP); + } else if (snprintf(group_file, sizeof(group_file), "%s/group", + group_dir) > (int)sizeof(group_file)) { + errno = ENAMETOOLONG; + return (-1); + } + } else { + if (strlen(group) >= sizeof(group_file)) { + errno = ENAMETOOLONG; + return (-1); + } + strcpy(group_file, group); + } + + initialized = 1; + return (0); +} + +/* + * Lock the group file + */ +int +gr_lock(void) +{ + if (*group_file == '\0') + return (-1); + + for (;;) { + struct stat st; + + lockfd = flopen(group_file, O_RDONLY|O_NONBLOCK|O_CLOEXEC, 0); + if (lockfd == -1) { + if (errno == EWOULDBLOCK) { + errx(1, "the group file is busy"); + } else { + err(1, "could not lock the group file: "); + } + } + if (fstat(lockfd, &st) == -1) + err(1, "fstat() failed: "); + if (st.st_nlink != 0) + break; + close(lockfd); + lockfd = -1; + } + return (lockfd); +} + +/* + * Create and open a presmuably safe temp file for editing group data + */ +int +gr_tmp(int mfd) +{ + char buf[8192]; + ssize_t nr; + const char *p; + int tfd; + + if (*group_file == '\0') + return (-1); + if ((p = strrchr(group_file, '/'))) + ++p; + else + p = group_file; + if (snprintf(tempname, sizeof(tempname), "%.*sgroup.XXXXXX", + (int)(p - group_file), group_file) >= (int)sizeof(tempname)) { + errno = ENAMETOOLONG; + return (-1); + } + if ((tfd = mkstemp(tempname)) == -1) + return (-1); + if (mfd != -1) { + while ((nr = read(mfd, buf, sizeof(buf))) > 0) + if (write(tfd, buf, (size_t)nr) != nr) break; + if (nr != 0) { + unlink(tempname); + *tempname = '\0'; + close(tfd); + return (-1); + } + } + return (tfd); +} + +/* + * Copy the group file from one descriptor to another, replacing, deleting + * or adding a single record on the way. + */ +int +gr_copy(int ffd, int tfd, const struct group *gr, struct group *old_gr) +{ + char buf[8192], *end, *line, *p, *q, *r, t; + struct group *fgr; + const struct group *sgr; + size_t len; + int eof, readlen; + + if (old_gr == NULL && gr == NULL) + return(-1); + + sgr = old_gr; + /* deleting a group */ + if (gr == NULL) { + line = NULL; + } else { + if ((line = gr_make(gr)) == NULL) + return (-1); + } + + /* adding a group */ + if (sgr == NULL) + sgr = gr; + + eof = 0; + len = 0; + p = q = end = buf; + for (;;) { + /* find the end of the current line */ + for (p = q; q < end && *q != '\0'; ++q) + if (*q == '\n') + break; + + /* if we don't have a complete line, fill up the buffer */ + if (q >= end) { + if (eof) + break; + if ((size_t)(q - p) >= sizeof(buf)) { + warnx("group line too long"); + errno = EINVAL; /* hack */ + goto err; + } + if (p < end) { + q = memmove(buf, p, end -p); + end -= p - buf; + } else { + p = q = end = buf; } + readlen = read(ffd, end, sizeof(buf) - (end -buf)); + if (readlen == -1) + goto err; + else + len = (size_t)readlen; + if (len == 0 && p == buf) + break; + end += len; + len = end - buf; + if (len < (ssize_t)sizeof(buf)) { + eof = 1; + if (len > 0 && buf[len -1] != '\n') + ++len, *end++ = '\n'; + } + continue; } - /* Check that group2 does not have more members than group1. */ - if (gr2->gr_mem[gr1Ndx] != NULL) - equal = false; + /* is it a blank line or a comment? */ + for (r = p; r < q && isspace(*r); ++r) + /* nothing */; + if (r == q || *r == '#') { + /* yep */ + if (write(tfd, p, q -p + 1) != q - p + 1) + goto err; + ++q; + continue; + } + + /* is it the one we're looking for? */ + + t = *q; + *q = '\0'; + + fgr = gr_scan(r); + + /* fgr is either a struct group for the current line, + * or NULL if the line is malformed. + */ + + *q = t; + if (fgr == NULL || fgr->gr_gid != sgr->gr_gid) { + /* nope */ + if (fgr != NULL) + free(fgr); + if (write(tfd, p, q - p + 1) != q - p + 1) + goto err; + ++q; + continue; + } + if (old_gr && !gr_equal(fgr, old_gr)) { + warnx("entry inconsistent"); + free(fgr); + errno = EINVAL; /* hack */ + goto err; + } + free(fgr); + + /* it is, replace or remove it */ + if (line != NULL) { + len = strlen(line); + if (write(tfd, line, len) != (int) len) + goto err; + } else { + /* when removed, avoid the \n */ + q++; + } + /* we're done, just copy the rest over */ + for (;;) { + if (write(tfd, q, end - q) != end - q) + goto err; + q = buf; + readlen = read(ffd, buf, sizeof(buf)); + if (readlen == 0) + break; + else + len = (size_t)readlen; + if (readlen == -1) + goto err; + end = buf + len; + } + goto done; } - return (equal); + /* if we got here, we didn't find the old entry */ + if (line == NULL) { + errno = ENOENT; + goto err; + } + len = strlen(line); + if ((size_t)write(tfd, line, len) != len || + write(tfd, "\n", 1) != 1) + goto err; + done: + if (line != NULL) + free(line); + return (0); + err: + if (line != NULL) + free(line); + return (-1); +} + +/* + * Regenerate the group file + */ +int +gr_mkdb(void) +{ + if (chmod(tempname, 0644) != 0) + return (-1); + + return (rename(tempname, group_file)); +} + +/* + * Clean up. Preserves errno for the caller's convenience. + */ +void +gr_fini(void) +{ + int serrno; + + if (!initialized) + return; + initialized = 0; + serrno = errno; + if (*tempname != '\0') { + unlink(tempname); + *tempname = '\0'; + } + if (lockfd != -1) + close(lockfd); + errno = serrno; +} + +/* + * Compares two struct group's. + */ +int +gr_equal(const struct group *gr1, const struct group *gr2) +{ + int gr1_ndx; + int gr2_ndx; + + /* Check that the non-member information is the same. */ + if (gr1->gr_name == NULL || gr2->gr_name == NULL) { + if (gr1->gr_name != gr2->gr_name) + return (false); + } else if (strcmp(gr1->gr_name, gr2->gr_name) != 0) + return (false); + if (gr1->gr_passwd == NULL || gr2->gr_passwd == NULL) { + if (gr1->gr_passwd != gr2->gr_passwd) + return (false); + } else if (strcmp(gr1->gr_passwd, gr2->gr_passwd) != 0) + return (false); + if (gr1->gr_gid != gr2->gr_gid) + return (false); + + /* Check all members in both groups. + * getgrnam can return gr_mem with a pointer to NULL. + * gr_dup and gr_add strip out this superfluous NULL, setting + * gr_mem to NULL for no members. + */ + if (gr1->gr_mem != NULL && gr2->gr_mem != NULL) { + int i; + + for (i = 0; gr1->gr_mem[i] != NULL; i++) { + if (strcmp(gr1->gr_mem[i], gr2->gr_mem[i]) != 0) + return (false); + } + } + /* Count number of members in both structs */ + gr2_ndx = 0; + if (gr2->gr_mem != NULL) + for(; gr2->gr_mem[gr2_ndx] != NULL; gr2_ndx++) + /* empty */; + gr1_ndx = 0; + if (gr1->gr_mem != NULL) + for(; gr1->gr_mem[gr1_ndx] != NULL; gr1_ndx++) + /* empty */; + if (gr1_ndx != gr2_ndx) + return (false); + + return (true); } /* @@ -85,27 +402,35 @@ gr_equal(const struct group *gr1, const struct group *gr2) char * gr_make(const struct group *gr) { + const char *group_line_format = "%s:%s:%ju:"; + const char *sep; char *line; - size_t lineSize; + char *p; + size_t line_size; int ndx; /* Calculate the length of the group line. */ - lineSize = snprintf(NULL, 0, GroupLineFormat, gr->gr_name, + line_size = snprintf(NULL, 0, group_line_format, gr->gr_name, gr->gr_passwd, (uintmax_t)gr->gr_gid) + 1; - for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) - lineSize += strlen(gr->gr_mem[ndx]) + 1; - if (ndx > 0) - lineSize--; + if (gr->gr_mem != NULL) { + for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) + line_size += strlen(gr->gr_mem[ndx]) + 1; + if (ndx > 0) + line_size--; + } /* Create the group line and fill it. */ - if ((line = malloc(lineSize)) == NULL) + if ((line = p = malloc(line_size)) == NULL) return (NULL); - lineSize = snprintf(line, lineSize, GroupLineFormat, gr->gr_name, - gr->gr_passwd, (uintmax_t)gr->gr_gid); - for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) { - strcat(line, gr->gr_mem[ndx]); - if (gr->gr_mem[ndx + 1] != NULL) - strcat(line, ","); + p += sprintf(p, group_line_format, gr->gr_name, gr->gr_passwd, + (uintmax_t)gr->gr_gid); + if (gr->gr_mem != NULL) { + sep = ""; + for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) { + p = stpcpy(p, sep); + p = stpcpy(p, gr->gr_mem[ndx]); + sep = ","; + } } return (line); @@ -117,47 +442,129 @@ gr_make(const struct group *gr) struct group * gr_dup(const struct group *gr) { + return (gr_add(gr, NULL)); +} +/* + * Add a new member name to a struct group. + */ +struct group * +gr_add(const struct group *gr, const char *newmember) +{ + char *mem; size_t len; - struct group *ngr; - int ndx; - int numMem; - - /* Calculate size of group. */ - len = sizeof(*gr) + - (gr->gr_name != NULL ? strlen(gr->gr_name) + 1 : 0) + - (gr->gr_passwd != NULL ? strlen(gr->gr_passwd) + 1 : 0); - numMem = 0; - if (gr->gr_mem != NULL) { - for (; gr->gr_mem[numMem] != NULL; numMem++) - len += strlen(gr->gr_mem[numMem]) + 1; - len += (numMem + 1) * sizeof(*gr->gr_mem); - } + int num_mem; + num_mem = 0; + len = grmemlen(gr, newmember, &num_mem); /* Create new group and copy old group into it. */ - if ((ngr = calloc(1, len)) == NULL) + if ((mem = malloc(len)) == NULL) return (NULL); - len = sizeof(*ngr); - ngr->gr_gid = gr->gr_gid; + return (grcopy(gr, mem, newmember, num_mem)); +} + +/* It is safer to walk the pointers given at gr_mem since there is no + * guarantee the gr_mem + strings are contiguous in the given struct group + * but compactify the new group into the following form. + * + * The new struct is laid out like this in memory. The example given is + * for a group with two members only. + * + * { + * (char *name) + * (char *passwd) + * (int gid) + * (gr_mem * newgrp + sizeof(struct group) + sizeof(**)) points to gr_mem area + * gr_mem area + * (member1 *) + * (member2 *) + * (NULL) + * (name string) + * (passwd string) + * (member1 string) + * (member2 string) + * } + */ +/* + * Copy the contents of a group plus given name to a preallocated group struct + */ +static struct group * +grcopy(const struct group *gr, char *dst, const char *name, int ndx) +{ + int i; + struct group *newgr; + + newgr = (struct group *)(void *)dst; /* avoid alignment warning */ + dst += sizeof(*newgr); + if (ndx != 0) { + newgr->gr_mem = (char **)(void *)(dst); /* avoid alignment warning */ + dst += (ndx + 1) * sizeof(*newgr->gr_mem); + } else + newgr->gr_mem = NULL; if (gr->gr_name != NULL) { - ngr->gr_name = (char *)ngr + len; - len += sprintf(ngr->gr_name, "%s", gr->gr_name) + 1; - } + newgr->gr_name = dst; + dst = stpcpy(dst, gr->gr_name) + 1; + } else + newgr->gr_name = NULL; if (gr->gr_passwd != NULL) { - ngr->gr_passwd = (char *)ngr + len; - len += sprintf(ngr->gr_passwd, "%s", gr->gr_passwd) + 1; - } + newgr->gr_passwd = dst; + dst = stpcpy(dst, gr->gr_passwd) + 1; + } else + newgr->gr_passwd = NULL; + newgr->gr_gid = gr->gr_gid; + i = 0; + /* Original group struct might have a NULL gr_mem */ if (gr->gr_mem != NULL) { - ngr->gr_mem = (char **)((char *)ngr + len); - len += (numMem + 1) * sizeof(*ngr->gr_mem); - for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) { - ngr->gr_mem[ndx] = (char *)ngr + len; - len += sprintf(ngr->gr_mem[ndx], "%s", - gr->gr_mem[ndx]) + 1; + for (; gr->gr_mem[i] != NULL; i++) { + newgr->gr_mem[i] = dst; + dst = stpcpy(dst, gr->gr_mem[i]) + 1; } - ngr->gr_mem[ndx] = NULL; } + /* If name is not NULL, newgr->gr_mem is known to be not NULL */ + if (name != NULL) { + newgr->gr_mem[i++] = dst; + dst = stpcpy(dst, name) + 1; + } + /* if newgr->gr_mem is not NULL add NULL marker */ + if (newgr->gr_mem != NULL) + newgr->gr_mem[i] = NULL; - return (ngr); + return (newgr); +} + +/* + * Calculate length of a struct group + given name + */ +static size_t +grmemlen(const struct group *gr, const char *name, int *num_mem) +{ + size_t len; + int i; + + if (gr == NULL) + return (0); + /* Calculate size of the group. */ + len = sizeof(*gr); + if (gr->gr_name != NULL) + len += strlen(gr->gr_name) + 1; + if (gr->gr_passwd != NULL) + len += strlen(gr->gr_passwd) + 1; + i = 0; + if (gr->gr_mem != NULL) { + for (; gr->gr_mem[i] != NULL; i++) { + len += strlen(gr->gr_mem[i]) + 1; + len += sizeof(*gr->gr_mem); + } + } + if (name != NULL) { + i++; + len += strlen(name) + 1; + len += sizeof(*gr->gr_mem); + } + /* Allow for NULL pointer */ + if (i != 0) + len += sizeof(*gr->gr_mem); + *num_mem = i; + return(len); } /* @@ -190,15 +597,18 @@ __gr_scan(char *line, struct group *gr) return (false); line = loc + 1; gr->gr_mem = NULL; - if (*line != '\0') { - ndx = 0; + ndx = 0; + do { + gr->gr_mem = reallocf(gr->gr_mem, sizeof(*gr->gr_mem) * + (ndx + 1)); + if (gr->gr_mem == NULL) + return (false); + + /* Skip locations without members (i.e., empty string). */ do { - if ((gr->gr_mem = reallocf(gr->gr_mem, - sizeof(*gr->gr_mem) * (ndx + 1))) == NULL) - return (false); gr->gr_mem[ndx] = strsep(&line, ","); - } while (gr->gr_mem[ndx++] != NULL); - } + } while (gr->gr_mem[ndx] != NULL && *gr->gr_mem[ndx] == '\0'); + } while (gr->gr_mem[ndx++] != NULL); return (true); } @@ -210,19 +620,19 @@ struct group * gr_scan(const char *line) { struct group gr; - char *lineCopy; - struct group *newGr; + char *line_copy; + struct group *new_gr; - if ((lineCopy = strdup(line)) == NULL) + if ((line_copy = strdup(line)) == NULL) return (NULL); - if (!__gr_scan(lineCopy, &gr)) { - free(lineCopy); + if (!__gr_scan(line_copy, &gr)) { + free(line_copy); return (NULL); } - newGr = gr_dup(&gr); - free(lineCopy); + new_gr = gr_dup(&gr); + free(line_copy); if (gr.gr_mem != NULL) free(gr.gr_mem); - return (newGr); + return (new_gr); }