Here's a diff leaving -G alone and introducing -S for setting a user's group. I am pretty unsure about my choice of F_SETSECGROUP, didn't knew if I should choose 0x1200 or 0x1001 or even something completely different.
Index: src/usr.sbin/user/user.c =================================================================== RCS file: /cvs/src/usr.sbin/user/user.c,v retrieving revision 1.79 diff -u -r1.79 user.c --- src/usr.sbin/user/user.c 6 Apr 2011 11:36:26 -0000 1.79 +++ src/usr.sbin/user/user.c 12 Apr 2011 17:36:01 -0000 @@ -101,7 +101,8 @@ F_SHELL = 0x0200, F_UID = 0x0400, F_USERNAME = 0x0800, - F_CLASS = 0x1000 + F_CLASS = 0x1000, + F_SETSECGROUP = 0x1001 }; #define CONFFILE "/etc/usermgmt.conf" @@ -485,6 +486,138 @@ return 1; } +/* set group membership on all `groups' for `user' */ +static int +set_group(char *user, int ngroups, const char **groups) +{ + struct group *grp; + struct stat st; + size_t login_len; + FILE *from; + FILE *to; + char buf[LINE_MAX]; + char f[MaxFileNameLen]; + char *colon; + char *cp; + char *ep; + int fd; + int cc; + int i; + int j; + + login_len = strlen(user); + for (i = 0 ; i < ngroups ; i++) { + if ((grp = getgrnam(groups[i])) == NULL) + warnx("can't append group `%s' for user `%s'", + groups[i], user); + } + if ((from = fopen(_PATH_GROUP, "r")) == NULL) { + warn("can't append group for `%s': can't open `%s'", user, + _PATH_GROUP); + return 0; + } + if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) { + warn("can't lock `%s'", _PATH_GROUP); + } + (void) fstat(fileno(from), &st); + (void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP); + if ((fd = mkstemp(f)) < 0) { + (void) fclose(from); + warn("can't append group: mkstemp failed"); + return 0; + } + if ((to = fdopen(fd, "w")) == NULL) { + (void) fclose(from); + (void) close(fd); + (void) unlink(f); + warn("can't append group: fdopen `%s' failed", f); + return 0; + } + while (fgets(buf, sizeof(buf), from) != NULL) { + cc = strlen(buf); + if (cc > 0 && buf[cc - 1] != '\n' && !feof(from)) { + while (fgetc(from) != '\n' && !feof(from)) + cc++; + warnx("%s: line `%s' too long (%d bytes), skipping", + _PATH_GROUP, buf, cc); + continue; + } + if ((colon = strchr(buf, ':')) == NULL) { + warnx("badly formed entry `%s'", buf); + continue; + } + + /* remove user from group */ + for (cp = buf, cc = 0; *cp != '\0' && cc < 3; cp++) { + if (*cp == ':') + cc++; + } + if (cc != 3) { + buf[strcspn(buf, "\n")] = '\0'; + warnx("Malformed entry `%s'. Skipping", buf); + continue; + } + while ((cp = strstr(cp, user)) != NULL) { + if ((cp[-1] == ':' || cp[-1] == ',') && + (cp[login_len] == ',' || cp[login_len] == '\n')) { + ep = cp + login_len; + if (cp[login_len] == ',') + ep++; + else if (cp[-1] == ',') + cp--; + memmove(cp, ep, strlen(ep) + 1); + } else { + if ((cp = strchr(cp, ',')) == NULL) + break; + cp++; + } + } + cc = strlen(buf); + + /* add user to groups */ + for (i = 0 ; i < ngroups ; i++) { + j = (int)(colon - buf); + if (strncmp(groups[i], buf, j) == 0 && + groups[i][j] == '\0') { + while (isspace(buf[cc - 1])) + cc--; + buf[(j = cc)] = '\0'; + if (buf[strlen(buf) - 1] != ':') + strlcat(buf, ",", sizeof(buf)); + cc = strlcat(buf, user, sizeof(buf)) + 1; + if (cc >= sizeof(buf)) { + warnx("Warning: group `%s' would " + "become too long, not modifying", + groups[i]); + cc = j + 1; + } + buf[cc - 1] = '\n'; + buf[cc] = '\0'; + } + } + if (fwrite(buf, cc, 1, to) != 1) { + (void) fclose(from); + (void) fclose(to); + (void) unlink(f); + warn("can't append group: short write to `%s'", f); + return 0; + } + } + (void) fclose(from); + if (fclose(to) == EOF) { + (void) unlink(f); + warn("can't append group: short write to `%s'", f); + return 0; + } + if (rename(f, _PATH_GROUP) < 0) { + (void) unlink(f); + warn("can't append group: can't rename `%s' to `%s'", f, _PATH_GROUP); + return 0; + } + (void) chmod(_PATH_GROUP, st.st_mode & 07777); + return 1; +} + /* modify the group entries for all `groups', by adding `user' */ static int append_group(char *user, int ngroups, const char **groups) @@ -1537,12 +1670,18 @@ err(EXIT_FAILURE, "can't move `%s' to `%s'", homedir, pwp->pw_dir); } - if (up->u_groupc > 0 && + if (up->u_groupc > 0 && (up->u_flags & F_SECGROUP) && !append_group(newlogin, up->u_groupc, up->u_groupv)) { (void) close(ptmpfd); pw_abort(); errx(EXIT_FAILURE, "can't append `%s' to new groups", newlogin); + } else if (up->u_groupc > 0 && (up->u_flags & F_SETSECGROUP) && + !set_group(newlogin, up->u_groupc, up->u_groupv)) { + (void) close(ptmpfd); + pw_abort(); + errx(EXIT_FAILURE, "can't append `%s' to new groups", + newlogin); } } (void) close(ptmpfd); @@ -1621,8 +1760,8 @@ " [-s shell] [-u uid] user\n", prog); } else if (strcmp(prog, "usermod") == 0) { (void) fprintf(stderr, "usage: %s [-mov] " - "[-G secondary-group[,group,...]] [-c comment]\n" - " [-d home-directory] [-e expiry-time] " + "[-G secondary-group[,group,...] | -S secondary-group[,group,...]]\n" + " [-c comment] [-d home-directory] [-e expiry-time] " "[-f inactive-time]\n" " [-g gid | name | =uid] [-L login-class] " "[-l new-login]\n" @@ -1820,7 +1959,7 @@ free(u.u_primgrp); u.u_primgrp = NULL; have_new_user = 0; - while ((c = getopt(argc, argv, "G:c:d:e:f:g:l:mos:u:" MOD_OPT_EXTENSIONS)) != -1) { + while ((c = getopt(argc, argv, "G:S:c:d:e:f:g:l:mos:u:" MOD_OPT_EXTENSIONS)) != -1) { switch(c) { case 'G': while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL && @@ -1834,6 +1973,18 @@ } u.u_flags |= F_SECGROUP; break; + case 'S': + while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL && + u.u_groupc < NGROUPS_MAX - 2) { + if (u.u_groupv[u.u_groupc][0] != 0) { + u.u_groupc++; + } + } + if (optarg != NULL) { + warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2); + } + u.u_flags |= F_SETSECGROUP; + break; case 'c': memsave(&u.u_comment, optarg, strlen(optarg)); u.u_flags |= F_COMMENT; @@ -1907,6 +2058,8 @@ warnx("option 'm' useless without 'd' or 'l' -- ignored"); u.u_flags &= ~F_MKDIR; } + if ((u.u_flags & F_SECGROUP) && (u.u_flags & F_SETSECGROUP)) + errx(EXIT_FAILURE, "options 'G' and 'S' are mutually exclusive"); argc -= optind; argv += optind; if (argc != 1) { Index: src/usr.sbin/user/usermod.8 =================================================================== RCS file: /cvs/src/usr.sbin/user/usermod.8,v retrieving revision 1.24 diff -u -r1.24 usermod.8 --- src/usr.sbin/user/usermod.8 3 Sep 2010 11:22:36 -0000 1.24 +++ src/usr.sbin/user/usermod.8 12 Apr 2011 17:36:01 -0000 @@ -41,10 +41,10 @@ .Nm usermod .Bk -words .Op Fl mov -.Op Fl G Ar secondary-group[,group,...] .Op Fl c Ar comment .Op Fl d Ar home-directory .Op Fl e Ar expiry-time +.Op Fl G Ar secondary-group[,group,...] | Fl S Ar secondary-group[,group,...] .Op Fl f Ar inactive-time .Oo .Fl g Ar gid | name | Li =uid @@ -103,11 +103,21 @@ .Fl e option. .It Fl G Ar secondary-group[,group,...] +Adds the user to the given groups. +.Fl G +and +.Fl S +are mutually exclusive. +.It Xo +.It Fl S Ar secondary-group[,group,...] Sets the secondary groups the user will be a member of in the .Pa /etc/group file. -.It Xo -.Fl g Ar gid | name | Li =uid +.Fl S +and +.Fl G +are mutually exclusive. +.It Fl g Ar gid | name | Li =uid .Xc Gives the group name or identifier to be used for the user's primary group. If this is