1 /* $NetBSD: user.c,v 1.46 2002/02/05 19:18:29 agc Exp $ */ 2 3 /* 4 * Copyright (c) 1999 Alistair G. Crooks. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. All advertising materials mentioning features or use of this software 15 * must display the following acknowledgement: 16 * This product includes software developed by Alistair G. Crooks. 17 * 4. The name of the author may not be used to endorse or promote 18 * products derived from this software without specific prior written 19 * permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 22 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 27 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 30 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 #include <sys/cdefs.h> 34 35 #ifndef lint 36 __COPYRIGHT("@(#) Copyright (c) 1999 \ 37 The NetBSD Foundation, Inc. All rights reserved."); 38 __RCSID("$NetBSD: user.c,v 1.46 2002/02/05 19:18:29 agc Exp $"); 39 #endif 40 41 #include <sys/types.h> 42 #include <sys/param.h> 43 #include <sys/stat.h> 44 45 #include <ctype.h> 46 #include <dirent.h> 47 #include <err.h> 48 #include <fcntl.h> 49 #include <grp.h> 50 #include <paths.h> 51 #include <pwd.h> 52 #include <stdarg.h> 53 #include <stdio.h> 54 #include <stdlib.h> 55 #include <string.h> 56 #include <time.h> 57 #include <unistd.h> 58 #include <util.h> 59 60 #include "defs.h" 61 #include "usermgmt.h" 62 63 64 /* this struct describes a uid range */ 65 typedef struct range_t { 66 int r_from; /* low uid */ 67 int r_to; /* high uid */ 68 } range_t; 69 70 /* this struct encapsulates the user information */ 71 typedef struct user_t { 72 int u_flags; /* see below */ 73 int u_uid; /* uid of user */ 74 char *u_password; /* encrypted password */ 75 char *u_comment; /* comment field */ 76 char *u_home; /* home directory */ 77 char *u_primgrp; /* primary group */ 78 int u_groupc; /* # of secondary groups */ 79 char *u_groupv[NGROUPS_MAX]; /* secondary groups */ 80 char *u_shell; /* user's shell */ 81 char *u_basedir; /* base directory for home */ 82 char *u_expire; /* when password will expire */ 83 int u_inactive; /* inactive */ 84 char *u_skeldir; /* directory for startup files */ 85 char *u_class; /* login class */ 86 unsigned u_rsize; /* size of range array */ 87 unsigned u_rc; /* # of ranges */ 88 range_t *u_rv; /* the ranges */ 89 unsigned u_defrc; /* # of ranges in defaults */ 90 int u_preserve; /* preserve uids on deletion */ 91 } user_t; 92 93 /* flags for which fields of the user_t replace the passwd entry */ 94 enum { 95 F_COMMENT = 0x0001, 96 F_DUPUID = 0x0002, 97 F_EXPIRE = 0x0004, 98 F_GROUP = 0x0008, 99 F_HOMEDIR = 0x0010, 100 F_MKDIR = 0x0020, 101 F_INACTIVE = 0x0040, 102 F_PASSWORD = 0x0080, 103 F_SECGROUP = 0x0100, 104 F_SHELL = 0x0200, 105 F_UID = 0x0400, 106 F_USERNAME = 0x0800, 107 F_CLASS = 0x1000 108 }; 109 110 #define CONFFILE "/etc/usermgmt.conf" 111 112 #ifndef DEF_GROUP 113 #define DEF_GROUP "users" 114 #endif 115 116 #ifndef DEF_BASEDIR 117 #define DEF_BASEDIR "/home" 118 #endif 119 120 #ifndef DEF_SKELDIR 121 #define DEF_SKELDIR "/etc/skel" 122 #endif 123 124 #ifndef DEF_SHELL 125 #define DEF_SHELL _PATH_CSHELL 126 #endif 127 128 #ifndef DEF_COMMENT 129 #define DEF_COMMENT "" 130 #endif 131 132 #ifndef DEF_LOWUID 133 #define DEF_LOWUID 1000 134 #endif 135 136 #ifndef DEF_HIGHUID 137 #define DEF_HIGHUID 60000 138 #endif 139 140 #ifndef DEF_INACTIVE 141 #define DEF_INACTIVE 0 142 #endif 143 144 #ifndef DEF_EXPIRE 145 #define DEF_EXPIRE NULL 146 #endif 147 148 #ifndef DEF_CLASS 149 #define DEF_CLASS "" 150 #endif 151 152 #ifndef WAITSECS 153 #define WAITSECS 10 154 #endif 155 156 #ifndef NOBODY_UID 157 #define NOBODY_UID 32767 158 #endif 159 160 /* some useful constants */ 161 enum { 162 MaxShellNameLen = 256, 163 MaxFileNameLen = MAXPATHLEN, 164 MaxUserNameLen = 32, 165 MaxFieldNameLen = 32, 166 MaxCommandLen = 2048, 167 MaxEntryLen = 2048, 168 PasswordLength = 13, 169 170 LowGid = DEF_LOWUID, 171 HighGid = DEF_HIGHUID 172 }; 173 174 /* Full paths of programs used here */ 175 #define CHMOD "/bin/chmod" 176 #define CHOWN "/usr/sbin/chown" 177 #define MKDIR "/bin/mkdir" 178 #define MV "/bin/mv" 179 #define NOLOGIN "/sbin/nologin" 180 #define PAX "/bin/pax" 181 #define RM "/bin/rm" 182 183 #define UNSET_EXPIRY "Null (unset)" 184 185 static int asystem(const char *fmt, ...) 186 __attribute__((__format__(__printf__, 1, 2))); 187 188 static int verbose; 189 190 /* if *cpp is non-null, free it, then assign `n' chars of `s' to it */ 191 static void 192 memsave(char **cpp, char *s, size_t n) 193 { 194 if (*cpp != NULL) { 195 FREE(*cpp); 196 } 197 NEWARRAY(char, *cpp, n + 1, exit(1)); 198 (void) memcpy(*cpp, s, n); 199 (*cpp)[n] = '\0'; 200 } 201 202 /* a replacement for system(3) */ 203 static int 204 asystem(const char *fmt, ...) 205 { 206 va_list vp; 207 char buf[MaxCommandLen]; 208 int ret; 209 210 va_start(vp, fmt); 211 (void) vsnprintf(buf, sizeof(buf), fmt, vp); 212 va_end(vp); 213 if (verbose) { 214 (void) printf("Command: %s\n", buf); 215 } 216 if ((ret = system(buf)) != 0) { 217 warnx("[Warning] can't system `%s'", buf); 218 } 219 return ret; 220 } 221 222 /* remove a users home directory, returning 1 for success (ie, no problems encountered) */ 223 static int 224 removehomedir(const char *user, int uid, const char *dir) 225 { 226 struct stat st; 227 228 /* userid not root? */ 229 if (uid == 0) { 230 warnx("Not deleting home directory `%s'; userid is 0", dir); 231 return 0; 232 } 233 234 /* directory exists (and is a directory!) */ 235 if (stat(dir, &st) < 0) { 236 warnx("Home directory `%s' doesn't exist", dir); 237 return 0; 238 } 239 if (!S_ISDIR(st.st_mode)) { 240 warnx("Home directory `%s' is not a directory", dir); 241 return 0; 242 } 243 244 /* userid matches directory owner? */ 245 if (st.st_uid != uid) { 246 warnx("User `%s' doesn't own directory `%s', not removed\n", user, dir); 247 return 0; 248 } 249 250 (void) seteuid(uid); 251 /* we add the "|| true" to keep asystem() quiet if there is a non-zero exit status. */ 252 (void) asystem("%s -rf %s > /dev/null 2>&1 || true", RM, dir); 253 (void) seteuid(0); 254 if (rmdir(dir) < 0) { 255 warnx("Unable to remove all files in `%s'\n", dir); 256 return 0; 257 } 258 return 1; 259 } 260 261 #define NetBSD_1_4_K 104110000 262 263 #if defined(__NetBSD_Version__) && (__NetBSD_Version__ < NetBSD_1_4_K) 264 /* bounds checking strncpy */ 265 static int 266 strlcpy(char *to, char *from, size_t tosize) 267 { 268 size_t n; 269 int fromsize; 270 271 fromsize = strlen(from); 272 n = MIN(tosize - 1, fromsize); 273 (void) memcpy(to, from, n); 274 to[n] = '\0'; 275 return fromsize; 276 } 277 #endif /* NetBSD < 1.4K */ 278 279 /* 280 * Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com> 281 * All rights reserved. 282 * 283 * Redistribution and use in source and binary forms, with or without 284 * modification, are permitted provided that the following conditions 285 * are met: 286 * 1. Redistributions of source code must retain the above copyright 287 * notice, this list of conditions and the following disclaimer. 288 * 2. Redistributions in binary form must reproduce the above copyright 289 * notice, this list of conditions and the following disclaimer in the 290 * documentation and/or other materials provided with the distribution. 291 * 3. The name of the author may not be used to endorse or promote products 292 * derived from this software without specific prior written permission. 293 * 294 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 295 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 296 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 297 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 298 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 299 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 300 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 301 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 302 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 303 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 304 */ 305 306 /* 307 * research has shown that NetBSD 1.3H was the first version of -current 308 * with asprintf in libc. agc 309 */ 310 #define NetBSD_1_3_H 103080000 311 312 #if defined(__NetBSD_Version__) && (__NetBSD_Version__ < NetBSD_1_3_H) 313 314 int 315 asprintf(char **str, char const *fmt, ...) 316 { 317 int ret; 318 va_list ap; 319 FILE f; 320 unsigned char *_base; 321 322 f._flags = __SWR | __SSTR | __SALC; 323 f._bf._base = f._p = (unsigned char *)malloc(128); 324 if (f._bf._base == NULL) 325 goto err; 326 f._bf._size = f._w = 127; /* Leave room for the NUL */ 327 va_start(ap, fmt); 328 ret = vfprintf(&f, fmt, ap); 329 va_end(ap); 330 if (ret == -1) 331 goto err; 332 *f._p = '\0'; 333 _base = realloc(f._bf._base, (size_t)(ret + 1)); 334 if (_base == NULL) 335 goto err; 336 *str = (char *)_base; 337 return (ret); 338 339 err: 340 if (f._bf._base) 341 free(f._bf._base); 342 *str = NULL; 343 return (-1); 344 } 345 #endif /* NetBSD < 1.3H */ 346 347 /* return 1 if all of `s' is numeric */ 348 static int 349 is_number(char *s) 350 { 351 for ( ; *s ; s++) { 352 if (!isdigit(*s)) { 353 return 0; 354 } 355 } 356 return 1; 357 } 358 359 /* 360 * check that the effective uid is 0 - called from funcs which will 361 * modify data and config files. 362 */ 363 static void 364 checkeuid(void) 365 { 366 if (geteuid() != 0) { 367 errx(EXIT_FAILURE, "Program must be run as root"); 368 } 369 } 370 371 /* copy any dot files into the user's home directory */ 372 static int 373 copydotfiles(char *skeldir, int uid, int gid, char *dir) 374 { 375 struct dirent *dp; 376 DIR *dirp; 377 int n; 378 379 if ((dirp = opendir(skeldir)) == NULL) { 380 warn("can't open source . files dir `%s'", skeldir); 381 return 0; 382 } 383 for (n = 0; (dp = readdir(dirp)) != NULL && n == 0 ; ) { 384 if (strcmp(dp->d_name, ".") == 0 || 385 strcmp(dp->d_name, "..") == 0) { 386 continue; 387 } 388 n = 1; 389 } 390 (void) closedir(dirp); 391 if (n == 0) { 392 warnx("No \"dot\" initialisation files found"); 393 } else { 394 (void) asystem("cd %s; %s -rw -pe %s . %s", 395 skeldir, PAX, (verbose) ? "-v" : "", dir); 396 } 397 (void) asystem("%s -R -h %d:%d %s", CHOWN, uid, gid, dir); 398 (void) asystem("%s -R u+w %s", CHMOD, dir); 399 return n; 400 } 401 402 /* create a group entry with gid `gid' */ 403 static int 404 creategid(char *group, int gid, char *name) 405 { 406 struct stat st; 407 FILE *from; 408 FILE *to; 409 char buf[MaxEntryLen]; 410 char f[MaxFileNameLen]; 411 int fd; 412 int cc; 413 414 if (getgrnam(group) != NULL) { 415 warnx("group `%s' already exists", group); 416 return 0; 417 } 418 if ((from = fopen(_PATH_GROUP, "r")) == NULL) { 419 warn("can't create gid for %s: can't open %s", name, _PATH_GROUP); 420 return 0; 421 } 422 if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) { 423 warn("can't lock `%s'", _PATH_GROUP); 424 } 425 (void) fstat(fileno(from), &st); 426 (void) snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP); 427 if ((fd = mkstemp(f)) < 0) { 428 (void) fclose(from); 429 warn("can't create gid: mkstemp failed"); 430 return 0; 431 } 432 if ((to = fdopen(fd, "w")) == NULL) { 433 (void) fclose(from); 434 (void) close(fd); 435 (void) unlink(f); 436 warn("can't create gid: fdopen `%s' failed", f); 437 return 0; 438 } 439 while ((cc = fread(buf, sizeof(char), sizeof(buf), from)) > 0) { 440 if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) { 441 (void) fclose(from); 442 (void) close(fd); 443 (void) unlink(f); 444 warn("can't create gid: short write to `%s'", f); 445 return 0; 446 } 447 } 448 (void) fprintf(to, "%s:*:%d:%s\n", group, gid, name); 449 (void) fclose(from); 450 (void) fclose(to); 451 if (rename(f, _PATH_GROUP) < 0) { 452 warn("can't create gid: can't rename `%s' to `%s'", f, _PATH_GROUP); 453 return 0; 454 } 455 (void) chmod(_PATH_GROUP, st.st_mode & 07777); 456 return 1; 457 } 458 459 /* modify the group entry with name `group' to be newent */ 460 static int 461 modify_gid(char *group, char *newent) 462 { 463 struct stat st; 464 FILE *from; 465 FILE *to; 466 char buf[MaxEntryLen]; 467 char f[MaxFileNameLen]; 468 char *colon; 469 int groupc; 470 int entc; 471 int fd; 472 int cc; 473 474 if ((from = fopen(_PATH_GROUP, "r")) == NULL) { 475 warn("can't create gid for %s: can't open %s", group, _PATH_GROUP); 476 return 0; 477 } 478 if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) { 479 warn("can't lock `%s'", _PATH_GROUP); 480 } 481 (void) fstat(fileno(from), &st); 482 (void) snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP); 483 if ((fd = mkstemp(f)) < 0) { 484 (void) fclose(from); 485 warn("can't create gid: mkstemp failed"); 486 return 0; 487 } 488 if ((to = fdopen(fd, "w")) == NULL) { 489 (void) fclose(from); 490 (void) close(fd); 491 (void) unlink(f); 492 warn("can't create gid: fdopen `%s' failed", f); 493 return 0; 494 } 495 groupc = strlen(group); 496 while (fgets(buf, sizeof(buf), from) != NULL) { 497 cc = strlen(buf); 498 if ((colon = strchr(buf, ':')) == NULL) { 499 warn("badly formed entry `%s'", buf); 500 continue; 501 } 502 entc = (int)(colon - buf); 503 if (entc == groupc && strncmp(group, buf, (unsigned) entc) == 0) { 504 if (newent == NULL) { 505 continue; 506 } else { 507 cc = strlen(newent); 508 (void) strlcpy(buf, newent, sizeof(buf)); 509 } 510 } 511 if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) { 512 (void) fclose(from); 513 (void) close(fd); 514 (void) unlink(f); 515 warn("can't create gid: short write to `%s'", f); 516 return 0; 517 } 518 } 519 (void) fclose(from); 520 (void) fclose(to); 521 if (rename(f, _PATH_GROUP) < 0) { 522 warn("can't create gid: can't rename `%s' to `%s'", f, _PATH_GROUP); 523 return 0; 524 } 525 (void) chmod(_PATH_GROUP, st.st_mode & 07777); 526 return 1; 527 } 528 529 /* modify the group entries for all `groups', by adding `user' */ 530 static int 531 append_group(char *user, int ngroups, char **groups) 532 { 533 struct group *grp; 534 struct stat st; 535 FILE *from; 536 FILE *to; 537 char buf[MaxEntryLen]; 538 char f[MaxFileNameLen]; 539 char *colon; 540 int groupc; 541 int entc; 542 int fd; 543 int nc; 544 int cc; 545 int i; 546 int j; 547 548 for (i = 0 ; i < ngroups ; i++) { 549 if ((grp = getgrnam(groups[i])) == NULL) { 550 warnx("can't append group `%s' for user `%s'", groups[i], user); 551 } else { 552 for (j = 0 ; grp->gr_mem[j] ; j++) { 553 if (strcmp(user, grp->gr_mem[j]) == 0) { 554 /* already in it */ 555 groups[i] = ""; 556 } 557 } 558 } 559 } 560 if ((from = fopen(_PATH_GROUP, "r")) == NULL) { 561 warn("can't append group for %s: can't open %s", user, _PATH_GROUP); 562 return 0; 563 } 564 if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) { 565 warn("can't lock `%s'", _PATH_GROUP); 566 } 567 (void) fstat(fileno(from), &st); 568 (void) snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP); 569 if ((fd = mkstemp(f)) < 0) { 570 (void) fclose(from); 571 warn("can't create gid: mkstemp failed"); 572 return 0; 573 } 574 if ((to = fdopen(fd, "w")) == NULL) { 575 (void) fclose(from); 576 (void) close(fd); 577 (void) unlink(f); 578 warn("can't create gid: fdopen `%s' failed", f); 579 return 0; 580 } 581 while (fgets(buf, sizeof(buf), from) != NULL) { 582 cc = strlen(buf); 583 if ((colon = strchr(buf, ':')) == NULL) { 584 warn("badly formed entry `%s'", buf); 585 continue; 586 } 587 entc = (int)(colon - buf); 588 for (i = 0 ; i < ngroups ; i++) { 589 if ((groupc = strlen(groups[i])) == 0) { 590 continue; 591 } 592 if (entc == groupc && strncmp(groups[i], buf, (unsigned) entc) == 0) { 593 if ((nc = snprintf(&buf[cc - 1], 594 sizeof(buf) - cc + 1, 595 "%s%s\n", 596 (buf[cc - 2] == ':') ? "" : ",", 597 user)) < 0) { 598 warnx("Warning: group `%s' entry too long", groups[i]); 599 } 600 cc += nc - 1; 601 } 602 } 603 if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) { 604 (void) fclose(from); 605 (void) close(fd); 606 (void) unlink(f); 607 warn("can't create gid: short write to `%s'", f); 608 return 0; 609 } 610 } 611 (void) fclose(from); 612 (void) fclose(to); 613 if (rename(f, _PATH_GROUP) < 0) { 614 warn("can't create gid: can't rename `%s' to `%s'", f, _PATH_GROUP); 615 return 0; 616 } 617 (void) chmod(_PATH_GROUP, st.st_mode & 07777); 618 return 1; 619 } 620 621 /* return 1 if `login' is a valid login name */ 622 static int 623 valid_login(char *login) 624 { 625 char *cp; 626 627 for (cp = login ; *cp ; cp++) { 628 if (!isalnum(*cp) && *cp != '.' && *cp != '_' && *cp != '-') { 629 return 0; 630 } 631 } 632 return 1; 633 } 634 635 /* return 1 if `group' is a valid group name */ 636 static int 637 valid_group(char *group) 638 { 639 char *cp; 640 641 for (cp = group ; *cp ; cp++) { 642 if (!isalnum(*cp)) { 643 return 0; 644 } 645 } 646 return 1; 647 } 648 649 /* find the next gid in the range lo .. hi */ 650 static int 651 getnextgid(int *gidp, int lo, int hi) 652 { 653 for (*gidp = lo ; *gidp < hi ; *gidp += 1) { 654 if (getgrgid((gid_t)*gidp) == NULL) { 655 return 1; 656 } 657 } 658 return 0; 659 } 660 661 #ifdef EXTENSIONS 662 /* save a range of uids */ 663 static int 664 save_range(user_t *up, char *cp) 665 { 666 int from; 667 int to; 668 int i; 669 670 if (up->u_rsize == 0) { 671 up->u_rsize = 32; 672 NEWARRAY(range_t, up->u_rv, up->u_rsize, return(0)); 673 } else if (up->u_rc == up->u_rsize) { 674 up->u_rsize *= 2; 675 RENEW(range_t, up->u_rv, up->u_rsize, return(0)); 676 } 677 if (up->u_rv && sscanf(cp, "%d..%d", &from, &to) == 2) { 678 for (i = 0 ; i < up->u_rc ; i++) { 679 if (up->u_rv[i].r_from == from && up->u_rv[i].r_to == to) { 680 break; 681 } 682 } 683 if (i == up->u_rc) { 684 up->u_rv[up->u_rc].r_from = from; 685 up->u_rv[up->u_rc].r_to = to; 686 up->u_rc += 1; 687 } 688 } else { 689 warnx("Bad range `%s'", cp); 690 return 0; 691 } 692 return 1; 693 } 694 #endif 695 696 /* set the defaults in the defaults file */ 697 static int 698 setdefaults(user_t *up) 699 { 700 char template[MaxFileNameLen]; 701 FILE *fp; 702 int ret; 703 int fd; 704 #ifdef EXTENSIONS 705 int i; 706 #endif 707 708 (void) snprintf(template, sizeof(template), "%s.XXXXXX", CONFFILE); 709 if ((fd = mkstemp(template)) < 0) { 710 warnx("can't mkstemp `%s' for writing", CONFFILE); 711 return 0; 712 } 713 if ((fp = fdopen(fd, "w")) == NULL) { 714 warn("can't fdopen `%s' for writing", CONFFILE); 715 return 0; 716 } 717 ret = 1; 718 if (fprintf(fp, "group\t\t%s\n", up->u_primgrp) <= 0 || 719 fprintf(fp, "base_dir\t%s\n", up->u_basedir) <= 0 || 720 fprintf(fp, "skel_dir\t%s\n", up->u_skeldir) <= 0 || 721 fprintf(fp, "shell\t\t%s\n", up->u_shell) <= 0 || 722 #ifdef EXTENSIONS 723 fprintf(fp, "class\t\t%s\n", up->u_class) <= 0 || 724 #endif 725 fprintf(fp, "inactive\t%d\n", up->u_inactive) <= 0 || 726 fprintf(fp, "expire\t\t%s\n", (up->u_expire == NULL) ? UNSET_EXPIRY : up->u_expire) <= 0 || 727 fprintf(fp, "preserve\t%s\n", (up->u_preserve == 0) ? "false" : "true") <= 0) { 728 warn("can't write to `%s'", CONFFILE); 729 ret = 0; 730 } 731 #ifdef EXTENSIONS 732 for (i = (up->u_defrc != up->u_rc) ? up->u_defrc : 0 ; i < up->u_rc ; i++) { 733 if (fprintf(fp, "range\t\t%d..%d\n", up->u_rv[i].r_from, up->u_rv[i].r_to) <= 0) { 734 warn("can't write to `%s'", CONFFILE); 735 ret = 0; 736 } 737 } 738 #endif 739 (void) fclose(fp); 740 if (ret) { 741 ret = ((rename(template, CONFFILE) == 0) && (chmod(CONFFILE, 0644) == 0)); 742 } 743 return ret; 744 } 745 746 /* read the defaults file */ 747 static void 748 read_defaults(user_t *up) 749 { 750 struct stat st; 751 size_t lineno; 752 size_t len; 753 FILE *fp; 754 char *cp; 755 char *s; 756 757 memsave(&up->u_primgrp, DEF_GROUP, strlen(DEF_GROUP)); 758 memsave(&up->u_basedir, DEF_BASEDIR, strlen(DEF_BASEDIR)); 759 memsave(&up->u_skeldir, DEF_SKELDIR, strlen(DEF_SKELDIR)); 760 memsave(&up->u_shell, DEF_SHELL, strlen(DEF_SHELL)); 761 memsave(&up->u_comment, DEF_COMMENT, strlen(DEF_COMMENT)); 762 #ifdef EXTENSIONS 763 memsave(&up->u_class, DEF_CLASS, strlen(DEF_CLASS)); 764 #endif 765 up->u_rsize = 16; 766 NEWARRAY(range_t, up->u_rv, up->u_rsize, exit(1)); 767 up->u_inactive = DEF_INACTIVE; 768 up->u_expire = DEF_EXPIRE; 769 if ((fp = fopen(CONFFILE, "r")) == NULL) { 770 if (stat(CONFFILE, &st) < 0 && !setdefaults(up)) { 771 warn("can't create `%s' defaults file", CONFFILE); 772 } 773 fp = fopen(CONFFILE, "r"); 774 } 775 if (fp != NULL) { 776 while ((s = fparseln(fp, &len, &lineno, NULL, 0)) != NULL) { 777 if (strncmp(s, "group", 5) == 0) { 778 for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) { 779 } 780 memsave(&up->u_primgrp, cp, strlen(cp)); 781 } else if (strncmp(s, "base_dir", 8) == 0) { 782 for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) { 783 } 784 memsave(&up->u_basedir, cp, strlen(cp)); 785 } else if (strncmp(s, "skel_dir", 8) == 0) { 786 for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) { 787 } 788 memsave(&up->u_skeldir, cp, strlen(cp)); 789 } else if (strncmp(s, "shell", 5) == 0) { 790 for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) { 791 } 792 memsave(&up->u_shell, cp, strlen(cp)); 793 #ifdef EXTENSIONS 794 } else if (strncmp(s, "class", 5) == 0) { 795 for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) { 796 } 797 memsave(&up->u_class, cp, strlen(cp)); 798 #endif 799 } else if (strncmp(s, "inactive", 8) == 0) { 800 for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) { 801 } 802 up->u_inactive = atoi(cp); 803 #ifdef EXTENSIONS 804 } else if (strncmp(s, "range", 5) == 0) { 805 for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) { 806 } 807 (void) save_range(up, cp); 808 #endif 809 #ifdef EXTENSIONS 810 } else if (strncmp(s, "preserve", 8) == 0) { 811 for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) { 812 } 813 up->u_preserve = (strncmp(cp, "true", 4) == 0) ? 1 : 814 (strncmp(cp, "yes", 3) == 0) ? 1 : 815 atoi(cp); 816 #endif 817 } else if (strncmp(s, "expire", 6) == 0) { 818 for (cp = s + 6 ; *cp && isspace(*cp) ; cp++) { 819 } 820 if (strcmp(cp, UNSET_EXPIRY) == 0) { 821 if (up->u_expire) { 822 FREE(up->u_expire); 823 } 824 up->u_expire = NULL; 825 } else { 826 memsave(&up->u_expire, cp, strlen(cp)); 827 } 828 } 829 (void) free(s); 830 } 831 (void) fclose(fp); 832 } 833 if (up->u_rc == 0) { 834 up->u_rv[up->u_rc].r_from = DEF_LOWUID; 835 up->u_rv[up->u_rc].r_to = DEF_HIGHUID; 836 up->u_rc += 1; 837 } 838 up->u_defrc = up->u_rc; 839 } 840 841 /* return the next valid unused uid */ 842 static int 843 getnextuid(int sync_uid_gid, int *uid, int low_uid, int high_uid) 844 { 845 for (*uid = low_uid ; *uid <= high_uid ; (*uid)++) { 846 if (getpwuid((uid_t)(*uid)) == NULL && *uid != NOBODY_UID) { 847 if (sync_uid_gid) { 848 if (getgrgid((gid_t)(*uid)) == NULL) { 849 return 1; 850 } 851 } else { 852 return 1; 853 } 854 } 855 } 856 return 0; 857 } 858 859 /* add a user */ 860 static int 861 adduser(char *login, user_t *up) 862 { 863 struct group *grp; 864 struct stat st; 865 struct tm tm; 866 time_t expire; 867 char password[PasswordLength + 1]; 868 char home[MaxFileNameLen]; 869 char buf[MaxFileNameLen]; 870 int sync_uid_gid; 871 int masterfd; 872 int ptmpfd; 873 int gid; 874 int cc; 875 int i; 876 877 if (!valid_login(login)) { 878 errx(EXIT_FAILURE, "`%s' is not a valid login name", login); 879 } 880 if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) { 881 err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD); 882 } 883 if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) { 884 err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD); 885 } 886 pw_init(); 887 if ((ptmpfd = pw_lock(WAITSECS)) < 0) { 888 (void) close(masterfd); 889 err(EXIT_FAILURE, "can't obtain pw_lock"); 890 } 891 while ((cc = read(masterfd, buf, sizeof(buf))) > 0) { 892 if (write(ptmpfd, buf, (size_t)(cc)) != cc) { 893 (void) close(masterfd); 894 (void) close(ptmpfd); 895 (void) pw_abort(); 896 err(EXIT_FAILURE, "short write to /etc/ptmp (not %d chars)", cc); 897 } 898 } 899 /* if no uid was specified, get next one in [low_uid..high_uid] range */ 900 sync_uid_gid = (strcmp(up->u_primgrp, "=uid") == 0); 901 if (up->u_uid == -1) { 902 /* default is in index '0' in array */ 903 for (i = up->u_rc - 1 ; i >= 0 ; --i) { 904 if (getnextuid(sync_uid_gid, &up->u_uid, up->u_rv[i].r_from, up->u_rv[i].r_to)) { 905 break; 906 } 907 } 908 if (i == up->u_rc) { 909 (void) close(ptmpfd); 910 (void) pw_abort(); 911 errx(EXIT_FAILURE, "can't get next uid for %d", up->u_uid); 912 } 913 } 914 /* check uid isn't already allocated */ 915 if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) { 916 (void) close(ptmpfd); 917 (void) pw_abort(); 918 errx(EXIT_FAILURE, "uid %d is already in use", up->u_uid); 919 } 920 /* if -g=uid was specified, check gid is unused */ 921 if (sync_uid_gid) { 922 if (getgrgid((gid_t)(up->u_uid)) != NULL) { 923 (void) close(ptmpfd); 924 (void) pw_abort(); 925 errx(EXIT_FAILURE, "gid %d is already in use", up->u_uid); 926 } 927 gid = up->u_uid; 928 } else if ((grp = getgrnam(up->u_primgrp)) != NULL) { 929 gid = grp->gr_gid; 930 } else if (is_number(up->u_primgrp) && 931 (grp = getgrgid((gid_t)atoi(up->u_primgrp))) != NULL) { 932 gid = grp->gr_gid; 933 } else { 934 (void) close(ptmpfd); 935 (void) pw_abort(); 936 errx(EXIT_FAILURE, "group %s not found", up->u_primgrp); 937 } 938 /* check name isn't already in use */ 939 if (!(up->u_flags & F_DUPUID) && getpwnam(login) != NULL) { 940 (void) close(ptmpfd); 941 (void) pw_abort(); 942 errx(EXIT_FAILURE, "already a `%s' user", login); 943 } 944 if (up->u_flags & F_HOMEDIR) { 945 (void) strlcpy(home, up->u_home, sizeof(home)); 946 } else { 947 /* if home directory hasn't been given, make it up */ 948 (void) snprintf(home, sizeof(home), "%s/%s", up->u_basedir, login); 949 } 950 expire = 0; 951 if (up->u_expire != NULL) { 952 (void) memset(&tm, 0, sizeof(tm)); 953 if (strptime(up->u_expire, "%c", &tm) == NULL) { 954 warnx("invalid time format `%s'", optarg); 955 } else { 956 expire = mktime(&tm); 957 } 958 } 959 if (lstat(home, &st) < 0 && !(up->u_flags & F_MKDIR)) { 960 warnx("Warning: home directory `%s' doesn't exist, and -m was not specified", 961 home); 962 } 963 password[PasswordLength] = '\0'; 964 if (up->u_password != NULL && 965 strlen(up->u_password) == PasswordLength) { 966 (void) memcpy(password, up->u_password, PasswordLength); 967 } else { 968 (void) memset(password, '*', PasswordLength); 969 if (up->u_password != NULL) { 970 warnx("Password `%s' is invalid: setting it to `%s'", 971 up->u_password, password); 972 } 973 } 974 cc = snprintf(buf, sizeof(buf), "%s:%s:%d:%d:%s:%d:%ld:%s:%s:%s\n", 975 login, 976 password, 977 up->u_uid, 978 gid, 979 #ifdef EXTENSIONS 980 up->u_class, 981 #else 982 "", 983 #endif 984 up->u_inactive, 985 (long) expire, 986 up->u_comment, 987 home, 988 up->u_shell); 989 if (write(ptmpfd, buf, (size_t) cc) != cc) { 990 (void) close(ptmpfd); 991 (void) pw_abort(); 992 err(EXIT_FAILURE, "can't add `%s'", buf); 993 } 994 if (up->u_flags & F_MKDIR) { 995 if (lstat(home, &st) == 0) { 996 (void) close(ptmpfd); 997 (void) pw_abort(); 998 errx(EXIT_FAILURE, "home directory `%s' already exists", home); 999 } else { 1000 if (asystem("%s -p %s", MKDIR, home) != 0) { 1001 (void) close(ptmpfd); 1002 (void) pw_abort(); 1003 err(EXIT_FAILURE, "can't mkdir `%s'", home); 1004 } 1005 (void) copydotfiles(up->u_skeldir, up->u_uid, gid, home); 1006 } 1007 } 1008 if (strcmp(up->u_primgrp, "=uid") == 0 && 1009 getgrnam(login) == NULL && 1010 !creategid(login, gid, login)) { 1011 (void) close(ptmpfd); 1012 (void) pw_abort(); 1013 errx(EXIT_FAILURE, "can't create gid %d for login name %s", gid, login); 1014 } 1015 if (up->u_groupc > 0 && !append_group(login, up->u_groupc, up->u_groupv)) { 1016 (void) close(ptmpfd); 1017 (void) pw_abort(); 1018 errx(EXIT_FAILURE, "can't append `%s' to new groups", login); 1019 } 1020 (void) close(ptmpfd); 1021 #if PW_MKDB_ARGC == 2 1022 if (pw_mkdb(login, 0) < 0) { 1023 err(EXIT_FAILURE, "pw_mkdb failed"); 1024 } 1025 #else 1026 if (pw_mkdb() < 0) { 1027 err(EXIT_FAILURE, "pw_mkdb failed"); 1028 } 1029 #endif 1030 return 1; 1031 } 1032 1033 /* modify a user */ 1034 static int 1035 moduser(char *login, char *newlogin, user_t *up) 1036 { 1037 struct passwd *pwp; 1038 struct group *grp; 1039 struct tm tm; 1040 const char *homedir; 1041 size_t colonc, len, loginc; 1042 size_t cc; 1043 FILE *master; 1044 char newdir[MaxFileNameLen]; 1045 char *buf, *colon, *line; 1046 int masterfd; 1047 int ptmpfd; 1048 int error; 1049 1050 if (!valid_login(newlogin)) { 1051 errx(EXIT_FAILURE, "`%s' is not a valid login name", login); 1052 } 1053 if ((pwp = getpwnam(login)) == NULL) { 1054 errx(EXIT_FAILURE, "No such user `%s'", login); 1055 } 1056 /* keep dir name in case we need it for '-m' */ 1057 homedir = pwp->pw_dir; 1058 1059 if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) { 1060 err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD); 1061 } 1062 if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) { 1063 err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD); 1064 } 1065 pw_init(); 1066 if ((ptmpfd = pw_lock(WAITSECS)) < 0) { 1067 (void) close(masterfd); 1068 err(EXIT_FAILURE, "can't obtain pw_lock"); 1069 } 1070 if ((master = fdopen(masterfd, "r")) == NULL) { 1071 (void) close(masterfd); 1072 (void) close(ptmpfd); 1073 (void) pw_abort(); 1074 err(EXIT_FAILURE, "can't fdopen fd for %s", _PATH_MASTERPASSWD); 1075 } 1076 if (up != NULL) { 1077 if (up->u_flags & F_USERNAME) { 1078 /* if changing name, check new name isn't already in use */ 1079 if (strcmp(login, newlogin) != 0 && getpwnam(newlogin) != NULL) { 1080 (void) close(ptmpfd); 1081 (void) pw_abort(); 1082 errx(EXIT_FAILURE, "already a `%s' user", newlogin); 1083 } 1084 pwp->pw_name = newlogin; 1085 1086 /* 1087 * Provide a new directory name in case the 1088 * home directory is to be moved. 1089 */ 1090 if (up->u_flags & F_MKDIR) { 1091 snprintf(newdir, sizeof(newdir), "%s/%s", up->u_basedir, newlogin); 1092 pwp->pw_dir = newdir; 1093 } 1094 } 1095 if (up->u_flags & F_PASSWORD) { 1096 if (up->u_password != NULL && strlen(up->u_password) == PasswordLength) 1097 pwp->pw_passwd = up->u_password; 1098 } 1099 if (up->u_flags & F_UID) { 1100 /* check uid isn't already allocated */ 1101 if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) { 1102 (void) close(ptmpfd); 1103 (void) pw_abort(); 1104 errx(EXIT_FAILURE, "uid %d is already in use", up->u_uid); 1105 } 1106 pwp->pw_uid = up->u_uid; 1107 } 1108 if (up->u_flags & F_GROUP) { 1109 /* if -g=uid was specified, check gid is unused */ 1110 if (strcmp(up->u_primgrp, "=uid") == 0) { 1111 if (getgrgid((gid_t)(up->u_uid)) != NULL) { 1112 (void) close(ptmpfd); 1113 (void) pw_abort(); 1114 errx(EXIT_FAILURE, "gid %d is already in use", up->u_uid); 1115 } 1116 pwp->pw_gid = up->u_uid; 1117 } else if ((grp = getgrnam(up->u_primgrp)) != NULL) { 1118 pwp->pw_gid = grp->gr_gid; 1119 } else if (is_number(up->u_primgrp) && 1120 (grp = getgrgid((gid_t)atoi(up->u_primgrp))) != NULL) { 1121 pwp->pw_gid = grp->gr_gid; 1122 } else { 1123 (void) close(ptmpfd); 1124 (void) pw_abort(); 1125 errx(EXIT_FAILURE, "group %s not found", up->u_primgrp); 1126 } 1127 } 1128 if (up->u_flags |= F_INACTIVE) 1129 pwp->pw_change = up->u_inactive; 1130 if (up->u_flags & F_EXPIRE) { 1131 (void) memset(&tm, 0, sizeof(tm)); 1132 if (strptime(up->u_expire, "%c", &tm) == NULL) 1133 warnx("invalid time format `%s'", optarg); 1134 else 1135 pwp->pw_expire = mktime(&tm); 1136 } 1137 if (up->u_flags & F_COMMENT) 1138 pwp->pw_gecos = up->u_comment; 1139 if (up->u_flags & F_HOMEDIR) 1140 pwp->pw_dir = up->u_home; 1141 if (up->u_flags & F_SHELL) 1142 pwp->pw_shell = up->u_shell; 1143 #ifdef EXTENSIONS 1144 if (up->u_flags & F_CLASS) 1145 pwp->pw_class = up->u_class; 1146 #endif 1147 } 1148 loginc = strlen(login); 1149 while ((line = fgetln(master, &len)) != NULL) { 1150 if ((colon = strchr(line, ':')) == NULL) { 1151 warnx("Malformed entry `%s'. Skipping", line); 1152 continue; 1153 } 1154 colonc = (size_t)(colon - line); 1155 if (strncmp(login, line, loginc) == 0 && loginc == colonc) { 1156 if (up != NULL) { 1157 len = (int)asprintf(&buf, "%s:%s:%d:%d:" 1158 #ifdef EXTENSIONS 1159 "%s" 1160 #endif 1161 ":%ld:%ld:%s:%s:%s\n", 1162 newlogin, 1163 pwp->pw_passwd, 1164 pwp->pw_uid, 1165 pwp->pw_gid, 1166 #ifdef EXTENSIONS 1167 pwp->pw_class, 1168 #endif 1169 (long)pwp->pw_change, 1170 (long)pwp->pw_expire, 1171 pwp->pw_gecos, 1172 pwp->pw_dir, 1173 pwp->pw_shell); 1174 if (write(ptmpfd, buf, len) != len) { 1175 (void) close(ptmpfd); 1176 (void) pw_abort(); 1177 err(EXIT_FAILURE, "can't add `%s'", buf); 1178 } 1179 (void) free(buf); 1180 } 1181 } else if ((cc = write(ptmpfd, line, len)) != len) { 1182 (void) close(masterfd); 1183 (void) close(ptmpfd); 1184 (void) pw_abort(); 1185 err(EXIT_FAILURE, "short write to /etc/ptmp (%lld not %lld chars)", 1186 (long long)cc, 1187 (long long)len); 1188 } 1189 } 1190 if (up != NULL) { 1191 if ((up->u_flags & F_MKDIR) && 1192 asystem("%s %s %s", MV, homedir, pwp->pw_dir) != 0) { 1193 (void) close(ptmpfd); 1194 (void) pw_abort(); 1195 err(EXIT_FAILURE, "can't move `%s' to `%s'", 1196 homedir, pwp->pw_dir); 1197 } 1198 if (up->u_groupc > 0 && 1199 !append_group(newlogin, up->u_groupc, up->u_groupv)) { 1200 (void) close(ptmpfd); 1201 (void) pw_abort(); 1202 errx(EXIT_FAILURE, "can't append `%s' to new groups", 1203 newlogin); 1204 } 1205 } 1206 (void) close(ptmpfd); 1207 #if PW_MKDB_ARGC == 2 1208 if (up != NULL && strcmp(login, newlogin) == 0) { 1209 error = pw_mkdb(login, 0); 1210 } else { 1211 error = pw_mkdb(NULL, 0); 1212 } 1213 #else 1214 error = pw_mkdb(); 1215 #endif 1216 if (error < 0) { 1217 err(EXIT_FAILURE, "pw_mkdb failed"); 1218 } 1219 1220 return 1; 1221 } 1222 1223 1224 #ifdef EXTENSIONS 1225 /* see if we can find out the user struct */ 1226 static struct passwd * 1227 find_user_info(char *name) 1228 { 1229 struct passwd *pwp; 1230 1231 if ((pwp = getpwnam(name)) != NULL) { 1232 return pwp; 1233 } 1234 if (is_number(name) && (pwp = getpwuid((uid_t)atoi(name))) != NULL) { 1235 return pwp; 1236 } 1237 return NULL; 1238 } 1239 #endif 1240 1241 #ifdef EXTENSIONS 1242 /* see if we can find out the group struct */ 1243 static struct group * 1244 find_group_info(char *name) 1245 { 1246 struct group *grp; 1247 1248 if ((grp = getgrnam(name)) != NULL) { 1249 return grp; 1250 } 1251 if (is_number(name) && (grp = getgrgid((gid_t)atoi(name))) != NULL) { 1252 return grp; 1253 } 1254 return NULL; 1255 } 1256 #endif 1257 1258 /* print out usage message, and then exit */ 1259 void 1260 usermgmt_usage(const char *prog) 1261 { 1262 if (strcmp(prog, "useradd") == 0) { 1263 (void) fprintf(stderr, "Usage: %s -D [-b basedir] [-e expiry] " 1264 "[-f inactive] [-g group]\n\t[-r lowuid..highuid] " 1265 "[-s shell] [-L class]\n", prog); 1266 (void) fprintf(stderr, "Usage: %s [-G group] [-b basedir] " 1267 "[-c comment] [-d homedir] [-e expiry]\n\t[-f inactive] " 1268 "[-g group] [-k skeletondir] [-m] [-o] [-p password]\n" 1269 "\t[-r lowuid..highuid] [-s shell]\n\t[-u uid] [-v] user\n", 1270 prog); 1271 } else if (strcmp(prog, "usermod") == 0) { 1272 (void) fprintf(stderr, "Usage: %s [-G group] [-c comment] " 1273 "[-d homedir] [-e expire] [-f inactive]\n\t[-g group] " 1274 "[-l newname] [-m] [-o] [-p password] [-s shell] [-u uid]\n" 1275 "\t[-L class] [-v] user\n", prog); 1276 } else if (strcmp(prog, "userdel") == 0) { 1277 (void) fprintf(stderr, "Usage: %s -D [-p preserve]\n", prog); 1278 (void) fprintf(stderr, 1279 "Usage: %s [-p preserve] [-r] [-v] user\n", prog); 1280 #ifdef EXTENSIONS 1281 } else if (strcmp(prog, "userinfo") == 0) { 1282 (void) fprintf(stderr, "Usage: %s [-e] [-v] user\n", prog); 1283 #endif 1284 } else if (strcmp(prog, "groupadd") == 0) { 1285 (void) fprintf(stderr, "Usage: %s [-g gid] [-o] [-v] group\n", 1286 prog); 1287 } else if (strcmp(prog, "groupdel") == 0) { 1288 (void) fprintf(stderr, "Usage: %s [-v] group\n", prog); 1289 } else if (strcmp(prog, "groupmod") == 0) { 1290 (void) fprintf(stderr, 1291 "Usage: %s [-g gid] [-o] [-n newname] [-v] group\n", prog); 1292 } else if (strcmp(prog, "user") == 0 || strcmp(prog, "group") == 0) { 1293 (void) fprintf(stderr, 1294 "Usage: %s ( add | del | mod | info ) ...\n", prog); 1295 #ifdef EXTENSIONS 1296 } else if (strcmp(prog, "groupinfo") == 0) { 1297 (void) fprintf(stderr, "Usage: %s [-e] [-v] group\n", prog); 1298 #endif 1299 } 1300 exit(EXIT_FAILURE); 1301 /* NOTREACHED */ 1302 } 1303 1304 #ifdef EXTENSIONS 1305 #define ADD_OPT_EXTENSIONS "p:r:vL:" 1306 #else 1307 #define ADD_OPT_EXTENSIONS 1308 #endif 1309 1310 int 1311 useradd(int argc, char **argv) 1312 { 1313 user_t u; 1314 int defaultfield; 1315 int bigD; 1316 int c; 1317 #ifdef EXTENSIONS 1318 int i; 1319 #endif 1320 1321 (void) memset(&u, 0, sizeof(u)); 1322 read_defaults(&u); 1323 u.u_uid = -1; 1324 defaultfield = bigD = 0; 1325 while ((c = getopt(argc, argv, "DG:b:c:d:e:f:g:k:mou:s:" ADD_OPT_EXTENSIONS)) != -1) { 1326 switch(c) { 1327 case 'D': 1328 bigD = 1; 1329 break; 1330 case 'G': 1331 while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL && 1332 u.u_groupc < NGROUPS_MAX) { 1333 if (u.u_groupv[u.u_groupc][0] != 0) { 1334 u.u_groupc++; 1335 } 1336 } 1337 if (optarg != NULL) { 1338 warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX); 1339 } 1340 break; 1341 case 'b': 1342 defaultfield = 1; 1343 memsave(&u.u_basedir, optarg, strlen(optarg)); 1344 break; 1345 case 'c': 1346 memsave(&u.u_comment, optarg, strlen(optarg)); 1347 break; 1348 case 'd': 1349 memsave(&u.u_home, optarg, strlen(optarg)); 1350 u.u_flags |= F_HOMEDIR; 1351 break; 1352 case 'e': 1353 defaultfield = 1; 1354 memsave(&u.u_expire, optarg, strlen(optarg)); 1355 break; 1356 case 'f': 1357 defaultfield = 1; 1358 u.u_inactive = atoi(optarg); 1359 break; 1360 case 'g': 1361 defaultfield = 1; 1362 memsave(&u.u_primgrp, optarg, strlen(optarg)); 1363 break; 1364 case 'k': 1365 memsave(&u.u_skeldir, optarg, strlen(optarg)); 1366 break; 1367 #ifdef EXTENSIONS 1368 case 'L': 1369 defaultfield = 1; 1370 memsave(&u.u_class, optarg, strlen(optarg)); 1371 break; 1372 #endif 1373 case 'm': 1374 u.u_flags |= F_MKDIR; 1375 break; 1376 case 'o': 1377 u.u_flags |= F_DUPUID; 1378 break; 1379 #ifdef EXTENSIONS 1380 case 'p': 1381 memsave(&u.u_password, optarg, strlen(optarg)); 1382 break; 1383 #endif 1384 #ifdef EXTENSIONS 1385 case 'r': 1386 defaultfield = 1; 1387 (void) save_range(&u, optarg); 1388 break; 1389 #endif 1390 case 's': 1391 defaultfield = 1; 1392 memsave(&u.u_shell, optarg, strlen(optarg)); 1393 break; 1394 case 'u': 1395 if (!is_number(optarg)) { 1396 errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric"); 1397 } 1398 u.u_uid = atoi(optarg); 1399 break; 1400 #ifdef EXTENSIONS 1401 case 'v': 1402 verbose = 1; 1403 break; 1404 #endif 1405 } 1406 } 1407 if (bigD) { 1408 if (defaultfield) { 1409 checkeuid(); 1410 return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE; 1411 } 1412 (void) printf("group\t\t%s\n", u.u_primgrp); 1413 (void) printf("base_dir\t%s\n", u.u_basedir); 1414 (void) printf("skel_dir\t%s\n", u.u_skeldir); 1415 (void) printf("shell\t\t%s\n", u.u_shell); 1416 #ifdef EXTENSIONS 1417 (void) printf("class\t\t%s\n", u.u_class); 1418 #endif 1419 (void) printf("inactive\t%d\n", u.u_inactive); 1420 (void) printf("expire\t\t%s\n", (u.u_expire == NULL) ? UNSET_EXPIRY : u.u_expire); 1421 #ifdef EXTENSIONS 1422 for (i = 0 ; i < u.u_rc ; i++) { 1423 (void) printf("range\t\t%d..%d\n", u.u_rv[i].r_from, u.u_rv[i].r_to); 1424 } 1425 #endif 1426 return EXIT_SUCCESS; 1427 } 1428 argc -= optind; 1429 argv += optind; 1430 if (argc != 1) { 1431 usermgmt_usage("useradd"); 1432 } 1433 checkeuid(); 1434 return adduser(*argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE; 1435 } 1436 1437 #ifdef EXTENSIONS 1438 #define MOD_OPT_EXTENSIONS "p:vL:" 1439 #else 1440 #define MOD_OPT_EXTENSIONS 1441 #endif 1442 1443 int 1444 usermod(int argc, char **argv) 1445 { 1446 user_t u; 1447 char newuser[MaxUserNameLen + 1]; 1448 int c, have_new_user; 1449 1450 (void) memset(&u, 0, sizeof(u)); 1451 (void) memset(newuser, 0, sizeof(newuser)); 1452 read_defaults(&u); 1453 have_new_user = 0; 1454 while ((c = getopt(argc, argv, "G:c:d:e:f:g:l:mos:u:" MOD_OPT_EXTENSIONS)) != -1) { 1455 switch(c) { 1456 case 'G': 1457 while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL && 1458 u.u_groupc < NGROUPS_MAX) { 1459 if (u.u_groupv[u.u_groupc][0] != 0) { 1460 u.u_groupc++; 1461 } 1462 } 1463 if (optarg != NULL) { 1464 warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX); 1465 } 1466 u.u_flags |= F_SECGROUP; 1467 break; 1468 case 'c': 1469 memsave(&u.u_comment, optarg, strlen(optarg)); 1470 u.u_flags |= F_COMMENT; 1471 break; 1472 case 'd': 1473 memsave(&u.u_home, optarg, strlen(optarg)); 1474 u.u_flags |= F_HOMEDIR; 1475 break; 1476 case 'e': 1477 memsave(&u.u_expire, optarg, strlen(optarg)); 1478 u.u_flags |= F_EXPIRE; 1479 break; 1480 case 'f': 1481 u.u_inactive = atoi(optarg); 1482 u.u_flags |= F_INACTIVE; 1483 break; 1484 case 'g': 1485 memsave(&u.u_primgrp, optarg, strlen(optarg)); 1486 u.u_flags |= F_GROUP; 1487 break; 1488 case 'l': 1489 (void) strlcpy(newuser, optarg, sizeof(newuser)); 1490 have_new_user = 1; 1491 u.u_flags |= F_USERNAME; 1492 break; 1493 #ifdef EXTENSIONS 1494 case 'L': 1495 memsave(&u.u_class, optarg, strlen(optarg)); 1496 u.u_flags |= F_CLASS; 1497 break; 1498 #endif 1499 case 'm': 1500 u.u_flags |= F_MKDIR; 1501 break; 1502 case 'o': 1503 u.u_flags |= F_DUPUID; 1504 break; 1505 #ifdef EXTENSIONS 1506 case 'p': 1507 memsave(&u.u_password, optarg, strlen(optarg)); 1508 u.u_flags |= F_PASSWORD; 1509 break; 1510 #endif 1511 case 's': 1512 memsave(&u.u_shell, optarg, strlen(optarg)); 1513 u.u_flags |= F_SHELL; 1514 break; 1515 case 'u': 1516 if (!is_number(optarg)) { 1517 errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric"); 1518 } 1519 u.u_uid = atoi(optarg); 1520 u.u_flags |= F_UID; 1521 break; 1522 #ifdef EXTENSIONS 1523 case 'v': 1524 verbose = 1; 1525 break; 1526 #endif 1527 } 1528 } 1529 if ((u.u_flags & F_MKDIR) && !(u.u_flags & F_HOMEDIR) && 1530 !(u.u_flags & F_USERNAME)) { 1531 warnx("option 'm' useless without 'd' or 'l' -- ignored"); 1532 u.u_flags &= ~F_MKDIR; 1533 } 1534 argc -= optind; 1535 argv += optind; 1536 if (argc != 1) { 1537 usermgmt_usage("usermod"); 1538 } 1539 checkeuid(); 1540 return moduser(*argv, (have_new_user) ? newuser : *argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE; 1541 } 1542 1543 #ifdef EXTENSIONS 1544 #define DEL_OPT_EXTENSIONS "Dp:v" 1545 #else 1546 #define DEL_OPT_EXTENSIONS 1547 #endif 1548 1549 int 1550 userdel(int argc, char **argv) 1551 { 1552 struct passwd *pwp; 1553 user_t u; 1554 char password[PasswordLength + 1]; 1555 int defaultfield; 1556 int rmhome; 1557 int bigD; 1558 int c; 1559 1560 (void) memset(&u, 0, sizeof(u)); 1561 read_defaults(&u); 1562 defaultfield = bigD = rmhome = 0; 1563 while ((c = getopt(argc, argv, "r" DEL_OPT_EXTENSIONS)) != -1) { 1564 switch(c) { 1565 #ifdef EXTENSIONS 1566 case 'D': 1567 bigD = 1; 1568 break; 1569 #endif 1570 #ifdef EXTENSIONS 1571 case 'p': 1572 defaultfield = 1; 1573 u.u_preserve = (strcmp(optarg, "true") == 0) ? 1 : 1574 (strcmp(optarg, "yes") == 0) ? 1 : 1575 atoi(optarg); 1576 break; 1577 #endif 1578 case 'r': 1579 rmhome = 1; 1580 break; 1581 #ifdef EXTENSIONS 1582 case 'v': 1583 verbose = 1; 1584 break; 1585 #endif 1586 } 1587 } 1588 #ifdef EXTENSIONS 1589 if (bigD) { 1590 if (defaultfield) { 1591 checkeuid(); 1592 return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE; 1593 } 1594 (void) printf("preserve\t%s\n", (u.u_preserve) ? "true" : "false"); 1595 return EXIT_SUCCESS; 1596 } 1597 #endif 1598 argc -= optind; 1599 argv += optind; 1600 if (argc != 1) { 1601 usermgmt_usage("userdel"); 1602 } 1603 checkeuid(); 1604 if ((pwp = getpwnam(*argv)) == NULL) { 1605 warnx("No such user `%s'", *argv); 1606 return EXIT_FAILURE; 1607 } 1608 if (rmhome) 1609 (void)removehomedir(pwp->pw_name, pwp->pw_uid, pwp->pw_dir); 1610 if (u.u_preserve) { 1611 u.u_flags |= F_SHELL; 1612 memsave(&u.u_shell, NOLOGIN, strlen(NOLOGIN)); 1613 (void) memset(password, '*', PasswordLength); 1614 password[PasswordLength] = '\0'; 1615 memsave(&u.u_password, password, PasswordLength); 1616 u.u_flags |= F_PASSWORD; 1617 return moduser(*argv, *argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE; 1618 } 1619 return moduser(*argv, *argv, NULL) ? EXIT_SUCCESS : EXIT_FAILURE; 1620 } 1621 1622 #ifdef EXTENSIONS 1623 #define GROUP_ADD_OPT_EXTENSIONS "v" 1624 #else 1625 #define GROUP_ADD_OPT_EXTENSIONS 1626 #endif 1627 1628 /* add a group */ 1629 int 1630 groupadd(int argc, char **argv) 1631 { 1632 int dupgid; 1633 int gid; 1634 int c; 1635 1636 gid = -1; 1637 dupgid = 0; 1638 while ((c = getopt(argc, argv, "g:o" GROUP_ADD_OPT_EXTENSIONS)) != -1) { 1639 switch(c) { 1640 case 'g': 1641 if (!is_number(optarg)) { 1642 errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric"); 1643 } 1644 gid = atoi(optarg); 1645 break; 1646 case 'o': 1647 dupgid = 1; 1648 break; 1649 #ifdef EXTENSIONS 1650 case 'v': 1651 verbose = 1; 1652 break; 1653 #endif 1654 } 1655 } 1656 argc -= optind; 1657 argv += optind; 1658 if (argc != 1) { 1659 usermgmt_usage("groupadd"); 1660 } 1661 checkeuid(); 1662 if (gid < 0 && !getnextgid(&gid, LowGid, HighGid)) { 1663 err(EXIT_FAILURE, "can't add group: can't get next gid"); 1664 } 1665 if (!dupgid && getgrgid((gid_t) gid) != NULL) { 1666 errx(EXIT_FAILURE, "can't add group: gid %d is a duplicate", gid); 1667 } 1668 if (!valid_group(*argv)) { 1669 warnx("warning - invalid group name `%s'", *argv); 1670 } 1671 if (!creategid(*argv, gid, "")) { 1672 errx(EXIT_FAILURE, "can't add group: problems with %s file", _PATH_GROUP); 1673 } 1674 return EXIT_SUCCESS; 1675 } 1676 1677 #ifdef EXTENSIONS 1678 #define GROUP_DEL_OPT_EXTENSIONS "v" 1679 #else 1680 #define GROUP_DEL_OPT_EXTENSIONS 1681 #endif 1682 1683 /* remove a group */ 1684 int 1685 groupdel(int argc, char **argv) 1686 { 1687 int c; 1688 1689 while ((c = getopt(argc, argv, "" GROUP_DEL_OPT_EXTENSIONS)) != -1) { 1690 switch(c) { 1691 #ifdef EXTENSIONS 1692 case 'v': 1693 verbose = 1; 1694 break; 1695 #endif 1696 } 1697 } 1698 argc -= optind; 1699 argv += optind; 1700 if (argc != 1) { 1701 usermgmt_usage("groupdel"); 1702 } 1703 checkeuid(); 1704 if (!modify_gid(*argv, NULL)) { 1705 err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP); 1706 } 1707 return EXIT_SUCCESS; 1708 } 1709 1710 #ifdef EXTENSIONS 1711 #define GROUP_MOD_OPT_EXTENSIONS "v" 1712 #else 1713 #define GROUP_MOD_OPT_EXTENSIONS 1714 #endif 1715 1716 /* modify a group */ 1717 int 1718 groupmod(int argc, char **argv) 1719 { 1720 struct group *grp; 1721 char buf[MaxEntryLen]; 1722 char *newname; 1723 char **cpp; 1724 int dupgid; 1725 int gid; 1726 int cc; 1727 int c; 1728 1729 gid = -1; 1730 dupgid = 0; 1731 newname = NULL; 1732 while ((c = getopt(argc, argv, "g:on:" GROUP_MOD_OPT_EXTENSIONS)) != -1) { 1733 switch(c) { 1734 case 'g': 1735 if (!is_number(optarg)) { 1736 errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric"); 1737 } 1738 gid = atoi(optarg); 1739 break; 1740 case 'o': 1741 dupgid = 1; 1742 break; 1743 case 'n': 1744 memsave(&newname, optarg, strlen(optarg)); 1745 break; 1746 #ifdef EXTENSIONS 1747 case 'v': 1748 verbose = 1; 1749 break; 1750 #endif 1751 } 1752 } 1753 argc -= optind; 1754 argv += optind; 1755 if (argc != 1) { 1756 usermgmt_usage("groupmod"); 1757 } 1758 checkeuid(); 1759 if (gid < 0 && newname == NULL) { 1760 err(EXIT_FAILURE, "Nothing to change"); 1761 } 1762 if (dupgid && gid < 0) { 1763 err(EXIT_FAILURE, "Duplicate which gid?"); 1764 } 1765 if ((grp = getgrnam(*argv)) == NULL) { 1766 err(EXIT_FAILURE, "can't find group `%s' to modify", *argv); 1767 } 1768 if (newname != NULL && !valid_group(newname)) { 1769 warn("warning - invalid group name `%s'", newname); 1770 } 1771 cc = snprintf(buf, sizeof(buf), "%s:%s:%d:", 1772 (newname) ? newname : grp->gr_name, 1773 grp->gr_passwd, 1774 (gid < 0) ? grp->gr_gid : gid); 1775 for (cpp = grp->gr_mem ; *cpp && cc < sizeof(buf) ; cpp++) { 1776 cc += snprintf(&buf[cc], sizeof(buf) - cc, "%s%s", *cpp, 1777 (cpp[1] == NULL) ? "" : ","); 1778 } 1779 cc += snprintf(&buf[cc], sizeof(buf) - cc, "\n"); 1780 if (!modify_gid(*argv, buf)) { 1781 err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP); 1782 } 1783 return EXIT_SUCCESS; 1784 } 1785 1786 #ifdef EXTENSIONS 1787 /* display user information */ 1788 int 1789 userinfo(int argc, char **argv) 1790 { 1791 struct passwd *pwp; 1792 struct group *grp; 1793 char buf[MaxEntryLen]; 1794 char **cpp; 1795 int exists; 1796 int cc; 1797 int i; 1798 1799 exists = 0; 1800 while ((i = getopt(argc, argv, "ev")) != -1) { 1801 switch(i) { 1802 case 'e': 1803 exists = 1; 1804 break; 1805 case 'v': 1806 verbose = 1; 1807 break; 1808 } 1809 } 1810 argc -= optind; 1811 argv += optind; 1812 if (argc != 1) { 1813 usermgmt_usage("userinfo"); 1814 } 1815 pwp = find_user_info(*argv); 1816 if (exists) { 1817 exit((pwp) ? EXIT_SUCCESS : EXIT_FAILURE); 1818 } 1819 if (pwp == NULL) { 1820 errx(EXIT_FAILURE, "can't find user `%s'", *argv); 1821 } 1822 (void) printf("login\t%s\n", pwp->pw_name); 1823 (void) printf("passwd\t%s\n", pwp->pw_passwd); 1824 (void) printf("uid\t%d\n", pwp->pw_uid); 1825 for (cc = 0 ; (grp = getgrent()) != NULL ; ) { 1826 for (cpp = grp->gr_mem ; *cpp ; cpp++) { 1827 if (strcmp(*cpp, *argv) == 0 && grp->gr_gid != pwp->pw_gid) { 1828 cc += snprintf(&buf[cc], sizeof(buf) - cc, "%s ", grp->gr_name); 1829 } 1830 } 1831 } 1832 if ((grp = getgrgid(pwp->pw_gid)) == NULL) { 1833 (void) printf("groups\t%d %s\n", pwp->pw_gid, buf); 1834 } else { 1835 (void) printf("groups\t%s %s\n", grp->gr_name, buf); 1836 } 1837 (void) printf("change\t%s", pwp->pw_change ? ctime(&pwp->pw_change) : "NEVER\n"); 1838 #ifdef EXTENSIONS 1839 (void) printf("class\t%s\n", pwp->pw_class); 1840 #endif 1841 (void) printf("gecos\t%s\n", pwp->pw_gecos); 1842 (void) printf("dir\t%s\n", pwp->pw_dir); 1843 (void) printf("shell\t%s\n", pwp->pw_shell); 1844 (void) printf("expire\t%s", pwp->pw_expire ? ctime(&pwp->pw_expire) : "NEVER\n"); 1845 return EXIT_SUCCESS; 1846 } 1847 #endif 1848 1849 #ifdef EXTENSIONS 1850 /* display user information */ 1851 int 1852 groupinfo(int argc, char **argv) 1853 { 1854 struct group *grp; 1855 char **cpp; 1856 int exists; 1857 int i; 1858 1859 exists = 0; 1860 while ((i = getopt(argc, argv, "ev")) != -1) { 1861 switch(i) { 1862 case 'e': 1863 exists = 1; 1864 break; 1865 case 'v': 1866 verbose = 1; 1867 break; 1868 } 1869 } 1870 argc -= optind; 1871 argv += optind; 1872 if (argc != 1) { 1873 usermgmt_usage("groupinfo"); 1874 } 1875 grp = find_group_info(*argv); 1876 if (exists) { 1877 exit((grp) ? EXIT_SUCCESS : EXIT_FAILURE); 1878 } 1879 if (grp == NULL) { 1880 errx(EXIT_FAILURE, "can't find group `%s'", *argv); 1881 } 1882 (void) printf("name\t%s\n", grp->gr_name); 1883 (void) printf("passwd\t%s\n", grp->gr_passwd); 1884 (void) printf("gid\t%d\n", grp->gr_gid); 1885 (void) printf("members\t"); 1886 for (cpp = grp->gr_mem ; *cpp ; cpp++) { 1887 (void) printf("%s ", *cpp); 1888 } 1889 (void) fputc('\n', stdout); 1890 return EXIT_SUCCESS; 1891 } 1892 #endif 1893