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