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