pw_group.c revision 283814
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 283814 2015-05-31 11:23:19Z 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, int pretty); 48static gid_t gr_gidpolicy(struct userconf * cnf, struct cargs * args); 49 50int 51pw_group(struct userconf * cnf, 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 62 static struct group fakegroup = 63 { 64 "nogroup", 65 "*", 66 -1, 67 NULL 68 }; 69 70 if (a_gid != NULL) { 71 if (strspn(a_gid->val, "0123456789") != strlen(a_gid->val)) 72 errx(EX_USAGE, "-g expects a number"); 73 } 74 75 if (mode == M_LOCK || mode == M_UNLOCK) 76 errx(EX_USAGE, "'lock' command is not available for groups"); 77 78 /* 79 * With M_NEXT, we only need to return the 80 * next gid to stdout 81 */ 82 if (mode == M_NEXT) { 83 gid_t next = gr_gidpolicy(cnf, args); 84 if (getarg(args, 'q')) 85 return next; 86 printf("%ld\n", (long)next); 87 return EXIT_SUCCESS; 88 } 89 90 if (mode == M_PRINT && getarg(args, 'a')) { 91 int pretty = getarg(args, 'P') != NULL; 92 93 SETGRENT(); 94 while ((grp = GETGRENT()) != NULL) 95 print_group(grp, pretty); 96 ENDGRENT(); 97 return EXIT_SUCCESS; 98 } 99 if (a_gid == NULL) { 100 if (a_name == NULL) 101 errx(EX_DATAERR, "group name or id required"); 102 103 if (mode != M_ADD && grp == NULL && isdigit((unsigned char)*a_name->val)) { 104 (a_gid = a_name)->ch = 'g'; 105 a_name = NULL; 106 } 107 } 108 grp = (a_name != NULL) ? GETGRNAM(a_name->val) : GETGRGID((gid_t) atoi(a_gid->val)); 109 110 if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) { 111 if (a_name == NULL && grp == NULL) /* Try harder */ 112 grp = GETGRGID(atoi(a_gid->val)); 113 114 if (grp == NULL) { 115 if (mode == M_PRINT && getarg(args, 'F')) { 116 char *fmems[1]; 117 fmems[0] = NULL; 118 fakegroup.gr_name = a_name ? a_name->val : "nogroup"; 119 fakegroup.gr_gid = a_gid ? (gid_t) atol(a_gid->val) : -1; 120 fakegroup.gr_mem = fmems; 121 return print_group(&fakegroup, getarg(args, 'P') != NULL); 122 } 123 errx(EX_DATAERR, "unknown group `%s'", a_name ? a_name->val : a_gid->val); 124 } 125 if (a_name == NULL) /* Needed later */ 126 a_name = addarg(args, 'n', grp->gr_name); 127 128 /* 129 * Handle deletions now 130 */ 131 if (mode == M_DELETE) { 132 gid_t gid = grp->gr_gid; 133 134 rc = delgrent(grp); 135 if (rc == -1) 136 err(EX_IOERR, "group '%s' not available (NIS?)", grp->gr_name); 137 else if (rc != 0) { 138 err(EX_IOERR, "group update"); 139 } 140 pw_log(cnf, mode, W_GROUP, "%s(%ld) removed", a_name->val, (long) gid); 141 return EXIT_SUCCESS; 142 } else if (mode == M_PRINT) 143 return print_group(grp, getarg(args, 'P') != NULL); 144 145 if (a_gid) 146 grp->gr_gid = (gid_t) atoi(a_gid->val); 147 148 if (a_newname != NULL) 149 grp->gr_name = pw_checkname((u_char *)a_newname->val, 0); 150 } else { 151 if (a_name == NULL) /* Required */ 152 errx(EX_DATAERR, "group name required"); 153 else if (grp != NULL) /* Exists */ 154 errx(EX_DATAERR, "group name `%s' already exists", a_name->val); 155 156 extendarray(&members, &grmembers, 200); 157 members[0] = NULL; 158 grp = &fakegroup; 159 grp->gr_name = pw_checkname((u_char *)a_name->val, 0); 160 grp->gr_passwd = "*"; 161 grp->gr_gid = gr_gidpolicy(cnf, args); 162 grp->gr_mem = members; 163 } 164 165 /* 166 * This allows us to set a group password Group passwords is an 167 * antique idea, rarely used and insecure (no secure database) Should 168 * be discouraged, but it is apparently still supported by some 169 * software. 170 */ 171 172 if ((arg = getarg(args, 'h')) != NULL || 173 (arg = getarg(args, 'H')) != NULL) { 174 if (strcmp(arg->val, "-") == 0) 175 grp->gr_passwd = "*"; /* No access */ 176 else { 177 int fd = atoi(arg->val); 178 int precrypt = (arg->ch == 'H'); 179 int b; 180 int istty = isatty(fd); 181 struct termios t; 182 char *p, line[256]; 183 184 if (istty) { 185 if (tcgetattr(fd, &t) == -1) 186 istty = 0; 187 else { 188 struct termios n = t; 189 190 /* Disable echo */ 191 n.c_lflag &= ~(ECHO); 192 tcsetattr(fd, TCSANOW, &n); 193 printf("%sassword for group %s:", (mode == M_UPDATE) ? "New p" : "P", grp->gr_name); 194 fflush(stdout); 195 } 196 } 197 b = read(fd, line, sizeof(line) - 1); 198 if (istty) { /* Restore state */ 199 tcsetattr(fd, TCSANOW, &t); 200 fputc('\n', stdout); 201 fflush(stdout); 202 } 203 if (b < 0) 204 err(EX_OSERR, "-h file descriptor"); 205 line[b] = '\0'; 206 if ((p = strpbrk(line, " \t\r\n")) != NULL) 207 *p = '\0'; 208 if (!*line) 209 errx(EX_DATAERR, "empty password read on file descriptor %d", fd); 210 if (precrypt) { 211 if (strchr(line, ':') != NULL) 212 return EX_DATAERR; 213 grp->gr_passwd = line; 214 } else 215 grp->gr_passwd = pw_pwcrypt(line); 216 } 217 } 218 219 if (((arg = getarg(args, 'M')) != NULL || 220 (arg = getarg(args, 'd')) != NULL || 221 (arg = getarg(args, 'm')) != NULL) && arg->val) { 222 int i = 0; 223 char *p; 224 struct passwd *pwd; 225 226 /* Make sure this is not stay NULL with -M "" */ 227 extendarray(&members, &grmembers, 200); 228 if (arg->ch == 'd') 229 delete_members(&members, &grmembers, &i, arg, grp); 230 else if (arg->ch == 'm') { 231 int k = 0; 232 233 if (grp->gr_mem != NULL) { 234 while (grp->gr_mem[k] != NULL) { 235 if (extendarray(&members, &grmembers, i + 2) != -1) 236 members[i++] = grp->gr_mem[k]; 237 k++; 238 } 239 } 240 } 241 242 if (arg->ch != 'd') 243 for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) { 244 int j; 245 246 /* 247 * Check for duplicates 248 */ 249 pwd = lookup_pwent(p); 250 for (j = 0; j < i && strcmp(members[j], pwd->pw_name) != 0; j++) 251 ; 252 if (j == i && extendarray(&members, &grmembers, i + 2) != -1) 253 members[i++] = newstr(pwd->pw_name); 254 } 255 while (i < grmembers) 256 members[i++] = NULL; 257 grp->gr_mem = members; 258 } 259 260 if (getarg(args, 'N') != NULL) 261 return print_group(grp, getarg(args, 'P') != NULL); 262 263 if (mode == M_ADD && (rc = addgrent(grp)) != 0) { 264 if (rc == -1) 265 errx(EX_IOERR, "group '%s' already exists", 266 grp->gr_name); 267 else 268 err(EX_IOERR, "group update"); 269 } else if (mode == M_UPDATE && (rc = chggrent(a_name->val, grp)) != 0) { 270 if (rc == -1) 271 errx(EX_IOERR, "group '%s' not available (NIS?)", 272 grp->gr_name); 273 else 274 err(EX_IOERR, "group update"); 275 } 276 277 arg = a_newname != NULL ? a_newname : a_name; 278 /* grp may have been invalidated */ 279 if ((grp = GETGRNAM(arg->val)) == NULL) 280 errx(EX_SOFTWARE, "group disappeared during update"); 281 282 pw_log(cnf, mode, W_GROUP, "%s(%ld)", grp->gr_name, (long) grp->gr_gid); 283 284 free(members); 285 286 return EXIT_SUCCESS; 287} 288 289 290/* 291 * Lookup a passwd entry using a name or UID. 292 */ 293static struct passwd * 294lookup_pwent(const char *user) 295{ 296 struct passwd *pwd; 297 298 if ((pwd = GETPWNAM(user)) == NULL && 299 (!isdigit((unsigned char)*user) || 300 (pwd = getpwuid((uid_t) atoi(user))) == NULL)) 301 errx(EX_NOUSER, "user `%s' does not exist", user); 302 303 return (pwd); 304} 305 306 307/* 308 * Delete requested members from a group. 309 */ 310static void 311delete_members(char ***members, int *grmembers, int *i, struct carg *arg, 312 struct group *grp) 313{ 314 bool matchFound; 315 char *user; 316 char *valueCopy; 317 char *valuePtr; 318 int k; 319 struct passwd *pwd; 320 321 if (grp->gr_mem == NULL) 322 return; 323 324 k = 0; 325 while (grp->gr_mem[k] != NULL) { 326 matchFound = false; 327 if ((valueCopy = strdup(arg->val)) == NULL) 328 errx(EX_UNAVAILABLE, "out of memory"); 329 valuePtr = valueCopy; 330 while ((user = strsep(&valuePtr, ", \t")) != NULL) { 331 pwd = lookup_pwent(user); 332 if (strcmp(grp->gr_mem[k], pwd->pw_name) == 0) { 333 matchFound = true; 334 break; 335 } 336 } 337 free(valueCopy); 338 339 if (!matchFound && 340 extendarray(members, grmembers, *i + 2) != -1) 341 (*members)[(*i)++] = grp->gr_mem[k]; 342 343 k++; 344 } 345 346 return; 347} 348 349 350static gid_t 351gr_gidpolicy(struct userconf * cnf, struct cargs * args) 352{ 353 struct group *grp; 354 gid_t gid = (gid_t) - 1; 355 struct carg *a_gid = getarg(args, 'g'); 356 357 /* 358 * Check the given gid, if any 359 */ 360 if (a_gid != NULL) { 361 gid = (gid_t) atol(a_gid->val); 362 363 if ((grp = GETGRGID(gid)) != NULL && getarg(args, 'o') == NULL) 364 errx(EX_DATAERR, "gid `%ld' has already been allocated", (long) grp->gr_gid); 365 } else { 366 struct bitmap bm; 367 368 /* 369 * We need to allocate the next available gid under one of 370 * two policies a) Grab the first unused gid b) Grab the 371 * highest possible unused gid 372 */ 373 if (cnf->min_gid >= cnf->max_gid) { /* Sanity claus^H^H^H^Hheck */ 374 cnf->min_gid = 1000; 375 cnf->max_gid = 32000; 376 } 377 bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1); 378 379 /* 380 * Now, let's fill the bitmap from the password file 381 */ 382 SETGRENT(); 383 while ((grp = GETGRENT()) != NULL) 384 if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid && 385 (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid) 386 bm_setbit(&bm, grp->gr_gid - cnf->min_gid); 387 ENDGRENT(); 388 389 /* 390 * Then apply the policy, with fallback to reuse if necessary 391 */ 392 if (cnf->reuse_gids) 393 gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); 394 else { 395 gid = (gid_t) (bm_lastset(&bm) + 1); 396 if (!bm_isset(&bm, gid)) 397 gid += cnf->min_gid; 398 else 399 gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid); 400 } 401 402 /* 403 * Another sanity check 404 */ 405 if (gid < cnf->min_gid || gid > cnf->max_gid) 406 errx(EX_SOFTWARE, "unable to allocate a new gid - range fully used"); 407 bm_dealloc(&bm); 408 } 409 return gid; 410} 411 412 413static int 414print_group(struct group * grp, int pretty) 415{ 416 if (!pretty) { 417 char *buf = NULL; 418 419 buf = gr_make(grp); 420 printf("%s\n", buf); 421 free(buf); 422 } else { 423 int i; 424 425 printf("Group Name: %-15s #%lu\n" 426 " Members: ", 427 grp->gr_name, (long) grp->gr_gid); 428 if (grp->gr_mem != NULL) { 429 for (i = 0; grp->gr_mem[i]; i++) 430 printf("%s%s", i ? "," : "", grp->gr_mem[i]); 431 } 432 fputs("\n\n", stdout); 433 } 434 return EXIT_SUCCESS; 435} 436