pw_group.c revision 284122
1/*- 2 * Copyright (C) 1996 3 * David L. Nugent. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27#ifndef lint 28static const char rcsid[] = 29 "$FreeBSD: head/usr.sbin/pw/pw_group.c 284122 2015-06-07 15:27:17Z bapt $"; 30#endif /* not lint */ 31 32#include <ctype.h> 33#include <err.h> 34#include <termios.h> 35#include <stdbool.h> 36#include <unistd.h> 37#include <grp.h> 38#include <libutil.h> 39 40#include "pw.h" 41#include "bitmap.h" 42 43 44static struct passwd *lookup_pwent(const char *user); 45static void delete_members(char ***members, int *grmembers, int *i, 46 struct carg *arg, struct group *grp); 47static int print_group(struct group * grp); 48static gid_t gr_gidpolicy(struct userconf * cnf, struct cargs * args); 49 50int 51pw_group(int mode, struct cargs * args) 52{ 53 int rc; 54 struct carg *a_newname = getarg(args, 'l'); 55 struct carg *a_name = getarg(args, 'n'); 56 struct carg *a_gid = getarg(args, 'g'); 57 struct carg *arg; 58 struct group *grp = NULL; 59 int grmembers = 0; 60 char **members = NULL; 61 struct userconf *cnf = conf.userconf; 62 63 static struct group fakegroup = 64 { 65 "nogroup", 66 "*", 67 -1, 68 NULL 69 }; 70 71 if (a_gid != NULL) { 72 if (strspn(a_gid->val, "0123456789") != strlen(a_gid->val)) 73 errx(EX_USAGE, "-g expects a number"); 74 } 75 76 if (mode == M_LOCK || mode == M_UNLOCK) 77 errx(EX_USAGE, "'lock' command is not available for groups"); 78 79 /* 80 * With M_NEXT, we only need to return the 81 * next gid to stdout 82 */ 83 if (mode == M_NEXT) { 84 gid_t next = gr_gidpolicy(cnf, args); 85 if (getarg(args, 'q')) 86 return next; 87 printf("%u\n", next); 88 return EXIT_SUCCESS; 89 } 90 91 if (mode == M_PRINT && getarg(args, 'a')) { 92 SETGRENT(); 93 while ((grp = GETGRENT()) != NULL) 94 print_group(grp); 95 ENDGRENT(); 96 return EXIT_SUCCESS; 97 } 98 if (a_gid == NULL) { 99 if (a_name == NULL) 100 errx(EX_DATAERR, "group name or id required"); 101 102 if (mode != M_ADD && grp == NULL && isdigit((unsigned char)*a_name->val)) { 103 (a_gid = a_name)->ch = 'g'; 104 a_name = NULL; 105 } 106 } 107 grp = (a_name != NULL) ? GETGRNAM(a_name->val) : GETGRGID((gid_t) atoi(a_gid->val)); 108 109 if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) { 110 if (a_name == NULL && grp == NULL) /* Try harder */ 111 grp = GETGRGID(atoi(a_gid->val)); 112 113 if (grp == NULL) { 114 if (mode == M_PRINT && getarg(args, 'F')) { 115 char *fmems[1]; 116 fmems[0] = NULL; 117 fakegroup.gr_name = a_name ? a_name->val : "nogroup"; 118 fakegroup.gr_gid = a_gid ? (gid_t) atol(a_gid->val) : (gid_t)-1; 119 fakegroup.gr_mem = fmems; 120 return print_group(&fakegroup); 121 } 122 errx(EX_DATAERR, "unknown group `%s'", a_name ? a_name->val : a_gid->val); 123 } 124 if (a_name == NULL) /* Needed later */ 125 a_name = addarg(args, 'n', grp->gr_name); 126 127 /* 128 * Handle deletions now 129 */ 130 if (mode == M_DELETE) { 131 gid_t gid = grp->gr_gid; 132 133 rc = delgrent(grp); 134 if (rc == -1) 135 err(EX_IOERR, "group '%s' not available (NIS?)", grp->gr_name); 136 else if (rc != 0) { 137 err(EX_IOERR, "group update"); 138 } 139 pw_log(cnf, mode, W_GROUP, "%s(%u) removed", a_name->val, gid); 140 return EXIT_SUCCESS; 141 } else if (mode == M_PRINT) 142 return print_group(grp); 143 144 if (a_gid) 145 grp->gr_gid = (gid_t) atoi(a_gid->val); 146 147 if (a_newname != NULL) 148 grp->gr_name = pw_checkname(a_newname->val, 0); 149 } else { 150 if (a_name == NULL) /* Required */ 151 errx(EX_DATAERR, "group name required"); 152 else if (grp != NULL) /* Exists */ 153 errx(EX_DATAERR, "group name `%s' already exists", a_name->val); 154 155 extendarray(&members, &grmembers, 200); 156 members[0] = NULL; 157 grp = &fakegroup; 158 grp->gr_name = pw_checkname(a_name->val, 0); 159 grp->gr_passwd = "*"; 160 grp->gr_gid = gr_gidpolicy(cnf, args); 161 grp->gr_mem = members; 162 } 163 164 /* 165 * This allows us to set a group password Group passwords is an 166 * antique idea, rarely used and insecure (no secure database) Should 167 * be discouraged, but it is apparently still supported by some 168 * software. 169 */ 170 171 if ((arg = getarg(args, 'h')) != NULL || 172 (arg = getarg(args, 'H')) != NULL) { 173 if (strcmp(arg->val, "-") == 0) 174 grp->gr_passwd = "*"; /* No access */ 175 else { 176 int fd = atoi(arg->val); 177 int precrypt = (arg->ch == 'H'); 178 int b; 179 int istty = isatty(fd); 180 struct termios t; 181 char *p, line[256]; 182 183 if (istty) { 184 if (tcgetattr(fd, &t) == -1) 185 istty = 0; 186 else { 187 struct termios n = t; 188 189 /* Disable echo */ 190 n.c_lflag &= ~(ECHO); 191 tcsetattr(fd, TCSANOW, &n); 192 printf("%sassword for group %s:", (mode == M_UPDATE) ? "New p" : "P", grp->gr_name); 193 fflush(stdout); 194 } 195 } 196 b = read(fd, line, sizeof(line) - 1); 197 if (istty) { /* Restore state */ 198 tcsetattr(fd, TCSANOW, &t); 199 fputc('\n', stdout); 200 fflush(stdout); 201 } 202 if (b < 0) 203 err(EX_OSERR, "-h file descriptor"); 204 line[b] = '\0'; 205 if ((p = strpbrk(line, " \t\r\n")) != NULL) 206 *p = '\0'; 207 if (!*line) 208 errx(EX_DATAERR, "empty password read on file descriptor %d", fd); 209 if (precrypt) { 210 if (strchr(line, ':') != NULL) 211 return EX_DATAERR; 212 grp->gr_passwd = line; 213 } else 214 grp->gr_passwd = pw_pwcrypt(line); 215 } 216 } 217 218 if (((arg = getarg(args, 'M')) != NULL || 219 (arg = getarg(args, 'd')) != NULL || 220 (arg = getarg(args, 'm')) != NULL) && arg->val) { 221 int i = 0; 222 char *p; 223 struct passwd *pwd; 224 225 /* Make sure this is not stay NULL with -M "" */ 226 extendarray(&members, &grmembers, 200); 227 if (arg->ch == 'd') 228 delete_members(&members, &grmembers, &i, arg, grp); 229 else if (arg->ch == 'm') { 230 int k = 0; 231 232 if (grp->gr_mem != NULL) { 233 while (grp->gr_mem[k] != NULL) { 234 if (extendarray(&members, &grmembers, i + 2) != -1) 235 members[i++] = grp->gr_mem[k]; 236 k++; 237 } 238 } 239 } 240 241 if (arg->ch != 'd') 242 for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) { 243 int j; 244 245 /* 246 * Check for duplicates 247 */ 248 pwd = lookup_pwent(p); 249 for (j = 0; j < i && strcmp(members[j], pwd->pw_name) != 0; j++) 250 ; 251 if (j == i && extendarray(&members, &grmembers, i + 2) != -1) 252 members[i++] = newstr(pwd->pw_name); 253 } 254 while (i < grmembers) 255 members[i++] = NULL; 256 grp->gr_mem = members; 257 } 258 259 if (conf.dryrun) 260 return print_group(grp); 261 262 if (mode == M_ADD && (rc = addgrent(grp)) != 0) { 263 if (rc == -1) 264 errx(EX_IOERR, "group '%s' already exists", 265 grp->gr_name); 266 else 267 err(EX_IOERR, "group update"); 268 } else if (mode == M_UPDATE && (rc = chggrent(a_name->val, grp)) != 0) { 269 if (rc == -1) 270 errx(EX_IOERR, "group '%s' not available (NIS?)", 271 grp->gr_name); 272 else 273 err(EX_IOERR, "group update"); 274 } 275 276 arg = a_newname != NULL ? a_newname : a_name; 277 /* grp may have been invalidated */ 278 if ((grp = GETGRNAM(arg->val)) == NULL) 279 errx(EX_SOFTWARE, "group disappeared during update"); 280 281 pw_log(cnf, mode, W_GROUP, "%s(%u)", grp->gr_name, grp->gr_gid); 282 283 free(members); 284 285 return EXIT_SUCCESS; 286} 287 288 289/* 290 * Lookup a passwd entry using a name or UID. 291 */ 292static struct passwd * 293lookup_pwent(const char *user) 294{ 295 struct passwd *pwd; 296 297 if ((pwd = GETPWNAM(user)) == NULL && 298 (!isdigit((unsigned char)*user) || 299 (pwd = getpwuid((uid_t) atoi(user))) == NULL)) 300 errx(EX_NOUSER, "user `%s' does not exist", user); 301 302 return (pwd); 303} 304 305 306/* 307 * Delete requested members from a group. 308 */ 309static void 310delete_members(char ***members, int *grmembers, int *i, struct carg *arg, 311 struct group *grp) 312{ 313 bool matchFound; 314 char *user; 315 char *valueCopy; 316 char *valuePtr; 317 int k; 318 struct passwd *pwd; 319 320 if (grp->gr_mem == NULL) 321 return; 322 323 k = 0; 324 while (grp->gr_mem[k] != NULL) { 325 matchFound = false; 326 if ((valueCopy = strdup(arg->val)) == NULL) 327 errx(EX_UNAVAILABLE, "out of memory"); 328 valuePtr = valueCopy; 329 while ((user = strsep(&valuePtr, ", \t")) != NULL) { 330 pwd = lookup_pwent(user); 331 if (strcmp(grp->gr_mem[k], pwd->pw_name) == 0) { 332 matchFound = true; 333 break; 334 } 335 } 336 free(valueCopy); 337 338 if (!matchFound && 339 extendarray(members, grmembers, *i + 2) != -1) 340 (*members)[(*i)++] = grp->gr_mem[k]; 341 342 k++; 343 } 344 345 return; 346} 347 348 349static gid_t 350gr_gidpolicy(struct userconf * cnf, struct cargs * args) 351{ 352 struct group *grp; 353 gid_t gid = (gid_t) - 1; 354 struct carg *a_gid = getarg(args, 'g'); 355 356 /* 357 * Check the given gid, if any 358 */ 359 if (a_gid != NULL) { 360 gid = (gid_t) atol(a_gid->val); 361 362 if ((grp = GETGRGID(gid)) != NULL && getarg(args, 'o') == NULL) 363 errx(EX_DATAERR, "gid `%u' has already been allocated", grp->gr_gid); 364 } else { 365 struct bitmap bm; 366 367 /* 368 * We need to allocate the next available gid under one of 369 * two policies a) Grab the first unused gid b) Grab the 370 * highest possible unused gid 371 */ 372 if (cnf->min_gid >= cnf->max_gid) { /* Sanity claus^H^H^H^Hheck */ 373 cnf->min_gid = 1000; 374 cnf->max_gid = 32000; 375 } 376 bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1); 377 378 /* 379 * Now, let's fill the bitmap from the password file 380 */ 381 SETGRENT(); 382 while ((grp = GETGRENT()) != NULL) 383 if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid && 384 (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid) 385 bm_setbit(&bm, grp->gr_gid - cnf->min_gid); 386 ENDGRENT(); 387 388 /* 389 * Then apply the policy, with fallback to reuse if necessary 390 */ 391 if (cnf->reuse_gids) 392 gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); 393 else { 394 gid = (gid_t) (bm_lastset(&bm) + 1); 395 if (!bm_isset(&bm, gid)) 396 gid += cnf->min_gid; 397 else 398 gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); 399 } 400 401 /* 402 * Another sanity check 403 */ 404 if (gid < cnf->min_gid || gid > cnf->max_gid) 405 errx(EX_SOFTWARE, "unable to allocate a new gid - range fully used"); 406 bm_dealloc(&bm); 407 } 408 return gid; 409} 410 411 412static int 413print_group(struct group * grp) 414{ 415 if (!conf.pretty) { 416 char *buf = NULL; 417 418 buf = gr_make(grp); 419 printf("%s\n", buf); 420 free(buf); 421 } else { 422 int i; 423 424 printf("Group Name: %-15s #%lu\n" 425 " Members: ", 426 grp->gr_name, (long) grp->gr_gid); 427 if (grp->gr_mem != NULL) { 428 for (i = 0; grp->gr_mem[i]; i++) 429 printf("%s%s", i ? "," : "", grp->gr_mem[i]); 430 } 431 fputs("\n\n", stdout); 432 } 433 return EXIT_SUCCESS; 434} 435