1 /* $NetBSD: user.c,v 1.45 2001/10/22 11:00:05 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.45 2001/10/22 11:00:05 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 for (i = 0 ; i < up->u_rc ; i++) { 903 if (getnextuid(sync_uid_gid, &up->u_uid, up->u_rv[i].r_from, up->u_rv[i].r_to)) { 904 break; 905 } 906 } 907 if (i == up->u_rc) { 908 (void) close(ptmpfd); 909 (void) pw_abort(); 910 errx(EXIT_FAILURE, "can't get next uid for %d", up->u_uid); 911 } 912 } 913 /* check uid isn't already allocated */ 914 if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) { 915 (void) close(ptmpfd); 916 (void) pw_abort(); 917 errx(EXIT_FAILURE, "uid %d is already in use", up->u_uid); 918 } 919 /* if -g=uid was specified, check gid is unused */ 920 if (sync_uid_gid) { 921 if (getgrgid((gid_t)(up->u_uid)) != NULL) { 922 (void) close(ptmpfd); 923 (void) pw_abort(); 924 errx(EXIT_FAILURE, "gid %d is already in use", up->u_uid); 925 } 926 gid = up->u_uid; 927 } else if ((grp = getgrnam(up->u_primgrp)) != NULL) { 928 gid = grp->gr_gid; 929 } else if (is_number(up->u_primgrp) && 930 (grp = getgrgid((gid_t)atoi(up->u_primgrp))) != NULL) { 931 gid = grp->gr_gid; 932 } else { 933 (void) close(ptmpfd); 934 (void) pw_abort(); 935 errx(EXIT_FAILURE, "group %s not found", up->u_primgrp); 936 } 937 /* check name isn't already in use */ 938 if (!(up->u_flags & F_DUPUID) && getpwnam(login) != NULL) { 939 (void) close(ptmpfd); 940 (void) pw_abort(); 941 errx(EXIT_FAILURE, "already a `%s' user", login); 942 } 943 if (up->u_flags & F_HOMEDIR) { 944 (void) strlcpy(home, up->u_home, sizeof(home)); 945 } else { 946 /* if home directory hasn't been given, make it up */ 947 (void) snprintf(home, sizeof(home), "%s/%s", up->u_basedir, login); 948 } 949 expire = 0; 950 if (up->u_expire != NULL) { 951 (void) memset(&tm, 0, sizeof(tm)); 952 if (strptime(up->u_expire, "%c", &tm) == NULL) { 953 warnx("invalid time format `%s'", optarg); 954 } else { 955 expire = mktime(&tm); 956 } 957 } 958 if (lstat(home, &st) < 0 && !(up->u_flags & F_MKDIR)) { 959 warnx("Warning: home directory `%s' doesn't exist, and -m was not specified", 960 home); 961 } 962 password[PasswordLength] = '\0'; 963 if (up->u_password != NULL && 964 strlen(up->u_password) == PasswordLength) { 965 (void) memcpy(password, up->u_password, PasswordLength); 966 } else { 967 (void) memset(password, '*', PasswordLength); 968 if (up->u_password != NULL) { 969 warnx("Password `%s' is invalid: setting it to `%s'", 970 up->u_password, password); 971 } 972 } 973 cc = snprintf(buf, sizeof(buf), "%s:%s:%d:%d:%s:%d:%ld:%s:%s:%s\n", 974 login, 975 password, 976 up->u_uid, 977 gid, 978 #ifdef EXTENSIONS 979 up->u_class, 980 #else 981 "", 982 #endif 983 up->u_inactive, 984 (long) expire, 985 up->u_comment, 986 home, 987 up->u_shell); 988 if (write(ptmpfd, buf, (size_t) cc) != cc) { 989 (void) close(ptmpfd); 990 (void) pw_abort(); 991 err(EXIT_FAILURE, "can't add `%s'", buf); 992 } 993 if (up->u_flags & F_MKDIR) { 994 if (lstat(home, &st) == 0) { 995 (void) close(ptmpfd); 996 (void) pw_abort(); 997 errx(EXIT_FAILURE, "home directory `%s' already exists", home); 998 } else { 999 if (asystem("%s -p %s", MKDIR, home) != 0) { 1000 (void) close(ptmpfd); 1001 (void) pw_abort(); 1002 err(EXIT_FAILURE, "can't mkdir `%s'", home); 1003 } 1004 (void) copydotfiles(up->u_skeldir, up->u_uid, gid, home); 1005 } 1006 } 1007 if (strcmp(up->u_primgrp, "=uid") == 0 && 1008 getgrnam(login) == NULL && 1009 !creategid(login, gid, login)) { 1010 (void) close(ptmpfd); 1011 (void) pw_abort(); 1012 errx(EXIT_FAILURE, "can't create gid %d for login name %s", gid, login); 1013 } 1014 if (up->u_groupc > 0 && !append_group(login, up->u_groupc, up->u_groupv)) { 1015 (void) close(ptmpfd); 1016 (void) pw_abort(); 1017 errx(EXIT_FAILURE, "can't append `%s' to new groups", login); 1018 } 1019 (void) close(ptmpfd); 1020 #if PW_MKDB_ARGC == 2 1021 if (pw_mkdb(login, 0) < 0) { 1022 err(EXIT_FAILURE, "pw_mkdb failed"); 1023 } 1024 #else 1025 if (pw_mkdb() < 0) { 1026 err(EXIT_FAILURE, "pw_mkdb failed"); 1027 } 1028 #endif 1029 return 1; 1030 } 1031 1032 /* modify a user */ 1033 static int 1034 moduser(char *login, char *newlogin, user_t *up) 1035 { 1036 struct passwd *pwp; 1037 struct group *grp; 1038 struct tm tm; 1039 const char *homedir; 1040 size_t colonc, len, loginc; 1041 size_t cc; 1042 FILE *master; 1043 char newdir[MaxFileNameLen]; 1044 char *buf, *colon, *line; 1045 int masterfd; 1046 int ptmpfd; 1047 int error; 1048 1049 if (!valid_login(newlogin)) { 1050 errx(EXIT_FAILURE, "`%s' is not a valid login name", login); 1051 } 1052 if ((pwp = getpwnam(login)) == NULL) { 1053 errx(EXIT_FAILURE, "No such user `%s'", login); 1054 } 1055 /* keep dir name in case we need it for '-m' */ 1056 homedir = pwp->pw_dir; 1057 1058 if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) { 1059 err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD); 1060 } 1061 if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) { 1062 err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD); 1063 } 1064 pw_init(); 1065 if ((ptmpfd = pw_lock(WAITSECS)) < 0) { 1066 (void) close(masterfd); 1067 err(EXIT_FAILURE, "can't obtain pw_lock"); 1068 } 1069 if ((master = fdopen(masterfd, "r")) == NULL) { 1070 (void) close(masterfd); 1071 (void) close(ptmpfd); 1072 (void) pw_abort(); 1073 err(EXIT_FAILURE, "can't fdopen fd for %s", _PATH_MASTERPASSWD); 1074 } 1075 if (up != NULL) { 1076 if (up->u_flags & F_USERNAME) { 1077 /* if changing name, check new name isn't already in use */ 1078 if (strcmp(login, newlogin) != 0 && getpwnam(newlogin) != NULL) { 1079 (void) close(ptmpfd); 1080 (void) pw_abort(); 1081 errx(EXIT_FAILURE, "already a `%s' user", newlogin); 1082 } 1083 pwp->pw_name = newlogin; 1084 1085 /* 1086 * Provide a new directory name in case the 1087 * home directory is to be moved. 1088 */ 1089 if (up->u_flags & F_MKDIR) { 1090 snprintf(newdir, sizeof(newdir), "%s/%s", up->u_basedir, newlogin); 1091 pwp->pw_dir = newdir; 1092 } 1093 } 1094 if (up->u_flags & F_PASSWORD) { 1095 if (up->u_password != NULL && strlen(up->u_password) == PasswordLength) 1096 pwp->pw_passwd = up->u_password; 1097 } 1098 if (up->u_flags & F_UID) { 1099 /* check uid isn't already allocated */ 1100 if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) { 1101 (void) close(ptmpfd); 1102 (void) pw_abort(); 1103 errx(EXIT_FAILURE, "uid %d is already in use", up->u_uid); 1104 } 1105 pwp->pw_uid = up->u_uid; 1106 } 1107 if (up->u_flags & F_GROUP) { 1108 /* if -g=uid was specified, check gid is unused */ 1109 if (strcmp(up->u_primgrp, "=uid") == 0) { 1110 if (getgrgid((gid_t)(up->u_uid)) != NULL) { 1111 (void) close(ptmpfd); 1112 (void) pw_abort(); 1113 errx(EXIT_FAILURE, "gid %d is already in use", up->u_uid); 1114 } 1115 pwp->pw_gid = up->u_uid; 1116 } else if ((grp = getgrnam(up->u_primgrp)) != NULL) { 1117 pwp->pw_gid = grp->gr_gid; 1118 } else if (is_number(up->u_primgrp) && 1119 (grp = getgrgid((gid_t)atoi(up->u_primgrp))) != NULL) { 1120 pwp->pw_gid = grp->gr_gid; 1121 } else { 1122 (void) close(ptmpfd); 1123 (void) pw_abort(); 1124 errx(EXIT_FAILURE, "group %s not found", up->u_primgrp); 1125 } 1126 } 1127 if (up->u_flags |= F_INACTIVE) 1128 pwp->pw_change = up->u_inactive; 1129 if (up->u_flags & F_EXPIRE) { 1130 (void) memset(&tm, 0, sizeof(tm)); 1131 if (strptime(up->u_expire, "%c", &tm) == NULL) 1132 warnx("invalid time format `%s'", optarg); 1133 else 1134 pwp->pw_expire = mktime(&tm); 1135 } 1136 if (up->u_flags & F_COMMENT) 1137 pwp->pw_gecos = up->u_comment; 1138 if (up->u_flags & F_HOMEDIR) 1139 pwp->pw_dir = up->u_home; 1140 if (up->u_flags & F_SHELL) 1141 pwp->pw_shell = up->u_shell; 1142 #ifdef EXTENSIONS 1143 if (up->u_flags & F_CLASS) 1144 pwp->pw_class = up->u_class; 1145 #endif 1146 } 1147 loginc = strlen(login); 1148 while ((line = fgetln(master, &len)) != NULL) { 1149 if ((colon = strchr(line, ':')) == NULL) { 1150 warnx("Malformed entry `%s'. Skipping", line); 1151 continue; 1152 } 1153 colonc = (size_t)(colon - line); 1154 if (strncmp(login, line, loginc) == 0 && loginc == colonc) { 1155 if (up != NULL) { 1156 len = (int)asprintf(&buf, "%s:%s:%d:%d:" 1157 #ifdef EXTENSIONS 1158 "%s" 1159 #endif 1160 ":%ld:%ld:%s:%s:%s\n", 1161 newlogin, 1162 pwp->pw_passwd, 1163 pwp->pw_uid, 1164 pwp->pw_gid, 1165 #ifdef EXTENSIONS 1166 pwp->pw_class, 1167 #endif 1168 (long)pwp->pw_change, 1169 (long)pwp->pw_expire, 1170 pwp->pw_gecos, 1171 pwp->pw_dir, 1172 pwp->pw_shell); 1173 if (write(ptmpfd, buf, len) != len) { 1174 (void) close(ptmpfd); 1175 (void) pw_abort(); 1176 err(EXIT_FAILURE, "can't add `%s'", buf); 1177 } 1178 (void) free(buf); 1179 } 1180 } else if ((cc = write(ptmpfd, line, len)) != len) { 1181 (void) close(masterfd); 1182 (void) close(ptmpfd); 1183 (void) pw_abort(); 1184 err(EXIT_FAILURE, "short write to /etc/ptmp (%lld not %lld chars)", 1185 (long long)cc, 1186 (long long)len); 1187 } 1188 } 1189 if (up != NULL) { 1190 if ((up->u_flags & F_MKDIR) && 1191 asystem("%s %s %s", MV, homedir, pwp->pw_dir) != 0) { 1192 (void) close(ptmpfd); 1193 (void) pw_abort(); 1194 err(EXIT_FAILURE, "can't move `%s' to `%s'", 1195 homedir, pwp->pw_dir); 1196 } 1197 if (up->u_groupc > 0 && 1198 !append_group(newlogin, up->u_groupc, up->u_groupv)) { 1199 (void) close(ptmpfd); 1200 (void) pw_abort(); 1201 errx(EXIT_FAILURE, "can't append `%s' to new groups", 1202 newlogin); 1203 } 1204 } 1205 (void) close(ptmpfd); 1206 #if PW_MKDB_ARGC == 2 1207 if (up != NULL && strcmp(login, newlogin) == 0) { 1208 error = pw_mkdb(login, 0); 1209 } else { 1210 error = pw_mkdb(NULL, 0); 1211 } 1212 #else 1213 error = pw_mkdb(); 1214 #endif 1215 if (error < 0) { 1216 err(EXIT_FAILURE, "pw_mkdb failed"); 1217 } 1218 1219 return 1; 1220 } 1221 1222 1223 #ifdef EXTENSIONS 1224 /* see if we can find out the user struct */ 1225 static struct passwd * 1226 find_user_info(char *name) 1227 { 1228 struct passwd *pwp; 1229 1230 if ((pwp = getpwnam(name)) != NULL) { 1231 return pwp; 1232 } 1233 if (is_number(name) && (pwp = getpwuid((uid_t)atoi(name))) != NULL) { 1234 return pwp; 1235 } 1236 return NULL; 1237 } 1238 #endif 1239 1240 #ifdef EXTENSIONS 1241 /* see if we can find out the group struct */ 1242 static struct group * 1243 find_group_info(char *name) 1244 { 1245 struct group *grp; 1246 1247 if ((grp = getgrnam(name)) != NULL) { 1248 return grp; 1249 } 1250 if (is_number(name) && (grp = getgrgid((gid_t)atoi(name))) != NULL) { 1251 return grp; 1252 } 1253 return NULL; 1254 } 1255 #endif 1256 1257 /* print out usage message, and then exit */ 1258 void 1259 usermgmt_usage(const char *prog) 1260 { 1261 if (strcmp(prog, "useradd") == 0) { 1262 (void) fprintf(stderr, "Usage: %s -D [-b basedir] [-e expiry] " 1263 "[-f inactive] [-g group]\n\t[-r lowuid..highuid] " 1264 "[-s shell] [-L class]\n", prog); 1265 (void) fprintf(stderr, "Usage: %s [-G group] [-b basedir] " 1266 "[-c comment] [-d homedir] [-e expiry]\n\t[-f inactive] " 1267 "[-g group] [-k skeletondir] [-m] [-o] [-p password]\n" 1268 "\t[-r lowuid..highuid] [-s shell]\n\t[-u uid] [-v] user\n", 1269 prog); 1270 } else if (strcmp(prog, "usermod") == 0) { 1271 (void) fprintf(stderr, "Usage: %s [-G group] [-c comment] " 1272 "[-d homedir] [-e expire] [-f inactive]\n\t[-g group] " 1273 "[-l newname] [-m] [-o] [-p password] [-s shell] [-u uid]\n" 1274 "\t[-L class] [-v] user\n", prog); 1275 } else if (strcmp(prog, "userdel") == 0) { 1276 (void) fprintf(stderr, "Usage: %s -D [-p preserve]\n", prog); 1277 (void) fprintf(stderr, 1278 "Usage: %s [-p preserve] [-r] [-v] user\n", prog); 1279 #ifdef EXTENSIONS 1280 } else if (strcmp(prog, "userinfo") == 0) { 1281 (void) fprintf(stderr, "Usage: %s [-e] [-v] user\n", prog); 1282 #endif 1283 } else if (strcmp(prog, "groupadd") == 0) { 1284 (void) fprintf(stderr, "Usage: %s [-g gid] [-o] [-v] group\n", 1285 prog); 1286 } else if (strcmp(prog, "groupdel") == 0) { 1287 (void) fprintf(stderr, "Usage: %s [-v] group\n", prog); 1288 } else if (strcmp(prog, "groupmod") == 0) { 1289 (void) fprintf(stderr, 1290 "Usage: %s [-g gid] [-o] [-n newname] [-v] group\n", prog); 1291 } else if (strcmp(prog, "user") == 0 || strcmp(prog, "group") == 0) { 1292 (void) fprintf(stderr, 1293 "Usage: %s ( add | del | mod | info ) ...\n", prog); 1294 #ifdef EXTENSIONS 1295 } else if (strcmp(prog, "groupinfo") == 0) { 1296 (void) fprintf(stderr, "Usage: %s [-e] [-v] group\n", prog); 1297 #endif 1298 } 1299 exit(EXIT_FAILURE); 1300 /* NOTREACHED */ 1301 } 1302 1303 #ifdef EXTENSIONS 1304 #define ADD_OPT_EXTENSIONS "p:r:vL:" 1305 #else 1306 #define ADD_OPT_EXTENSIONS 1307 #endif 1308 1309 int 1310 useradd(int argc, char **argv) 1311 { 1312 user_t u; 1313 int defaultfield; 1314 int bigD; 1315 int c; 1316 #ifdef EXTENSIONS 1317 int i; 1318 #endif 1319 1320 (void) memset(&u, 0, sizeof(u)); 1321 read_defaults(&u); 1322 u.u_uid = -1; 1323 defaultfield = bigD = 0; 1324 while ((c = getopt(argc, argv, "DG:b:c:d:e:f:g:k:mou:s:" ADD_OPT_EXTENSIONS)) != -1) { 1325 switch(c) { 1326 case 'D': 1327 bigD = 1; 1328 break; 1329 case 'G': 1330 while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL && 1331 u.u_groupc < NGROUPS_MAX) { 1332 if (u.u_groupv[u.u_groupc][0] != 0) { 1333 u.u_groupc++; 1334 } 1335 } 1336 if (optarg != NULL) { 1337 warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX); 1338 } 1339 break; 1340 case 'b': 1341 defaultfield = 1; 1342 memsave(&u.u_basedir, optarg, strlen(optarg)); 1343 break; 1344 case 'c': 1345 memsave(&u.u_comment, optarg, strlen(optarg)); 1346 break; 1347 case 'd': 1348 memsave(&u.u_home, optarg, strlen(optarg)); 1349 u.u_flags |= F_HOMEDIR; 1350 break; 1351 case 'e': 1352 defaultfield = 1; 1353 memsave(&u.u_expire, optarg, strlen(optarg)); 1354 break; 1355 case 'f': 1356 defaultfield = 1; 1357 u.u_inactive = atoi(optarg); 1358 break; 1359 case 'g': 1360 defaultfield = 1; 1361 memsave(&u.u_primgrp, optarg, strlen(optarg)); 1362 break; 1363 case 'k': 1364 memsave(&u.u_skeldir, optarg, strlen(optarg)); 1365 break; 1366 #ifdef EXTENSIONS 1367 case 'L': 1368 defaultfield = 1; 1369 memsave(&u.u_class, optarg, strlen(optarg)); 1370 break; 1371 #endif 1372 case 'm': 1373 u.u_flags |= F_MKDIR; 1374 break; 1375 case 'o': 1376 u.u_flags |= F_DUPUID; 1377 break; 1378 #ifdef EXTENSIONS 1379 case 'p': 1380 memsave(&u.u_password, optarg, strlen(optarg)); 1381 break; 1382 #endif 1383 #ifdef EXTENSIONS 1384 case 'r': 1385 defaultfield = 1; 1386 (void) save_range(&u, optarg); 1387 break; 1388 #endif 1389 case 's': 1390 defaultfield = 1; 1391 memsave(&u.u_shell, optarg, strlen(optarg)); 1392 break; 1393 case 'u': 1394 if (!is_number(optarg)) { 1395 errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric"); 1396 } 1397 u.u_uid = atoi(optarg); 1398 break; 1399 #ifdef EXTENSIONS 1400 case 'v': 1401 verbose = 1; 1402 break; 1403 #endif 1404 } 1405 } 1406 if (bigD) { 1407 if (defaultfield) { 1408 checkeuid(); 1409 return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE; 1410 } 1411 (void) printf("group\t\t%s\n", u.u_primgrp); 1412 (void) printf("base_dir\t%s\n", u.u_basedir); 1413 (void) printf("skel_dir\t%s\n", u.u_skeldir); 1414 (void) printf("shell\t\t%s\n", u.u_shell); 1415 #ifdef EXTENSIONS 1416 (void) printf("class\t\t%s\n", u.u_class); 1417 #endif 1418 (void) printf("inactive\t%d\n", u.u_inactive); 1419 (void) printf("expire\t\t%s\n", (u.u_expire == NULL) ? UNSET_EXPIRY : u.u_expire); 1420 #ifdef EXTENSIONS 1421 for (i = 0 ; i < u.u_rc ; i++) { 1422 (void) printf("range\t\t%d..%d\n", u.u_rv[i].r_from, u.u_rv[i].r_to); 1423 } 1424 #endif 1425 return EXIT_SUCCESS; 1426 } 1427 argc -= optind; 1428 argv += optind; 1429 if (argc != 1) { 1430 usermgmt_usage("useradd"); 1431 } 1432 checkeuid(); 1433 return adduser(*argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE; 1434 } 1435 1436 #ifdef EXTENSIONS 1437 #define MOD_OPT_EXTENSIONS "p:vL:" 1438 #else 1439 #define MOD_OPT_EXTENSIONS 1440 #endif 1441 1442 int 1443 usermod(int argc, char **argv) 1444 { 1445 user_t u; 1446 char newuser[MaxUserNameLen + 1]; 1447 int c, have_new_user; 1448 1449 (void) memset(&u, 0, sizeof(u)); 1450 (void) memset(newuser, 0, sizeof(newuser)); 1451 read_defaults(&u); 1452 have_new_user = 0; 1453 while ((c = getopt(argc, argv, "G:c:d:e:f:g:l:mos:u:" MOD_OPT_EXTENSIONS)) != -1) { 1454 switch(c) { 1455 case 'G': 1456 while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL && 1457 u.u_groupc < NGROUPS_MAX) { 1458 if (u.u_groupv[u.u_groupc][0] != 0) { 1459 u.u_groupc++; 1460 } 1461 } 1462 if (optarg != NULL) { 1463 warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX); 1464 } 1465 u.u_flags |= F_SECGROUP; 1466 break; 1467 case 'c': 1468 memsave(&u.u_comment, optarg, strlen(optarg)); 1469 u.u_flags |= F_COMMENT; 1470 break; 1471 case 'd': 1472 memsave(&u.u_home, optarg, strlen(optarg)); 1473 u.u_flags |= F_HOMEDIR; 1474 break; 1475 case 'e': 1476 memsave(&u.u_expire, optarg, strlen(optarg)); 1477 u.u_flags |= F_EXPIRE; 1478 break; 1479 case 'f': 1480 u.u_inactive = atoi(optarg); 1481 u.u_flags |= F_INACTIVE; 1482 break; 1483 case 'g': 1484 memsave(&u.u_primgrp, optarg, strlen(optarg)); 1485 u.u_flags |= F_GROUP; 1486 break; 1487 case 'l': 1488 (void) strlcpy(newuser, optarg, sizeof(newuser)); 1489 have_new_user = 1; 1490 u.u_flags |= F_USERNAME; 1491 break; 1492 #ifdef EXTENSIONS 1493 case 'L': 1494 memsave(&u.u_class, optarg, strlen(optarg)); 1495 u.u_flags |= F_CLASS; 1496 break; 1497 #endif 1498 case 'm': 1499 u.u_flags |= F_MKDIR; 1500 break; 1501 case 'o': 1502 u.u_flags |= F_DUPUID; 1503 break; 1504 #ifdef EXTENSIONS 1505 case 'p': 1506 memsave(&u.u_password, optarg, strlen(optarg)); 1507 u.u_flags |= F_PASSWORD; 1508 break; 1509 #endif 1510 case 's': 1511 memsave(&u.u_shell, optarg, strlen(optarg)); 1512 u.u_flags |= F_SHELL; 1513 break; 1514 case 'u': 1515 if (!is_number(optarg)) { 1516 errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric"); 1517 } 1518 u.u_uid = atoi(optarg); 1519 u.u_flags |= F_UID; 1520 break; 1521 #ifdef EXTENSIONS 1522 case 'v': 1523 verbose = 1; 1524 break; 1525 #endif 1526 } 1527 } 1528 if ((u.u_flags & F_MKDIR) && !(u.u_flags & F_HOMEDIR) && 1529 !(u.u_flags & F_USERNAME)) { 1530 warnx("option 'm' useless without 'd' or 'l' -- ignored"); 1531 u.u_flags &= ~F_MKDIR; 1532 } 1533 argc -= optind; 1534 argv += optind; 1535 if (argc != 1) { 1536 usermgmt_usage("usermod"); 1537 } 1538 checkeuid(); 1539 return moduser(*argv, (have_new_user) ? newuser : *argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE; 1540 } 1541 1542 #ifdef EXTENSIONS 1543 #define DEL_OPT_EXTENSIONS "Dp:v" 1544 #else 1545 #define DEL_OPT_EXTENSIONS 1546 #endif 1547 1548 int 1549 userdel(int argc, char **argv) 1550 { 1551 struct passwd *pwp; 1552 user_t u; 1553 char password[PasswordLength + 1]; 1554 int defaultfield; 1555 int rmhome; 1556 int bigD; 1557 int c; 1558 1559 (void) memset(&u, 0, sizeof(u)); 1560 read_defaults(&u); 1561 defaultfield = bigD = rmhome = 0; 1562 while ((c = getopt(argc, argv, "r" DEL_OPT_EXTENSIONS)) != -1) { 1563 switch(c) { 1564 #ifdef EXTENSIONS 1565 case 'D': 1566 bigD = 1; 1567 break; 1568 #endif 1569 #ifdef EXTENSIONS 1570 case 'p': 1571 defaultfield = 1; 1572 u.u_preserve = (strcmp(optarg, "true") == 0) ? 1 : 1573 (strcmp(optarg, "yes") == 0) ? 1 : 1574 atoi(optarg); 1575 break; 1576 #endif 1577 case 'r': 1578 rmhome = 1; 1579 break; 1580 #ifdef EXTENSIONS 1581 case 'v': 1582 verbose = 1; 1583 break; 1584 #endif 1585 } 1586 } 1587 #ifdef EXTENSIONS 1588 if (bigD) { 1589 if (defaultfield) { 1590 checkeuid(); 1591 return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE; 1592 } 1593 (void) printf("preserve\t%s\n", (u.u_preserve) ? "true" : "false"); 1594 return EXIT_SUCCESS; 1595 } 1596 #endif 1597 argc -= optind; 1598 argv += optind; 1599 if (argc != 1) { 1600 usermgmt_usage("userdel"); 1601 } 1602 checkeuid(); 1603 if ((pwp = getpwnam(*argv)) == NULL) { 1604 warnx("No such user `%s'", *argv); 1605 return EXIT_FAILURE; 1606 } 1607 if (rmhome) 1608 (void)removehomedir(pwp->pw_name, pwp->pw_uid, pwp->pw_dir); 1609 if (u.u_preserve) { 1610 u.u_flags |= F_SHELL; 1611 memsave(&u.u_shell, NOLOGIN, strlen(NOLOGIN)); 1612 (void) memset(password, '*', PasswordLength); 1613 password[PasswordLength] = '\0'; 1614 memsave(&u.u_password, password, PasswordLength); 1615 u.u_flags |= F_PASSWORD; 1616 return moduser(*argv, *argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE; 1617 } 1618 return moduser(*argv, *argv, NULL) ? EXIT_SUCCESS : EXIT_FAILURE; 1619 } 1620 1621 #ifdef EXTENSIONS 1622 #define GROUP_ADD_OPT_EXTENSIONS "v" 1623 #else 1624 #define GROUP_ADD_OPT_EXTENSIONS 1625 #endif 1626 1627 /* add a group */ 1628 int 1629 groupadd(int argc, char **argv) 1630 { 1631 int dupgid; 1632 int gid; 1633 int c; 1634 1635 gid = -1; 1636 dupgid = 0; 1637 while ((c = getopt(argc, argv, "g:o" GROUP_ADD_OPT_EXTENSIONS)) != -1) { 1638 switch(c) { 1639 case 'g': 1640 if (!is_number(optarg)) { 1641 errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric"); 1642 } 1643 gid = atoi(optarg); 1644 break; 1645 case 'o': 1646 dupgid = 1; 1647 break; 1648 #ifdef EXTENSIONS 1649 case 'v': 1650 verbose = 1; 1651 break; 1652 #endif 1653 } 1654 } 1655 argc -= optind; 1656 argv += optind; 1657 if (argc != 1) { 1658 usermgmt_usage("groupadd"); 1659 } 1660 checkeuid(); 1661 if (gid < 0 && !getnextgid(&gid, LowGid, HighGid)) { 1662 err(EXIT_FAILURE, "can't add group: can't get next gid"); 1663 } 1664 if (!dupgid && getgrgid((gid_t) gid) != NULL) { 1665 errx(EXIT_FAILURE, "can't add group: gid %d is a duplicate", gid); 1666 } 1667 if (!valid_group(*argv)) { 1668 warnx("warning - invalid group name `%s'", *argv); 1669 } 1670 if (!creategid(*argv, gid, "")) { 1671 errx(EXIT_FAILURE, "can't add group: problems with %s file", _PATH_GROUP); 1672 } 1673 return EXIT_SUCCESS; 1674 } 1675 1676 #ifdef EXTENSIONS 1677 #define GROUP_DEL_OPT_EXTENSIONS "v" 1678 #else 1679 #define GROUP_DEL_OPT_EXTENSIONS 1680 #endif 1681 1682 /* remove a group */ 1683 int 1684 groupdel(int argc, char **argv) 1685 { 1686 int c; 1687 1688 while ((c = getopt(argc, argv, "" GROUP_DEL_OPT_EXTENSIONS)) != -1) { 1689 switch(c) { 1690 #ifdef EXTENSIONS 1691 case 'v': 1692 verbose = 1; 1693 break; 1694 #endif 1695 } 1696 } 1697 argc -= optind; 1698 argv += optind; 1699 if (argc != 1) { 1700 usermgmt_usage("groupdel"); 1701 } 1702 checkeuid(); 1703 if (!modify_gid(*argv, NULL)) { 1704 err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP); 1705 } 1706 return EXIT_SUCCESS; 1707 } 1708 1709 #ifdef EXTENSIONS 1710 #define GROUP_MOD_OPT_EXTENSIONS "v" 1711 #else 1712 #define GROUP_MOD_OPT_EXTENSIONS 1713 #endif 1714 1715 /* modify a group */ 1716 int 1717 groupmod(int argc, char **argv) 1718 { 1719 struct group *grp; 1720 char buf[MaxEntryLen]; 1721 char *newname; 1722 char **cpp; 1723 int dupgid; 1724 int gid; 1725 int cc; 1726 int c; 1727 1728 gid = -1; 1729 dupgid = 0; 1730 newname = NULL; 1731 while ((c = getopt(argc, argv, "g:on:" GROUP_MOD_OPT_EXTENSIONS)) != -1) { 1732 switch(c) { 1733 case 'g': 1734 if (!is_number(optarg)) { 1735 errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric"); 1736 } 1737 gid = atoi(optarg); 1738 break; 1739 case 'o': 1740 dupgid = 1; 1741 break; 1742 case 'n': 1743 memsave(&newname, optarg, strlen(optarg)); 1744 break; 1745 #ifdef EXTENSIONS 1746 case 'v': 1747 verbose = 1; 1748 break; 1749 #endif 1750 } 1751 } 1752 argc -= optind; 1753 argv += optind; 1754 if (argc != 1) { 1755 usermgmt_usage("groupmod"); 1756 } 1757 checkeuid(); 1758 if (gid < 0 && newname == NULL) { 1759 err(EXIT_FAILURE, "Nothing to change"); 1760 } 1761 if (dupgid && gid < 0) { 1762 err(EXIT_FAILURE, "Duplicate which gid?"); 1763 } 1764 if ((grp = getgrnam(*argv)) == NULL) { 1765 err(EXIT_FAILURE, "can't find group `%s' to modify", *argv); 1766 } 1767 if (newname != NULL && !valid_group(newname)) { 1768 warn("warning - invalid group name `%s'", newname); 1769 } 1770 cc = snprintf(buf, sizeof(buf), "%s:%s:%d:", 1771 (newname) ? newname : grp->gr_name, 1772 grp->gr_passwd, 1773 (gid < 0) ? grp->gr_gid : gid); 1774 for (cpp = grp->gr_mem ; *cpp && cc < sizeof(buf) ; cpp++) { 1775 cc += snprintf(&buf[cc], sizeof(buf) - cc, "%s%s", *cpp, 1776 (cpp[1] == NULL) ? "" : ","); 1777 } 1778 cc += snprintf(&buf[cc], sizeof(buf) - cc, "\n"); 1779 if (!modify_gid(*argv, buf)) { 1780 err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP); 1781 } 1782 return EXIT_SUCCESS; 1783 } 1784 1785 #ifdef EXTENSIONS 1786 /* display user information */ 1787 int 1788 userinfo(int argc, char **argv) 1789 { 1790 struct passwd *pwp; 1791 struct group *grp; 1792 char buf[MaxEntryLen]; 1793 char **cpp; 1794 int exists; 1795 int cc; 1796 int i; 1797 1798 exists = 0; 1799 while ((i = getopt(argc, argv, "ev")) != -1) { 1800 switch(i) { 1801 case 'e': 1802 exists = 1; 1803 break; 1804 case 'v': 1805 verbose = 1; 1806 break; 1807 } 1808 } 1809 argc -= optind; 1810 argv += optind; 1811 if (argc != 1) { 1812 usermgmt_usage("userinfo"); 1813 } 1814 pwp = find_user_info(*argv); 1815 if (exists) { 1816 exit((pwp) ? EXIT_SUCCESS : EXIT_FAILURE); 1817 } 1818 if (pwp == NULL) { 1819 errx(EXIT_FAILURE, "can't find user `%s'", *argv); 1820 } 1821 (void) printf("login\t%s\n", pwp->pw_name); 1822 (void) printf("passwd\t%s\n", pwp->pw_passwd); 1823 (void) printf("uid\t%d\n", pwp->pw_uid); 1824 for (cc = 0 ; (grp = getgrent()) != NULL ; ) { 1825 for (cpp = grp->gr_mem ; *cpp ; cpp++) { 1826 if (strcmp(*cpp, *argv) == 0 && grp->gr_gid != pwp->pw_gid) { 1827 cc += snprintf(&buf[cc], sizeof(buf) - cc, "%s ", grp->gr_name); 1828 } 1829 } 1830 } 1831 if ((grp = getgrgid(pwp->pw_gid)) == NULL) { 1832 (void) printf("groups\t%d %s\n", pwp->pw_gid, buf); 1833 } else { 1834 (void) printf("groups\t%s %s\n", grp->gr_name, buf); 1835 } 1836 (void) printf("change\t%s", pwp->pw_change ? ctime(&pwp->pw_change) : "NEVER\n"); 1837 #ifdef EXTENSIONS 1838 (void) printf("class\t%s\n", pwp->pw_class); 1839 #endif 1840 (void) printf("gecos\t%s\n", pwp->pw_gecos); 1841 (void) printf("dir\t%s\n", pwp->pw_dir); 1842 (void) printf("shell\t%s\n", pwp->pw_shell); 1843 (void) printf("expire\t%s", pwp->pw_expire ? ctime(&pwp->pw_expire) : "NEVER\n"); 1844 return EXIT_SUCCESS; 1845 } 1846 #endif 1847 1848 #ifdef EXTENSIONS 1849 /* display user information */ 1850 int 1851 groupinfo(int argc, char **argv) 1852 { 1853 struct group *grp; 1854 char **cpp; 1855 int exists; 1856 int i; 1857 1858 exists = 0; 1859 while ((i = getopt(argc, argv, "ev")) != -1) { 1860 switch(i) { 1861 case 'e': 1862 exists = 1; 1863 break; 1864 case 'v': 1865 verbose = 1; 1866 break; 1867 } 1868 } 1869 argc -= optind; 1870 argv += optind; 1871 if (argc != 1) { 1872 usermgmt_usage("groupinfo"); 1873 } 1874 grp = find_group_info(*argv); 1875 if (exists) { 1876 exit((grp) ? EXIT_SUCCESS : EXIT_FAILURE); 1877 } 1878 if (grp == NULL) { 1879 errx(EXIT_FAILURE, "can't find group `%s'", *argv); 1880 } 1881 (void) printf("name\t%s\n", grp->gr_name); 1882 (void) printf("passwd\t%s\n", grp->gr_passwd); 1883 (void) printf("gid\t%d\n", grp->gr_gid); 1884 (void) printf("members\t"); 1885 for (cpp = grp->gr_mem ; *cpp ; cpp++) { 1886 (void) printf("%s ", *cpp); 1887 } 1888 (void) fputc('\n', stdout); 1889 return EXIT_SUCCESS; 1890 } 1891 #endif 1892