1 1.135 nia /* $NetBSD: user.c,v 1.135 2021/12/12 14:33:13 nia Exp $ */ 2 1.1 agc 3 1.1 agc /* 4 1.4 lukem * Copyright (c) 1999 Alistair G. Crooks. All rights reserved. 5 1.96 christos * Copyright (c) 2005 Liam J. Foy. All rights reserved. 6 1.1 agc * 7 1.1 agc * Redistribution and use in source and binary forms, with or without 8 1.1 agc * modification, are permitted provided that the following conditions 9 1.1 agc * are met: 10 1.1 agc * 1. Redistributions of source code must retain the above copyright 11 1.1 agc * notice, this list of conditions and the following disclaimer. 12 1.1 agc * 2. Redistributions in binary form must reproduce the above copyright 13 1.1 agc * notice, this list of conditions and the following disclaimer in the 14 1.1 agc * documentation and/or other materials provided with the distribution. 15 1.98 agc * 3. The name of the author may not be used to endorse or promote 16 1.1 agc * products derived from this software without specific prior written 17 1.1 agc * permission. 18 1.1 agc * 19 1.1 agc * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 20 1.1 agc * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 1.1 agc * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 1.1 agc * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 1.1 agc * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 1.1 agc * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 25 1.1 agc * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 1.1 agc * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 27 1.1 agc * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 1.1 agc * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 1.1 agc * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 1.1 agc */ 31 1.11 agc #include <sys/cdefs.h> 32 1.11 agc 33 1.11 agc #ifndef lint 34 1.120 lukem __COPYRIGHT("@(#) Copyright (c) 1999\ 35 1.120 lukem The NetBSD Foundation, Inc. All rights reserved."); 36 1.135 nia __RCSID("$NetBSD: user.c,v 1.135 2021/12/12 14:33:13 nia Exp $"); 37 1.11 agc #endif 38 1.11 agc 39 1.1 agc #include <sys/types.h> 40 1.1 agc #include <sys/param.h> 41 1.1 agc #include <sys/stat.h> 42 1.128 dholland #include <sys/wait.h> 43 1.1 agc 44 1.5 lukem #include <ctype.h> 45 1.5 lukem #include <dirent.h> 46 1.5 lukem #include <err.h> 47 1.5 lukem #include <fcntl.h> 48 1.5 lukem #include <grp.h> 49 1.82 christos #ifdef EXTENSIONS 50 1.82 christos #include <login_cap.h> 51 1.82 christos #endif 52 1.5 lukem #include <pwd.h> 53 1.53 agc #include <regex.h> 54 1.5 lukem #include <stdarg.h> 55 1.1 agc #include <stdio.h> 56 1.1 agc #include <stdlib.h> 57 1.1 agc #include <string.h> 58 1.59 agc #include <syslog.h> 59 1.1 agc #include <time.h> 60 1.5 lukem #include <unistd.h> 61 1.1 agc #include <util.h> 62 1.82 christos #include <errno.h> 63 1.1 agc 64 1.129 dholland #include "pathnames.h" 65 1.1 agc #include "defs.h" 66 1.1 agc #include "usermgmt.h" 67 1.1 agc 68 1.26 simonb 69 1.9 agc /* this struct describes a uid range */ 70 1.9 agc typedef struct range_t { 71 1.9 agc int r_from; /* low uid */ 72 1.9 agc int r_to; /* high uid */ 73 1.9 agc } range_t; 74 1.9 agc 75 1.125 mlelstv typedef struct rangelist_t { 76 1.125 mlelstv unsigned rl_rsize; /* size of range array */ 77 1.125 mlelstv unsigned rl_rc; /* # of ranges */ 78 1.125 mlelstv range_t *rl_rv; /* the ranges */ 79 1.125 mlelstv unsigned rl_defrc; /* # of ranges in defaults */ 80 1.125 mlelstv } rangelist_t; 81 1.125 mlelstv 82 1.125 mlelstv /* this struct encapsulates the user and group information */ 83 1.9 agc typedef struct user_t { 84 1.26 simonb int u_flags; /* see below */ 85 1.9 agc int u_uid; /* uid of user */ 86 1.64 agc char *u_password; /* encrypted password */ 87 1.64 agc char *u_comment; /* comment field */ 88 1.64 agc char *u_home; /* home directory */ 89 1.115 elad mode_t u_homeperm; /* permissions of home dir */ 90 1.64 agc char *u_primgrp; /* primary group */ 91 1.9 agc int u_groupc; /* # of secondary groups */ 92 1.53 agc const char *u_groupv[NGROUPS_MAX]; /* secondary groups */ 93 1.64 agc char *u_shell; /* user's shell */ 94 1.64 agc char *u_basedir; /* base directory for home */ 95 1.64 agc char *u_expire; /* when password will expire */ 96 1.64 agc char *u_inactive; /* when account will expire */ 97 1.64 agc char *u_skeldir; /* directory for startup files */ 98 1.64 agc char *u_class; /* login class */ 99 1.125 mlelstv rangelist_t u_r; /* list of ranges */ 100 1.9 agc unsigned u_defrc; /* # of ranges in defaults */ 101 1.9 agc int u_preserve; /* preserve uids on deletion */ 102 1.75 agc int u_allow_samba; /* allow trailing '$' for samba login names */ 103 1.82 christos int u_locked; /* user account lock */ 104 1.9 agc } user_t; 105 1.125 mlelstv #define u_rsize u_r.rl_rsize 106 1.125 mlelstv #define u_rc u_r.rl_rc 107 1.125 mlelstv #define u_rv u_r.rl_rv 108 1.125 mlelstv #define u_defrc u_r.rl_defrc 109 1.125 mlelstv 110 1.125 mlelstv /* this struct encapsulates the user and group information */ 111 1.125 mlelstv typedef struct group_t { 112 1.125 mlelstv rangelist_t g_r; /* list of ranges */ 113 1.125 mlelstv } group_t; 114 1.125 mlelstv #define g_rsize g_r.rl_rsize 115 1.125 mlelstv #define g_rc g_r.rl_rc 116 1.125 mlelstv #define g_rv g_r.rl_rv 117 1.125 mlelstv #define g_defrc g_r.rl_defrc 118 1.125 mlelstv 119 1.125 mlelstv typedef struct def_t { 120 1.125 mlelstv user_t user; 121 1.125 mlelstv group_t group; 122 1.125 mlelstv } def_t; 123 1.9 agc 124 1.26 simonb /* flags for which fields of the user_t replace the passwd entry */ 125 1.26 simonb enum { 126 1.26 simonb F_COMMENT = 0x0001, 127 1.26 simonb F_DUPUID = 0x0002, 128 1.26 simonb F_EXPIRE = 0x0004, 129 1.26 simonb F_GROUP = 0x0008, 130 1.26 simonb F_HOMEDIR = 0x0010, 131 1.26 simonb F_MKDIR = 0x0020, 132 1.26 simonb F_INACTIVE = 0x0040, 133 1.26 simonb F_PASSWORD = 0x0080, 134 1.26 simonb F_SECGROUP = 0x0100, 135 1.26 simonb F_SHELL = 0x0200, 136 1.26 simonb F_UID = 0x0400, 137 1.42 christos F_USERNAME = 0x0800, 138 1.42 christos F_CLASS = 0x1000 139 1.26 simonb }; 140 1.26 simonb 141 1.82 christos #define UNLOCK 0 142 1.82 christos #define LOCK 1 143 1.82 christos #define LOCKED "*LOCKED*" 144 1.82 christos 145 1.9 agc #ifndef DEF_GROUP 146 1.9 agc #define DEF_GROUP "users" 147 1.9 agc #endif 148 1.9 agc 149 1.9 agc #ifndef DEF_BASEDIR 150 1.9 agc #define DEF_BASEDIR "/home" 151 1.9 agc #endif 152 1.9 agc 153 1.9 agc #ifndef DEF_SKELDIR 154 1.9 agc #define DEF_SKELDIR "/etc/skel" 155 1.9 agc #endif 156 1.9 agc 157 1.9 agc #ifndef DEF_SHELL 158 1.117 pavel #define DEF_SHELL _PATH_BSHELL 159 1.9 agc #endif 160 1.9 agc 161 1.9 agc #ifndef DEF_COMMENT 162 1.9 agc #define DEF_COMMENT "" 163 1.9 agc #endif 164 1.9 agc 165 1.9 agc #ifndef DEF_LOWUID 166 1.9 agc #define DEF_LOWUID 1000 167 1.9 agc #endif 168 1.9 agc 169 1.9 agc #ifndef DEF_HIGHUID 170 1.9 agc #define DEF_HIGHUID 60000 171 1.9 agc #endif 172 1.9 agc 173 1.9 agc #ifndef DEF_INACTIVE 174 1.9 agc #define DEF_INACTIVE 0 175 1.9 agc #endif 176 1.9 agc 177 1.9 agc #ifndef DEF_EXPIRE 178 1.24 simonb #define DEF_EXPIRE NULL 179 1.9 agc #endif 180 1.9 agc 181 1.42 christos #ifndef DEF_CLASS 182 1.42 christos #define DEF_CLASS "" 183 1.42 christos #endif 184 1.42 christos 185 1.9 agc #ifndef WAITSECS 186 1.9 agc #define WAITSECS 10 187 1.9 agc #endif 188 1.9 agc 189 1.9 agc #ifndef NOBODY_UID 190 1.9 agc #define NOBODY_UID 32767 191 1.9 agc #endif 192 1.9 agc 193 1.115 elad #ifndef DEF_HOMEPERM 194 1.115 elad #define DEF_HOMEPERM 0755 195 1.115 elad #endif 196 1.115 elad 197 1.9 agc /* some useful constants */ 198 1.9 agc enum { 199 1.9 agc MaxShellNameLen = 256, 200 1.9 agc MaxFileNameLen = MAXPATHLEN, 201 1.92 christos MaxUserNameLen = LOGIN_NAME_MAX - 1, 202 1.9 agc MaxCommandLen = 2048, 203 1.9 agc MaxEntryLen = 2048, 204 1.60 itojun PasswordLength = 2048, 205 1.9 agc 206 1.65 agc DES_Len = 13, 207 1.9 agc }; 208 1.9 agc 209 1.52 grant #define UNSET_INACTIVE "Null (unset)" 210 1.9 agc #define UNSET_EXPIRY "Null (unset)" 211 1.9 agc 212 1.127 dholland static int asystem(const char *fmt, ...) __printflike(1, 2); 213 1.91 christos static int is_number(const char *); 214 1.91 christos static struct group *find_group_info(const char *); 215 1.91 christos static int verbose; 216 1.1 agc 217 1.82 christos static char * 218 1.82 christos skipspace(char *s) 219 1.82 christos { 220 1.93 agc for (; *s && isspace((unsigned char)*s) ; s++) { 221 1.93 agc } 222 1.82 christos return s; 223 1.82 christos } 224 1.82 christos 225 1.82 christos static int 226 1.82 christos check_numeric(const char *val, const char *name) 227 1.82 christos { 228 1.93 agc if (!is_number(val)) { 229 1.82 christos errx(EXIT_FAILURE, "When using [-%c %s], " 230 1.82 christos "the %s must be numeric", *name, name, name); 231 1.93 agc } 232 1.82 christos return atoi(val); 233 1.82 christos } 234 1.82 christos 235 1.111 christos /* resize *cpp appropriately then assign `n' chars of `s' to it */ 236 1.1 agc static void 237 1.53 agc memsave(char **cpp, const char *s, size_t n) 238 1.1 agc { 239 1.111 christos RENEW(char, *cpp, n + 1, exit(1)); 240 1.82 christos (void)memcpy(*cpp, s, n); 241 1.14 soren (*cpp)[n] = '\0'; 242 1.1 agc } 243 1.1 agc 244 1.1 agc /* a replacement for system(3) */ 245 1.1 agc static int 246 1.25 is asystem(const char *fmt, ...) 247 1.1 agc { 248 1.1 agc va_list vp; 249 1.1 agc char buf[MaxCommandLen]; 250 1.1 agc int ret; 251 1.1 agc 252 1.1 agc va_start(vp, fmt); 253 1.82 christos (void)vsnprintf(buf, sizeof(buf), fmt, vp); 254 1.1 agc va_end(vp); 255 1.1 agc if (verbose) { 256 1.82 christos (void)printf("Command: %s\n", buf); 257 1.1 agc } 258 1.128 dholland ret = system(buf); 259 1.128 dholland if (ret == -1) { 260 1.82 christos warn("Error running `%s'", buf); 261 1.128 dholland } else if (WIFSIGNALED(ret)) { 262 1.128 dholland warnx("Error running `%s': Signal %d", buf, WTERMSIG(ret)); 263 1.128 dholland } else if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0) { 264 1.128 dholland warnx("Error running `%s': Exit %d", buf, WEXITSTATUS(ret)); 265 1.1 agc } 266 1.1 agc return ret; 267 1.1 agc } 268 1.9 agc 269 1.26 simonb /* remove a users home directory, returning 1 for success (ie, no problems encountered) */ 270 1.26 simonb static int 271 1.96 christos removehomedir(struct passwd *pwp) 272 1.26 simonb { 273 1.26 simonb struct stat st; 274 1.26 simonb 275 1.26 simonb /* userid not root? */ 276 1.96 christos if (pwp->pw_uid == 0) { 277 1.96 christos warnx("Not deleting home directory `%s'; userid is 0", pwp->pw_dir); 278 1.26 simonb return 0; 279 1.26 simonb } 280 1.26 simonb 281 1.26 simonb /* directory exists (and is a directory!) */ 282 1.96 christos if (stat(pwp->pw_dir, &st) < 0) { 283 1.96 christos warn("Cannot access home directory `%s'", pwp->pw_dir); 284 1.26 simonb return 0; 285 1.26 simonb } 286 1.26 simonb if (!S_ISDIR(st.st_mode)) { 287 1.96 christos warnx("Home directory `%s' is not a directory", pwp->pw_dir); 288 1.26 simonb return 0; 289 1.26 simonb } 290 1.26 simonb 291 1.26 simonb /* userid matches directory owner? */ 292 1.96 christos if (st.st_uid != pwp->pw_uid) { 293 1.82 christos warnx("User `%s' doesn't own directory `%s', not removed", 294 1.96 christos pwp->pw_name, pwp->pw_dir); 295 1.26 simonb return 0; 296 1.26 simonb } 297 1.26 simonb 298 1.96 christos (void)seteuid(pwp->pw_uid); 299 1.26 simonb /* we add the "|| true" to keep asystem() quiet if there is a non-zero exit status. */ 300 1.129 dholland (void)asystem("%s -rf %s > /dev/null 2>&1 || true", _PATH_RM, 301 1.129 dholland pwp->pw_dir); 302 1.82 christos (void)seteuid(0); 303 1.133 wiz if (rmdir(pwp->pw_dir) < 0 && errno != ENOENT) { 304 1.96 christos warn("Unable to remove all files in `%s'", pwp->pw_dir); 305 1.26 simonb return 0; 306 1.26 simonb } 307 1.26 simonb return 1; 308 1.26 simonb } 309 1.26 simonb 310 1.9 agc /* return 1 if all of `s' is numeric */ 311 1.9 agc static int 312 1.81 agc is_number(const char *s) 313 1.9 agc { 314 1.9 agc for ( ; *s ; s++) { 315 1.67 jrf if (!isdigit((unsigned char) *s)) { 316 1.9 agc return 0; 317 1.9 agc } 318 1.9 agc } 319 1.9 agc return 1; 320 1.9 agc } 321 1.9 agc 322 1.9 agc /* 323 1.9 agc * check that the effective uid is 0 - called from funcs which will 324 1.9 agc * modify data and config files. 325 1.9 agc */ 326 1.9 agc static void 327 1.9 agc checkeuid(void) 328 1.9 agc { 329 1.9 agc if (geteuid() != 0) { 330 1.9 agc errx(EXIT_FAILURE, "Program must be run as root"); 331 1.9 agc } 332 1.9 agc } 333 1.9 agc 334 1.1 agc /* copy any dot files into the user's home directory */ 335 1.1 agc static int 336 1.115 elad copydotfiles(char *skeldir, int uid, int gid, char *dir, mode_t homeperm) 337 1.1 agc { 338 1.1 agc struct dirent *dp; 339 1.1 agc DIR *dirp; 340 1.1 agc int n; 341 1.1 agc 342 1.24 simonb if ((dirp = opendir(skeldir)) == NULL) { 343 1.82 christos warn("Can't open source . files dir `%s'", skeldir); 344 1.1 agc return 0; 345 1.1 agc } 346 1.24 simonb for (n = 0; (dp = readdir(dirp)) != NULL && n == 0 ; ) { 347 1.1 agc if (strcmp(dp->d_name, ".") == 0 || 348 1.1 agc strcmp(dp->d_name, "..") == 0) { 349 1.1 agc continue; 350 1.1 agc } 351 1.10 agc n = 1; 352 1.1 agc } 353 1.82 christos (void)closedir(dirp); 354 1.1 agc if (n == 0) { 355 1.1 agc warnx("No \"dot\" initialisation files found"); 356 1.1 agc } else { 357 1.96 christos (void)asystem("cd %s && %s -rw -pe %s . %s", 358 1.129 dholland skeldir, _PATH_PAX, (verbose) ? "-v" : "", dir); 359 1.1 agc } 360 1.129 dholland (void)asystem("%s -R -h %d:%d %s", _PATH_CHOWN, uid, gid, dir); 361 1.129 dholland (void)asystem("%s -R u+w %s", _PATH_CHMOD, dir); 362 1.115 elad #ifdef EXTENSIONS 363 1.129 dholland (void)asystem("%s 0%o %s", _PATH_CHMOD, homeperm, dir); 364 1.115 elad #endif 365 1.1 agc return n; 366 1.1 agc } 367 1.1 agc 368 1.1 agc /* create a group entry with gid `gid' */ 369 1.1 agc static int 370 1.53 agc creategid(char *group, int gid, const char *name) 371 1.1 agc { 372 1.1 agc struct stat st; 373 1.1 agc FILE *from; 374 1.1 agc FILE *to; 375 1.1 agc char buf[MaxEntryLen]; 376 1.1 agc char f[MaxFileNameLen]; 377 1.1 agc int fd; 378 1.1 agc int cc; 379 1.1 agc 380 1.24 simonb if (getgrnam(group) != NULL) { 381 1.96 christos warnx("Can't create group `%s': already exists", group); 382 1.12 agc return 0; 383 1.12 agc } 384 1.24 simonb if ((from = fopen(_PATH_GROUP, "r")) == NULL) { 385 1.96 christos warn("Can't create group `%s': can't open `%s'", name, 386 1.82 christos _PATH_GROUP); 387 1.1 agc return 0; 388 1.1 agc } 389 1.1 agc if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) { 390 1.82 christos warn("Can't lock `%s'", _PATH_GROUP); 391 1.107 joerg (void)fclose(from); 392 1.96 christos return 0; 393 1.1 agc } 394 1.82 christos (void)fstat(fileno(from), &st); 395 1.82 christos (void)snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP); 396 1.1 agc if ((fd = mkstemp(f)) < 0) { 397 1.96 christos warn("Can't create group `%s': mkstemp failed", group); 398 1.82 christos (void)fclose(from); 399 1.1 agc return 0; 400 1.1 agc } 401 1.24 simonb if ((to = fdopen(fd, "w")) == NULL) { 402 1.96 christos warn("Can't create group `%s': fdopen `%s' failed", 403 1.96 christos group, f); 404 1.82 christos (void)fclose(from); 405 1.82 christos (void)close(fd); 406 1.82 christos (void)unlink(f); 407 1.1 agc return 0; 408 1.1 agc } 409 1.1 agc while ((cc = fread(buf, sizeof(char), sizeof(buf), from)) > 0) { 410 1.1 agc if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) { 411 1.96 christos warn("Can't create group `%s': short write to `%s'", 412 1.96 christos group, f); 413 1.82 christos (void)fclose(from); 414 1.82 christos (void)close(fd); 415 1.82 christos (void)unlink(f); 416 1.1 agc return 0; 417 1.1 agc } 418 1.1 agc } 419 1.82 christos (void)fprintf(to, "%s:*:%d:%s\n", group, gid, name); 420 1.82 christos (void)fclose(from); 421 1.82 christos (void)fclose(to); 422 1.20 agc if (rename(f, _PATH_GROUP) < 0) { 423 1.96 christos warn("Can't create group `%s': can't rename `%s' to `%s'", 424 1.96 christos group, f, _PATH_GROUP); 425 1.82 christos (void)unlink(f); 426 1.1 agc return 0; 427 1.1 agc } 428 1.82 christos (void)chmod(_PATH_GROUP, st.st_mode & 07777); 429 1.82 christos syslog(LOG_INFO, "New group added: name=%s, gid=%d", group, gid); 430 1.1 agc return 1; 431 1.1 agc } 432 1.1 agc 433 1.1 agc /* modify the group entry with name `group' to be newent */ 434 1.1 agc static int 435 1.1 agc modify_gid(char *group, char *newent) 436 1.1 agc { 437 1.1 agc struct stat st; 438 1.1 agc FILE *from; 439 1.1 agc FILE *to; 440 1.1 agc char buf[MaxEntryLen]; 441 1.1 agc char f[MaxFileNameLen]; 442 1.1 agc char *colon; 443 1.1 agc int groupc; 444 1.1 agc int entc; 445 1.1 agc int fd; 446 1.1 agc int cc; 447 1.1 agc 448 1.24 simonb if ((from = fopen(_PATH_GROUP, "r")) == NULL) { 449 1.96 christos warn("Can't modify group `%s': can't open `%s'", 450 1.82 christos group, _PATH_GROUP); 451 1.1 agc return 0; 452 1.1 agc } 453 1.1 agc if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) { 454 1.96 christos warn("Can't modify group `%s': can't lock `%s'", 455 1.96 christos group, _PATH_GROUP); 456 1.107 joerg (void)fclose(from); 457 1.96 christos return 0; 458 1.1 agc } 459 1.82 christos (void)fstat(fileno(from), &st); 460 1.82 christos (void)snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP); 461 1.1 agc if ((fd = mkstemp(f)) < 0) { 462 1.96 christos warn("Can't modify group `%s': mkstemp failed", group); 463 1.82 christos (void)fclose(from); 464 1.1 agc return 0; 465 1.1 agc } 466 1.24 simonb if ((to = fdopen(fd, "w")) == NULL) { 467 1.96 christos warn("Can't modify group `%s': fdopen `%s' failed", group, f); 468 1.82 christos (void)fclose(from); 469 1.82 christos (void)close(fd); 470 1.82 christos (void)unlink(f); 471 1.1 agc return 0; 472 1.1 agc } 473 1.1 agc groupc = strlen(group); 474 1.15 soren while (fgets(buf, sizeof(buf), from) != NULL) { 475 1.12 agc cc = strlen(buf); 476 1.15 soren if ((colon = strchr(buf, ':')) == NULL) { 477 1.82 christos warnx("Badly formed entry `%s'", buf); 478 1.1 agc continue; 479 1.1 agc } 480 1.1 agc entc = (int)(colon - buf); 481 1.82 christos if (entc == groupc && 482 1.82 christos strncmp(group, buf, (unsigned) entc) == 0) { 483 1.15 soren if (newent == NULL) { 484 1.91 christos struct group *grp_rm; 485 1.91 christos struct passwd *user_pwd; 486 1.91 christos 487 1.91 christos /* 488 1.91 christos * Check that the group being removed 489 1.91 christos * isn't any user's Primary group. Just 490 1.91 christos * warn if it is. This could cause problems 491 1.91 christos * if the group GID was reused for a 492 1.91 christos * different purpose. 493 1.91 christos */ 494 1.91 christos 495 1.91 christos grp_rm = find_group_info(group); 496 1.91 christos while ((user_pwd = getpwent()) != NULL) { 497 1.91 christos if (user_pwd->pw_gid == grp_rm->gr_gid) { 498 1.91 christos warnx("Warning: group `%s'(%d)" 499 1.91 christos " is the primary group of" 500 1.91 christos " `%s'. Use caution if you" 501 1.91 christos " later add this GID.", 502 1.91 christos grp_rm->gr_name, 503 1.91 christos grp_rm->gr_gid, user_pwd->pw_name); 504 1.91 christos } 505 1.91 christos } 506 1.91 christos endpwent(); 507 1.1 agc continue; 508 1.1 agc } else { 509 1.1 agc cc = strlen(newent); 510 1.82 christos (void)strlcpy(buf, newent, sizeof(buf)); 511 1.1 agc } 512 1.1 agc } 513 1.1 agc if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) { 514 1.96 christos warn("Can't modify group `%s': short write to `%s'", 515 1.96 christos group, f); 516 1.82 christos (void)fclose(from); 517 1.82 christos (void)close(fd); 518 1.82 christos (void)unlink(f); 519 1.1 agc return 0; 520 1.1 agc } 521 1.1 agc } 522 1.82 christos (void)fclose(from); 523 1.82 christos (void)fclose(to); 524 1.20 agc if (rename(f, _PATH_GROUP) < 0) { 525 1.96 christos warn("Can't modify group `%s': can't rename `%s' to `%s'", 526 1.96 christos group, f, _PATH_GROUP); 527 1.82 christos (void)unlink(f); 528 1.1 agc return 0; 529 1.1 agc } 530 1.82 christos (void)chmod(_PATH_GROUP, st.st_mode & 07777); 531 1.59 agc if (newent == NULL) { 532 1.59 agc syslog(LOG_INFO, "group deleted: name=%s", group); 533 1.59 agc } else { 534 1.59 agc syslog(LOG_INFO, "group information modified: name=%s", group); 535 1.59 agc } 536 1.1 agc return 1; 537 1.1 agc } 538 1.1 agc 539 1.23 agc /* modify the group entries for all `groups', by adding `user' */ 540 1.23 agc static int 541 1.53 agc append_group(char *user, int ngroups, const char **groups) 542 1.23 agc { 543 1.23 agc struct group *grp; 544 1.23 agc struct stat st; 545 1.23 agc FILE *from; 546 1.23 agc FILE *to; 547 1.23 agc char buf[MaxEntryLen]; 548 1.23 agc char f[MaxFileNameLen]; 549 1.23 agc char *colon; 550 1.23 agc int groupc; 551 1.23 agc int entc; 552 1.23 agc int fd; 553 1.23 agc int nc; 554 1.23 agc int cc; 555 1.23 agc int i; 556 1.23 agc int j; 557 1.23 agc 558 1.23 agc for (i = 0 ; i < ngroups ; i++) { 559 1.27 simonb if ((grp = getgrnam(groups[i])) == NULL) { 560 1.82 christos warnx("Can't append group `%s' for user `%s'", 561 1.82 christos groups[i], user); 562 1.27 simonb } else { 563 1.23 agc for (j = 0 ; grp->gr_mem[j] ; j++) { 564 1.23 agc if (strcmp(user, grp->gr_mem[j]) == 0) { 565 1.23 agc /* already in it */ 566 1.23 agc groups[i] = ""; 567 1.23 agc } 568 1.23 agc } 569 1.23 agc } 570 1.23 agc } 571 1.24 simonb if ((from = fopen(_PATH_GROUP, "r")) == NULL) { 572 1.96 christos warn("Can't append group(s) for `%s': can't open `%s'", 573 1.82 christos user, _PATH_GROUP); 574 1.23 agc return 0; 575 1.23 agc } 576 1.23 agc if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) { 577 1.96 christos warn("Can't append group(s) for `%s': can't lock `%s'", 578 1.96 christos user, _PATH_GROUP); 579 1.107 joerg (void)fclose(from); 580 1.96 christos return 0; 581 1.23 agc } 582 1.82 christos (void)fstat(fileno(from), &st); 583 1.82 christos (void)snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP); 584 1.23 agc if ((fd = mkstemp(f)) < 0) { 585 1.96 christos warn("Can't append group(s) for `%s': mkstemp failed", 586 1.96 christos user); 587 1.82 christos (void)fclose(from); 588 1.23 agc return 0; 589 1.23 agc } 590 1.24 simonb if ((to = fdopen(fd, "w")) == NULL) { 591 1.96 christos warn("Can't append group(s) for `%s': fdopen `%s' failed", 592 1.96 christos user, f); 593 1.82 christos (void)fclose(from); 594 1.82 christos (void)close(fd); 595 1.82 christos (void)unlink(f); 596 1.23 agc return 0; 597 1.23 agc } 598 1.23 agc while (fgets(buf, sizeof(buf), from) != NULL) { 599 1.23 agc cc = strlen(buf); 600 1.23 agc if ((colon = strchr(buf, ':')) == NULL) { 601 1.82 christos warnx("Badly formed entry `%s'", buf); 602 1.23 agc continue; 603 1.23 agc } 604 1.23 agc entc = (int)(colon - buf); 605 1.23 agc for (i = 0 ; i < ngroups ; i++) { 606 1.23 agc if ((groupc = strlen(groups[i])) == 0) { 607 1.23 agc continue; 608 1.23 agc } 609 1.82 christos if (entc == groupc && 610 1.82 christos strncmp(groups[i], buf, (unsigned) entc) == 0) { 611 1.23 agc if ((nc = snprintf(&buf[cc - 1], 612 1.82 christos sizeof(buf) - cc + 1, "%s%s\n", 613 1.93 agc (buf[cc - 2] == ':') ? "" : ",", user)) < 0) { 614 1.82 christos warnx("Warning: group `%s' " 615 1.82 christos "entry too long", groups[i]); 616 1.93 agc } 617 1.23 agc cc += nc - 1; 618 1.23 agc } 619 1.23 agc } 620 1.23 agc if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) { 621 1.96 christos warn("Can't append group(s) for `%s':" 622 1.96 christos " short write to `%s'", user, f); 623 1.82 christos (void)fclose(from); 624 1.82 christos (void)close(fd); 625 1.82 christos (void)unlink(f); 626 1.23 agc return 0; 627 1.23 agc } 628 1.23 agc } 629 1.82 christos (void)fclose(from); 630 1.82 christos (void)fclose(to); 631 1.23 agc if (rename(f, _PATH_GROUP) < 0) { 632 1.96 christos warn("Can't append group(s) for `%s': " 633 1.96 christos "can't rename `%s' to `%s'", user, f, _PATH_GROUP); 634 1.82 christos (void)unlink(f); 635 1.23 agc return 0; 636 1.23 agc } 637 1.82 christos (void)chmod(_PATH_GROUP, st.st_mode & 07777); 638 1.23 agc return 1; 639 1.23 agc } 640 1.23 agc 641 1.79 agc /* the valid characters for login and group names */ 642 1.79 agc #define VALID_CHAR(c) (isalnum(c) || (c) == '.' || (c) == '_' || (c) == '-') 643 1.79 agc 644 1.1 agc /* return 1 if `login' is a valid login name */ 645 1.1 agc static int 646 1.75 agc valid_login(char *login_name, int allow_samba) 647 1.1 agc { 648 1.67 jrf unsigned char *cp; 649 1.1 agc 650 1.92 christos /* First character of a login name cannot be '-'. */ 651 1.93 agc if (*login_name == '-') { 652 1.92 christos return 0; 653 1.93 agc } 654 1.66 agc if (strlen(login_name) >= LOGIN_NAME_MAX) { 655 1.66 agc return 0; 656 1.66 agc } 657 1.82 christos for (cp = (unsigned char *)login_name ; *cp ; cp++) { 658 1.79 agc if (!VALID_CHAR(*cp)) { 659 1.75 agc #ifdef EXTENSIONS 660 1.75 agc /* check for a trailing '$' in a Samba user name */ 661 1.75 agc if (allow_samba && *cp == '$' && *(cp + 1) == 0x0) { 662 1.75 agc return 1; 663 1.96 christos } 664 1.75 agc #endif 665 1.1 agc return 0; 666 1.1 agc } 667 1.1 agc } 668 1.1 agc return 1; 669 1.1 agc } 670 1.1 agc 671 1.1 agc /* return 1 if `group' is a valid group name */ 672 1.1 agc static int 673 1.1 agc valid_group(char *group) 674 1.1 agc { 675 1.67 jrf unsigned char *cp; 676 1.1 agc 677 1.82 christos for (cp = (unsigned char *)group; *cp; cp++) { 678 1.79 agc if (!VALID_CHAR(*cp)) { 679 1.1 agc return 0; 680 1.1 agc } 681 1.1 agc } 682 1.1 agc return 1; 683 1.1 agc } 684 1.1 agc 685 1.1 agc /* find the next gid in the range lo .. hi */ 686 1.1 agc static int 687 1.1 agc getnextgid(int *gidp, int lo, int hi) 688 1.1 agc { 689 1.1 agc for (*gidp = lo ; *gidp < hi ; *gidp += 1) { 690 1.24 simonb if (getgrgid((gid_t)*gidp) == NULL) { 691 1.1 agc return 1; 692 1.1 agc } 693 1.1 agc } 694 1.1 agc return 0; 695 1.1 agc } 696 1.1 agc 697 1.1 agc #ifdef EXTENSIONS 698 1.1 agc /* save a range of uids */ 699 1.1 agc static int 700 1.125 mlelstv save_range(rangelist_t *rlp, char *cp) 701 1.1 agc { 702 1.1 agc int from; 703 1.1 agc int to; 704 1.1 agc int i; 705 1.1 agc 706 1.125 mlelstv if (rlp->rl_rsize == 0) { 707 1.125 mlelstv rlp->rl_rsize = 32; 708 1.125 mlelstv NEWARRAY(range_t, rlp->rl_rv, rlp->rl_rsize, return(0)); 709 1.125 mlelstv } else if (rlp->rl_rc == rlp->rl_rsize) { 710 1.125 mlelstv rlp->rl_rsize *= 2; 711 1.125 mlelstv RENEW(range_t, rlp->rl_rv, rlp->rl_rsize, return(0)); 712 1.125 mlelstv } 713 1.125 mlelstv if (rlp->rl_rv && sscanf(cp, "%d..%d", &from, &to) == 2) { 714 1.125 mlelstv for (i = rlp->rl_defrc ; i < rlp->rl_rc ; i++) { 715 1.125 mlelstv if (rlp->rl_rv[i].r_from == from && 716 1.125 mlelstv rlp->rl_rv[i].r_to == to) { 717 1.1 agc break; 718 1.1 agc } 719 1.1 agc } 720 1.125 mlelstv if (i == rlp->rl_rc) { 721 1.125 mlelstv rlp->rl_rv[rlp->rl_rc].r_from = from; 722 1.125 mlelstv rlp->rl_rv[rlp->rl_rc].r_to = to; 723 1.125 mlelstv rlp->rl_rc += 1; 724 1.1 agc } 725 1.1 agc } else { 726 1.1 agc warnx("Bad range `%s'", cp); 727 1.1 agc return 0; 728 1.1 agc } 729 1.1 agc return 1; 730 1.1 agc } 731 1.1 agc #endif 732 1.1 agc 733 1.1 agc /* set the defaults in the defaults file */ 734 1.1 agc static int 735 1.1 agc setdefaults(user_t *up) 736 1.1 agc { 737 1.1 agc char template[MaxFileNameLen]; 738 1.1 agc FILE *fp; 739 1.1 agc int ret; 740 1.1 agc int fd; 741 1.42 christos #ifdef EXTENSIONS 742 1.1 agc int i; 743 1.42 christos #endif 744 1.1 agc 745 1.96 christos (void)snprintf(template, sizeof(template), "%s.XXXXXX", 746 1.94 rpaulo _PATH_USERMGMT_CONF); 747 1.1 agc if ((fd = mkstemp(template)) < 0) { 748 1.96 christos warn("Can't set defaults: can't mkstemp `%s' for writing", 749 1.96 christos _PATH_USERMGMT_CONF); 750 1.1 agc return 0; 751 1.1 agc } 752 1.24 simonb if ((fp = fdopen(fd, "w")) == NULL) { 753 1.96 christos warn("Can't set defaults: can't fdopen `%s' for writing", 754 1.96 christos _PATH_USERMGMT_CONF); 755 1.1 agc return 0; 756 1.1 agc } 757 1.1 agc ret = 1; 758 1.1 agc if (fprintf(fp, "group\t\t%s\n", up->u_primgrp) <= 0 || 759 1.1 agc fprintf(fp, "base_dir\t%s\n", up->u_basedir) <= 0 || 760 1.1 agc fprintf(fp, "skel_dir\t%s\n", up->u_skeldir) <= 0 || 761 1.1 agc fprintf(fp, "shell\t\t%s\n", up->u_shell) <= 0 || 762 1.42 christos #ifdef EXTENSIONS 763 1.42 christos fprintf(fp, "class\t\t%s\n", up->u_class) <= 0 || 764 1.115 elad fprintf(fp, "homeperm\t0%o\n", up->u_homeperm) <= 0 || 765 1.42 christos #endif 766 1.96 christos fprintf(fp, "inactive\t%s\n", (up->u_inactive == NULL) ? 767 1.82 christos UNSET_INACTIVE : up->u_inactive) <= 0 || 768 1.82 christos fprintf(fp, "expire\t\t%s\n", (up->u_expire == NULL) ? 769 1.82 christos UNSET_EXPIRY : up->u_expire) <= 0 || 770 1.82 christos fprintf(fp, "preserve\t%s\n", (up->u_preserve == 0) ? 771 1.82 christos "false" : "true") <= 0) { 772 1.94 rpaulo warn("Can't write to `%s'", _PATH_USERMGMT_CONF); 773 1.1 agc ret = 0; 774 1.1 agc } 775 1.1 agc #ifdef EXTENSIONS 776 1.82 christos for (i = (up->u_defrc != up->u_rc) ? up->u_defrc : 0; 777 1.82 christos i < up->u_rc ; i++) { 778 1.82 christos if (fprintf(fp, "range\t\t%d..%d\n", up->u_rv[i].r_from, 779 1.82 christos up->u_rv[i].r_to) <= 0) { 780 1.96 christos warn("Can't set defaults: can't write to `%s'", 781 1.96 christos _PATH_USERMGMT_CONF); 782 1.1 agc ret = 0; 783 1.1 agc } 784 1.1 agc } 785 1.1 agc #endif 786 1.82 christos (void)fclose(fp); 787 1.1 agc if (ret) { 788 1.94 rpaulo ret = ((rename(template, _PATH_USERMGMT_CONF) == 0) && 789 1.94 rpaulo (chmod(_PATH_USERMGMT_CONF, 0644) == 0)); 790 1.1 agc } 791 1.1 agc return ret; 792 1.1 agc } 793 1.1 agc 794 1.1 agc /* read the defaults file */ 795 1.1 agc static void 796 1.125 mlelstv read_defaults(def_t *dp) 797 1.1 agc { 798 1.1 agc struct stat st; 799 1.1 agc size_t lineno; 800 1.1 agc size_t len; 801 1.1 agc FILE *fp; 802 1.82 christos char *cp; 803 1.82 christos char *s; 804 1.125 mlelstv user_t *up = &dp->user; 805 1.125 mlelstv group_t *gp = &dp->group; 806 1.125 mlelstv 807 1.125 mlelstv (void)memset(dp, 0, sizeof(*dp)); 808 1.1 agc 809 1.1 agc memsave(&up->u_primgrp, DEF_GROUP, strlen(DEF_GROUP)); 810 1.1 agc memsave(&up->u_basedir, DEF_BASEDIR, strlen(DEF_BASEDIR)); 811 1.1 agc memsave(&up->u_skeldir, DEF_SKELDIR, strlen(DEF_SKELDIR)); 812 1.1 agc memsave(&up->u_shell, DEF_SHELL, strlen(DEF_SHELL)); 813 1.1 agc memsave(&up->u_comment, DEF_COMMENT, strlen(DEF_COMMENT)); 814 1.42 christos #ifdef EXTENSIONS 815 1.42 christos memsave(&up->u_class, DEF_CLASS, strlen(DEF_CLASS)); 816 1.42 christos #endif 817 1.1 agc up->u_rsize = 16; 818 1.49 agc up->u_defrc = 0; 819 1.1 agc NEWARRAY(range_t, up->u_rv, up->u_rsize, exit(1)); 820 1.9 agc up->u_inactive = DEF_INACTIVE; 821 1.1 agc up->u_expire = DEF_EXPIRE; 822 1.132 zafer up->u_homeperm = DEF_HOMEPERM; 823 1.125 mlelstv gp->g_rsize = 16; 824 1.125 mlelstv gp->g_defrc = 0; 825 1.125 mlelstv NEWARRAY(range_t, gp->g_rv, gp->g_rsize, exit(1)); 826 1.94 rpaulo if ((fp = fopen(_PATH_USERMGMT_CONF, "r")) == NULL) { 827 1.94 rpaulo if (stat(_PATH_USERMGMT_CONF, &st) < 0 && !setdefaults(up)) { 828 1.94 rpaulo warn("Can't create `%s' defaults file", 829 1.94 rpaulo _PATH_USERMGMT_CONF); 830 1.1 agc } 831 1.94 rpaulo fp = fopen(_PATH_USERMGMT_CONF, "r"); 832 1.1 agc } 833 1.24 simonb if (fp != NULL) { 834 1.15 soren while ((s = fparseln(fp, &len, &lineno, NULL, 0)) != NULL) { 835 1.1 agc if (strncmp(s, "group", 5) == 0) { 836 1.82 christos cp = skipspace(s + 5); 837 1.82 christos memsave(&up->u_primgrp, (char *)cp, strlen(cp)); 838 1.1 agc } else if (strncmp(s, "base_dir", 8) == 0) { 839 1.82 christos cp = skipspace(s + 8); 840 1.82 christos memsave(&up->u_basedir, (char *)cp, strlen(cp)); 841 1.1 agc } else if (strncmp(s, "skel_dir", 8) == 0) { 842 1.82 christos cp = skipspace(s + 8); 843 1.82 christos memsave(&up->u_skeldir, (char *)cp, strlen(cp)); 844 1.1 agc } else if (strncmp(s, "shell", 5) == 0) { 845 1.82 christos cp = skipspace(s + 5); 846 1.1 agc memsave(&up->u_shell, cp, strlen(cp)); 847 1.42 christos #ifdef EXTENSIONS 848 1.82 christos } else if (strncmp((char *)s, "class", 5) == 0) { 849 1.82 christos cp = skipspace(s + 5); 850 1.42 christos memsave(&up->u_class, cp, strlen(cp)); 851 1.42 christos #endif 852 1.115 elad #ifdef EXTENSIONS 853 1.115 elad } else if (strncmp(s, "homeperm", 8) == 0) { 854 1.115 elad for (cp = s + 8; *cp && 855 1.115 elad isspace((unsigned char)*cp); cp++) 856 1.115 elad ; 857 1.115 elad up->u_homeperm = strtoul(cp, NULL, 8); 858 1.115 elad #endif 859 1.1 agc } else if (strncmp(s, "inactive", 8) == 0) { 860 1.82 christos cp = skipspace(s + 8); 861 1.52 grant if (strcmp(cp, UNSET_INACTIVE) == 0) { 862 1.52 grant if (up->u_inactive) { 863 1.52 grant FREE(up->u_inactive); 864 1.52 grant } 865 1.52 grant up->u_inactive = NULL; 866 1.52 grant } else { 867 1.52 grant memsave(&up->u_inactive, cp, strlen(cp)); 868 1.52 grant } 869 1.1 agc #ifdef EXTENSIONS 870 1.1 agc } else if (strncmp(s, "range", 5) == 0) { 871 1.82 christos cp = skipspace(s + 5); 872 1.125 mlelstv (void)save_range(&up->u_r, cp); 873 1.1 agc #endif 874 1.1 agc #ifdef EXTENSIONS 875 1.1 agc } else if (strncmp(s, "preserve", 8) == 0) { 876 1.82 christos cp = skipspace(s + 8); 877 1.96 christos up->u_preserve = 878 1.82 christos (strncmp(cp, "true", 4) == 0) ? 1 : 879 1.82 christos (strncmp(cp, "yes", 3) == 0) ? 1 : atoi(cp); 880 1.1 agc #endif 881 1.1 agc } else if (strncmp(s, "expire", 6) == 0) { 882 1.82 christos cp = skipspace(s + 6); 883 1.1 agc if (strcmp(cp, UNSET_EXPIRY) == 0) { 884 1.1 agc if (up->u_expire) { 885 1.1 agc FREE(up->u_expire); 886 1.1 agc } 887 1.15 soren up->u_expire = NULL; 888 1.1 agc } else { 889 1.1 agc memsave(&up->u_expire, cp, strlen(cp)); 890 1.1 agc } 891 1.125 mlelstv #ifdef EXTENSIONS 892 1.125 mlelstv } else if (strncmp(s, "gid_range", 9) == 0) { 893 1.125 mlelstv cp = skipspace(s + 9); 894 1.125 mlelstv (void)save_range(&gp->g_r, cp); 895 1.125 mlelstv #endif 896 1.1 agc } 897 1.82 christos (void)free(s); 898 1.1 agc } 899 1.82 christos (void)fclose(fp); 900 1.1 agc } 901 1.9 agc if (up->u_rc == 0) { 902 1.9 agc up->u_rv[up->u_rc].r_from = DEF_LOWUID; 903 1.9 agc up->u_rv[up->u_rc].r_to = DEF_HIGHUID; 904 1.9 agc up->u_rc += 1; 905 1.9 agc } 906 1.9 agc up->u_defrc = up->u_rc; 907 1.1 agc } 908 1.1 agc 909 1.1 agc /* return the next valid unused uid */ 910 1.1 agc static int 911 1.1 agc getnextuid(int sync_uid_gid, int *uid, int low_uid, int high_uid) 912 1.1 agc { 913 1.1 agc for (*uid = low_uid ; *uid <= high_uid ; (*uid)++) { 914 1.24 simonb if (getpwuid((uid_t)(*uid)) == NULL && *uid != NOBODY_UID) { 915 1.1 agc if (sync_uid_gid) { 916 1.24 simonb if (getgrgid((gid_t)(*uid)) == NULL) { 917 1.1 agc return 1; 918 1.1 agc } 919 1.1 agc } else { 920 1.1 agc return 1; 921 1.1 agc } 922 1.1 agc } 923 1.1 agc } 924 1.1 agc return 0; 925 1.1 agc } 926 1.1 agc 927 1.61 agc /* structure which defines a password type */ 928 1.61 agc typedef struct passwd_type_t { 929 1.62 agc const char *type; /* optional type descriptor */ 930 1.82 christos size_t desc_length; /* length of type descriptor */ 931 1.82 christos size_t length; /* length of password */ 932 1.62 agc const char *regex; /* regexp to output the password */ 933 1.82 christos size_t re_sub; /* subscript of regexp to use */ 934 1.61 agc } passwd_type_t; 935 1.61 agc 936 1.61 agc static passwd_type_t passwd_types[] = { 937 1.134 nia { "$argon2i", 8, SIZE_MAX, "\\$[^$]+\\$[^$]+\\$[^$]+\\$(.*)", 1 }, /* Argon2i */ 938 1.134 nia { "$argon2id", 9, SIZE_MAX, "\\$[^$]+\\$[^$]+\\$[^$]+\\$(.*)", 1 }, /* Argon2id */ 939 1.135 nia { "$argon2d", 8, SIZE_MAX, "\\$[^$]+\\$[^$]+\\$[^$]+\\$(.*)", 1 }, /* Argon2d */ 940 1.76 agc { "$sha1", 5, 28, "\\$[^$]+\\$[^$]+\\$[^$]+\\$(.*)", 1 }, /* SHA1 */ 941 1.124 hubertf { "$2a", 3, 53, "\\$[^$]+\\$[^$]+\\$(.*)", 1 }, /* Blowfish */ 942 1.62 agc { "$1", 2, 34, NULL, 0 }, /* MD5 */ 943 1.65 agc { "", 0, DES_Len,NULL, 0 }, /* standard DES */ 944 1.82 christos { NULL, (size_t)~0, (size_t)~0, NULL, 0 } 945 1.82 christos /* none - terminate search */ 946 1.61 agc }; 947 1.61 agc 948 1.62 agc /* return non-zero if it's a valid password - check length for cipher type */ 949 1.61 agc static int 950 1.62 agc valid_password_length(char *newpasswd) 951 1.61 agc { 952 1.61 agc passwd_type_t *pwtp; 953 1.62 agc regmatch_t matchv[10]; 954 1.62 agc regex_t r; 955 1.61 agc 956 1.82 christos for (pwtp = passwd_types; pwtp->desc_length != (size_t)~0; pwtp++) { 957 1.61 agc if (strncmp(newpasswd, pwtp->type, pwtp->desc_length) == 0) { 958 1.62 agc if (pwtp->regex == NULL) { 959 1.134 nia return pwtp->length == SIZE_MAX || 960 1.134 nia strlen(newpasswd) == pwtp->length; 961 1.62 agc } 962 1.82 christos (void)regcomp(&r, pwtp->regex, REG_EXTENDED); 963 1.62 agc if (regexec(&r, newpasswd, 10, matchv, 0) == 0) { 964 1.62 agc regfree(&r); 965 1.134 nia return pwtp->length == SIZE_MAX || 966 1.134 nia (int)(matchv[pwtp->re_sub].rm_eo - 967 1.134 nia matchv[pwtp->re_sub].rm_so) == 968 1.134 nia pwtp->length; 969 1.62 agc } 970 1.62 agc regfree(&r); 971 1.61 agc } 972 1.61 agc } 973 1.62 agc return 0; 974 1.61 agc } 975 1.61 agc 976 1.82 christos #ifdef EXTENSIONS 977 1.102 agc /* return 1 if `class' is a valid login class */ 978 1.82 christos static int 979 1.82 christos valid_class(char *class) 980 1.82 christos { 981 1.82 christos login_cap_t *lc; 982 1.96 christos 983 1.93 agc if (class == NULL || *class == '\0') { 984 1.86 christos return 1; 985 1.93 agc } 986 1.82 christos /* 987 1.82 christos * Check if /etc/login.conf exists. login_getclass() will 988 1.82 christos * return 1 due to it not existing, so not informing the 989 1.82 christos * user the actual login class does not exist. 990 1.82 christos */ 991 1.82 christos 992 1.129 dholland if (access(_PATH_LOGINCONF, R_OK) == -1) { 993 1.84 christos warn("Access failed for `%s'; will not validate class `%s'", 994 1.129 dholland _PATH_LOGINCONF, class); 995 1.84 christos return 1; 996 1.84 christos } 997 1.82 christos 998 1.86 christos if ((lc = login_getclass(class)) != NULL) { 999 1.82 christos login_close(lc); 1000 1.86 christos return 1; 1001 1.86 christos } 1002 1.86 christos return 0; 1003 1.82 christos } 1004 1.99 christos 1005 1.102 agc /* return 1 if the `shellname' is a valid user shell */ 1006 1.99 christos static int 1007 1.99 christos valid_shell(const char *shellname) 1008 1.99 christos { 1009 1.99 christos char *shellp; 1010 1.99 christos 1011 1.99 christos if (access(_PATH_SHELLS, R_OK) == -1) { 1012 1.99 christos /* Don't exit */ 1013 1.99 christos warn("Access failed for `%s'; will not validate shell `%s'", 1014 1.99 christos _PATH_SHELLS, shellname); 1015 1.99 christos return 1; 1016 1.99 christos } 1017 1.99 christos 1018 1.102 agc /* if nologin is used as a shell, consider it a valid shell */ 1019 1.129 dholland if (strcmp(shellname, _PATH_SBIN_NOLOGIN) == 0) 1020 1.102 agc return 1; 1021 1.100 christos 1022 1.104 christos while ((shellp = getusershell()) != NULL) 1023 1.104 christos if (strcmp(shellp, shellname) == 0) 1024 1.99 christos return 1; 1025 1.99 christos 1026 1.104 christos warnx("Shell `%s' not found in `%s'", shellname, _PATH_SHELLS); 1027 1.104 christos 1028 1.104 christos return access(shellname, X_OK) != -1; 1029 1.99 christos } 1030 1.82 christos #endif 1031 1.82 christos 1032 1.64 agc /* look for a valid time, return 0 if it was specified but bad */ 1033 1.64 agc static int 1034 1.64 agc scantime(time_t *tp, char *s) 1035 1.64 agc { 1036 1.64 agc struct tm tm; 1037 1.87 christos char *ep; 1038 1.88 he long val; 1039 1.64 agc 1040 1.64 agc *tp = 0; 1041 1.64 agc if (s != NULL) { 1042 1.82 christos (void)memset(&tm, 0, sizeof(tm)); 1043 1.64 agc if (strptime(s, "%c", &tm) != NULL) { 1044 1.64 agc *tp = mktime(&tm); 1045 1.93 agc return (*tp == -1) ? 0 : 1; 1046 1.64 agc } else if (strptime(s, "%B %d %Y", &tm) != NULL) { 1047 1.64 agc *tp = mktime(&tm); 1048 1.93 agc return (*tp == -1) ? 0 : 1; 1049 1.64 agc } else { 1050 1.88 he errno = 0; 1051 1.88 he *tp = val = strtol(s, &ep, 10); 1052 1.88 he if (*ep != '\0' || *tp < -1 || errno == ERANGE) { 1053 1.87 christos *tp = 0; 1054 1.87 christos return 0; 1055 1.87 christos } 1056 1.93 agc if (*tp != val) { 1057 1.88 he return 0; 1058 1.93 agc } 1059 1.64 agc } 1060 1.64 agc } 1061 1.64 agc return 1; 1062 1.64 agc } 1063 1.64 agc 1064 1.1 agc /* add a user */ 1065 1.1 agc static int 1066 1.53 agc adduser(char *login_name, user_t *up) 1067 1.1 agc { 1068 1.1 agc struct group *grp; 1069 1.1 agc struct stat st; 1070 1.1 agc time_t expire; 1071 1.52 grant time_t inactive; 1072 1.1 agc char password[PasswordLength + 1]; 1073 1.1 agc char home[MaxFileNameLen]; 1074 1.1 agc char buf[MaxFileNameLen]; 1075 1.1 agc int sync_uid_gid; 1076 1.1 agc int masterfd; 1077 1.1 agc int ptmpfd; 1078 1.1 agc int gid; 1079 1.1 agc int cc; 1080 1.1 agc int i; 1081 1.1 agc 1082 1.75 agc if (!valid_login(login_name, up->u_allow_samba)) { 1083 1.96 christos errx(EXIT_FAILURE, "Can't add user `%s': invalid login name", login_name); 1084 1.1 agc } 1085 1.82 christos #ifdef EXTENSIONS 1086 1.85 christos if (!valid_class(up->u_class)) { 1087 1.96 christos errx(EXIT_FAILURE, "Can't add user `%s': no such login class `%s'", 1088 1.96 christos login_name, up->u_class); 1089 1.82 christos } 1090 1.82 christos #endif 1091 1.20 agc if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) { 1092 1.96 christos err(EXIT_FAILURE, "Can't add user `%s': can't open `%s'", 1093 1.96 christos login_name, _PATH_MASTERPASSWD); 1094 1.1 agc } 1095 1.1 agc if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) { 1096 1.96 christos err(EXIT_FAILURE, "Can't add user `%s': can't lock `%s'", 1097 1.96 christos login_name, _PATH_MASTERPASSWD); 1098 1.1 agc } 1099 1.1 agc pw_init(); 1100 1.1 agc if ((ptmpfd = pw_lock(WAITSECS)) < 0) { 1101 1.82 christos int serrno = errno; 1102 1.82 christos (void)close(masterfd); 1103 1.82 christos errno = serrno; 1104 1.96 christos err(EXIT_FAILURE, "Can't add user `%s': can't obtain pw_lock", 1105 1.96 christos login_name); 1106 1.1 agc } 1107 1.1 agc while ((cc = read(masterfd, buf, sizeof(buf))) > 0) { 1108 1.1 agc if (write(ptmpfd, buf, (size_t)(cc)) != cc) { 1109 1.82 christos int serrno = errno; 1110 1.82 christos (void)close(masterfd); 1111 1.82 christos (void)close(ptmpfd); 1112 1.82 christos (void)pw_abort(); 1113 1.82 christos errno = serrno; 1114 1.96 christos err(EXIT_FAILURE, "Can't add user `%s': " 1115 1.96 christos "short write to /etc/ptmp", login_name); 1116 1.1 agc } 1117 1.1 agc } 1118 1.126 wiz (void)close(masterfd); 1119 1.1 agc /* if no uid was specified, get next one in [low_uid..high_uid] range */ 1120 1.1 agc sync_uid_gid = (strcmp(up->u_primgrp, "=uid") == 0); 1121 1.1 agc if (up->u_uid == -1) { 1122 1.51 agc int got_id = 0; 1123 1.51 agc 1124 1.51 agc /* 1125 1.51 agc * Look for a free UID in the command line ranges (if any). 1126 1.51 agc * These start after the ranges specified in the config file. 1127 1.51 agc */ 1128 1.51 agc for (i = up->u_defrc; !got_id && i < up->u_rc ; i++) { 1129 1.51 agc got_id = getnextuid(sync_uid_gid, &up->u_uid, 1130 1.51 agc up->u_rv[i].r_from, up->u_rv[i].r_to); 1131 1.51 agc } 1132 1.51 agc /* 1133 1.51 agc * If there were no free UIDs in the command line ranges, 1134 1.51 agc * try the ranges from the config file (there will always 1135 1.51 agc * be at least one default). 1136 1.51 agc */ 1137 1.51 agc for (i = 0; !got_id && i < up->u_defrc; i++) { 1138 1.51 agc got_id = getnextuid(sync_uid_gid, &up->u_uid, 1139 1.51 agc up->u_rv[i].r_from, up->u_rv[i].r_to); 1140 1.1 agc } 1141 1.51 agc if (!got_id) { 1142 1.82 christos (void)close(ptmpfd); 1143 1.82 christos (void)pw_abort(); 1144 1.96 christos errx(EXIT_FAILURE, "Can't add user `%s': " 1145 1.96 christos "can't get next uid for %d", login_name, 1146 1.82 christos up->u_uid); 1147 1.1 agc } 1148 1.1 agc } 1149 1.1 agc /* check uid isn't already allocated */ 1150 1.26 simonb if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) { 1151 1.82 christos (void)close(ptmpfd); 1152 1.82 christos (void)pw_abort(); 1153 1.96 christos errx(EXIT_FAILURE, "Can't add user `%s': " 1154 1.96 christos "uid %d is already in use", login_name, up->u_uid); 1155 1.1 agc } 1156 1.1 agc /* if -g=uid was specified, check gid is unused */ 1157 1.1 agc if (sync_uid_gid) { 1158 1.24 simonb if (getgrgid((gid_t)(up->u_uid)) != NULL) { 1159 1.82 christos (void)close(ptmpfd); 1160 1.82 christos (void)pw_abort(); 1161 1.96 christos errx(EXIT_FAILURE, "Can't add user `%s': " 1162 1.96 christos "gid %d is already in use", login_name, 1163 1.82 christos up->u_uid); 1164 1.1 agc } 1165 1.1 agc gid = up->u_uid; 1166 1.24 simonb } else if ((grp = getgrnam(up->u_primgrp)) != NULL) { 1167 1.1 agc gid = grp->gr_gid; 1168 1.9 agc } else if (is_number(up->u_primgrp) && 1169 1.24 simonb (grp = getgrgid((gid_t)atoi(up->u_primgrp))) != NULL) { 1170 1.1 agc gid = grp->gr_gid; 1171 1.1 agc } else { 1172 1.82 christos (void)close(ptmpfd); 1173 1.82 christos (void)pw_abort(); 1174 1.96 christos errx(EXIT_FAILURE, "Can't add user `%s': group %s not found", 1175 1.96 christos login_name, up->u_primgrp); 1176 1.1 agc } 1177 1.1 agc /* check name isn't already in use */ 1178 1.53 agc if (!(up->u_flags & F_DUPUID) && getpwnam(login_name) != NULL) { 1179 1.82 christos (void)close(ptmpfd); 1180 1.82 christos (void)pw_abort(); 1181 1.96 christos errx(EXIT_FAILURE, "Can't add user `%s': " 1182 1.96 christos "`%s' is already a user", login_name, login_name); 1183 1.1 agc } 1184 1.26 simonb if (up->u_flags & F_HOMEDIR) { 1185 1.82 christos (void)strlcpy(home, up->u_home, sizeof(home)); 1186 1.13 jlam } else { 1187 1.13 jlam /* if home directory hasn't been given, make it up */ 1188 1.82 christos (void)snprintf(home, sizeof(home), "%s/%s", up->u_basedir, 1189 1.82 christos login_name); 1190 1.1 agc } 1191 1.99 christos if (up->u_flags & F_SHELL) { 1192 1.99 christos #ifdef EXTENSIONS 1193 1.99 christos if (!valid_shell(up->u_shell)) { 1194 1.104 christos int oerrno = errno; 1195 1.99 christos (void)close(ptmpfd); 1196 1.99 christos (void)pw_abort(); 1197 1.104 christos errno = oerrno; 1198 1.99 christos errx(EXIT_FAILURE, "Can't add user `%s': " 1199 1.104 christos "Cannot access shell `%s'", 1200 1.104 christos login_name, up->u_shell); 1201 1.99 christos } 1202 1.99 christos #endif 1203 1.99 christos } 1204 1.99 christos 1205 1.64 agc if (!scantime(&inactive, up->u_inactive)) { 1206 1.87 christos warnx("Warning: inactive time `%s' invalid, password expiry off", 1207 1.52 grant up->u_inactive); 1208 1.52 grant } 1209 1.87 christos if (!scantime(&expire, up->u_expire) || expire == -1) { 1210 1.87 christos warnx("Warning: expire time `%s' invalid, account expiry off", 1211 1.52 grant up->u_expire); 1212 1.87 christos expire = 0; /* Just in case. */ 1213 1.21 agc } 1214 1.26 simonb if (lstat(home, &st) < 0 && !(up->u_flags & F_MKDIR)) { 1215 1.82 christos warnx("Warning: home directory `%s' doesn't exist, " 1216 1.82 christos "and -m was not specified", home); 1217 1.1 agc } 1218 1.60 itojun password[sizeof(password) - 1] = '\0'; 1219 1.62 agc if (up->u_password != NULL && valid_password_length(up->u_password)) { 1220 1.82 christos (void)strlcpy(password, up->u_password, sizeof(password)); 1221 1.1 agc } else { 1222 1.82 christos (void)memset(password, '*', DES_Len); 1223 1.65 agc password[DES_Len] = 0; 1224 1.15 soren if (up->u_password != NULL) { 1225 1.2 simonb warnx("Password `%s' is invalid: setting it to `%s'", 1226 1.9 agc up->u_password, password); 1227 1.1 agc } 1228 1.1 agc } 1229 1.52 grant cc = snprintf(buf, sizeof(buf), "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s\n", 1230 1.53 agc login_name, 1231 1.1 agc password, 1232 1.1 agc up->u_uid, 1233 1.1 agc gid, 1234 1.42 christos #ifdef EXTENSIONS 1235 1.42 christos up->u_class, 1236 1.42 christos #else 1237 1.42 christos "", 1238 1.42 christos #endif 1239 1.52 grant (long) inactive, 1240 1.9 agc (long) expire, 1241 1.1 agc up->u_comment, 1242 1.1 agc home, 1243 1.1 agc up->u_shell); 1244 1.1 agc if (write(ptmpfd, buf, (size_t) cc) != cc) { 1245 1.82 christos int serrno = errno; 1246 1.82 christos (void)close(ptmpfd); 1247 1.82 christos (void)pw_abort(); 1248 1.82 christos errno = serrno; 1249 1.96 christos err(EXIT_FAILURE, "Can't add user `%s': write failed", 1250 1.96 christos login_name); 1251 1.1 agc } 1252 1.26 simonb if (up->u_flags & F_MKDIR) { 1253 1.33 simonb if (lstat(home, &st) == 0) { 1254 1.82 christos (void)close(ptmpfd); 1255 1.82 christos (void)pw_abort(); 1256 1.82 christos errx(EXIT_FAILURE, 1257 1.96 christos "Can't add user `%s': home directory `%s' " 1258 1.96 christos "already exists", login_name, home); 1259 1.33 simonb } else { 1260 1.129 dholland if (asystem("%s -p %s", _PATH_MKDIR, home) != 0) { 1261 1.82 christos (void)close(ptmpfd); 1262 1.82 christos (void)pw_abort(); 1263 1.96 christos errx(EXIT_FAILURE, "Can't add user `%s': " 1264 1.96 christos "can't mkdir `%s'", login_name, home); 1265 1.33 simonb } 1266 1.115 elad (void)copydotfiles(up->u_skeldir, up->u_uid, gid, home, 1267 1.115 elad up->u_homeperm); 1268 1.1 agc } 1269 1.1 agc } 1270 1.1 agc if (strcmp(up->u_primgrp, "=uid") == 0 && 1271 1.53 agc getgrnam(login_name) == NULL && 1272 1.53 agc !creategid(login_name, gid, login_name)) { 1273 1.82 christos (void)close(ptmpfd); 1274 1.82 christos (void)pw_abort(); 1275 1.96 christos errx(EXIT_FAILURE, "Can't add user `%s': can't create gid %d ", 1276 1.96 christos login_name, gid); 1277 1.82 christos } 1278 1.82 christos if (up->u_groupc > 0 && 1279 1.82 christos !append_group(login_name, up->u_groupc, up->u_groupv)) { 1280 1.82 christos (void)close(ptmpfd); 1281 1.82 christos (void)pw_abort(); 1282 1.96 christos errx(EXIT_FAILURE, "Can't add user `%s': can't append " 1283 1.96 christos "to new groups", login_name); 1284 1.1 agc } 1285 1.82 christos (void)close(ptmpfd); 1286 1.45 agc #if PW_MKDB_ARGC == 2 1287 1.82 christos if (pw_mkdb(login_name, 0) < 0) 1288 1.45 agc #else 1289 1.82 christos if (pw_mkdb() < 0) 1290 1.82 christos #endif 1291 1.82 christos { 1292 1.82 christos (void)pw_abort(); 1293 1.96 christos errx(EXIT_FAILURE, "Can't add user `%s': pw_mkdb failed", 1294 1.96 christos login_name); 1295 1.45 agc } 1296 1.82 christos syslog(LOG_INFO, "New user added: name=%s, uid=%d, gid=%d, home=%s, " 1297 1.82 christos "shell=%s", login_name, up->u_uid, gid, home, up->u_shell); 1298 1.1 agc return 1; 1299 1.1 agc } 1300 1.1 agc 1301 1.53 agc /* remove a user from the groups file */ 1302 1.53 agc static int 1303 1.53 agc rm_user_from_groups(char *login_name) 1304 1.53 agc { 1305 1.53 agc struct stat st; 1306 1.53 agc regmatch_t matchv[10]; 1307 1.53 agc regex_t r; 1308 1.53 agc FILE *from; 1309 1.53 agc FILE *to; 1310 1.53 agc char line[MaxEntryLen]; 1311 1.53 agc char buf[MaxEntryLen]; 1312 1.53 agc char f[MaxFileNameLen]; 1313 1.53 agc int fd; 1314 1.53 agc int cc; 1315 1.53 agc int sc; 1316 1.53 agc 1317 1.82 christos (void)snprintf(line, sizeof(line), "(:|,)(%s)(,|$)", login_name); 1318 1.82 christos if ((sc = regcomp(&r, line, REG_EXTENDED|REG_NEWLINE)) != 0) { 1319 1.82 christos (void)regerror(sc, &r, buf, sizeof(buf)); 1320 1.82 christos warnx("Can't compile regular expression `%s' (%s)", line, 1321 1.82 christos buf); 1322 1.53 agc return 0; 1323 1.53 agc } 1324 1.53 agc if ((from = fopen(_PATH_GROUP, "r")) == NULL) { 1325 1.96 christos warn("Can't remove user `%s' from `%s': can't open `%s'", 1326 1.96 christos login_name, _PATH_GROUP, _PATH_GROUP); 1327 1.53 agc return 0; 1328 1.53 agc } 1329 1.53 agc if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) { 1330 1.96 christos warn("Can't remove user `%s' from `%s': can't lock `%s'", 1331 1.96 christos login_name, _PATH_GROUP, _PATH_GROUP); 1332 1.107 joerg (void)fclose(from); 1333 1.96 christos return 0; 1334 1.53 agc } 1335 1.82 christos (void)fstat(fileno(from), &st); 1336 1.82 christos (void)snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP); 1337 1.53 agc if ((fd = mkstemp(f)) < 0) { 1338 1.96 christos warn("Can't remove user `%s' from `%s': mkstemp failed", 1339 1.96 christos login_name, _PATH_GROUP); 1340 1.82 christos (void)fclose(from); 1341 1.53 agc return 0; 1342 1.53 agc } 1343 1.53 agc if ((to = fdopen(fd, "w")) == NULL) { 1344 1.96 christos warn("Can't remove user `%s' from `%s': fdopen `%s' failed", 1345 1.96 christos login_name, _PATH_GROUP, f); 1346 1.82 christos (void)fclose(from); 1347 1.82 christos (void)close(fd); 1348 1.82 christos (void)unlink(f); 1349 1.53 agc return 0; 1350 1.53 agc } 1351 1.82 christos while (fgets(buf, sizeof(buf), from) != NULL) { 1352 1.53 agc cc = strlen(buf); 1353 1.53 agc if (regexec(&r, buf, 10, matchv, 0) == 0) { 1354 1.70 agc if (buf[(int)matchv[1].rm_so] == ',') { 1355 1.70 agc matchv[2].rm_so = matchv[1].rm_so; 1356 1.70 agc } else if (matchv[2].rm_eo != matchv[3].rm_eo) { 1357 1.70 agc matchv[2].rm_eo = matchv[3].rm_eo; 1358 1.53 agc } 1359 1.70 agc cc -= (int) matchv[2].rm_eo; 1360 1.70 agc sc = (int) matchv[2].rm_so; 1361 1.82 christos if (fwrite(buf, sizeof(char), (size_t)sc, to) != sc || 1362 1.82 christos fwrite(&buf[(int)matchv[2].rm_eo], sizeof(char), 1363 1.82 christos (size_t)cc, to) != cc) { 1364 1.96 christos warn("Can't remove user `%s' from `%s': " 1365 1.96 christos "short write to `%s'", login_name, 1366 1.96 christos _PATH_GROUP, f); 1367 1.82 christos (void)fclose(from); 1368 1.82 christos (void)close(fd); 1369 1.82 christos (void)unlink(f); 1370 1.53 agc return 0; 1371 1.53 agc } 1372 1.53 agc } else if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) { 1373 1.96 christos warn("Can't remove user `%s' from `%s': " 1374 1.96 christos "short write to `%s'", login_name, _PATH_GROUP, f); 1375 1.82 christos (void)fclose(from); 1376 1.82 christos (void)close(fd); 1377 1.82 christos (void)unlink(f); 1378 1.53 agc return 0; 1379 1.53 agc } 1380 1.53 agc } 1381 1.82 christos (void)fclose(from); 1382 1.82 christos (void)fclose(to); 1383 1.53 agc if (rename(f, _PATH_GROUP) < 0) { 1384 1.96 christos warn("Can't remove user `%s' from `%s': " 1385 1.96 christos "can't rename `%s' to `%s'", 1386 1.96 christos login_name, _PATH_GROUP, f, _PATH_GROUP); 1387 1.82 christos (void)unlink(f); 1388 1.53 agc return 0; 1389 1.53 agc } 1390 1.82 christos (void)chmod(_PATH_GROUP, st.st_mode & 07777); 1391 1.53 agc return 1; 1392 1.53 agc } 1393 1.53 agc 1394 1.58 agc /* check that the user or group is local, not from YP/NIS */ 1395 1.56 agc static int 1396 1.58 agc is_local(char *name, const char *file) 1397 1.56 agc { 1398 1.56 agc FILE *fp; 1399 1.56 agc char buf[MaxEntryLen]; 1400 1.82 christos size_t len; 1401 1.56 agc int ret; 1402 1.56 agc 1403 1.58 agc if ((fp = fopen(file, "r")) == NULL) { 1404 1.82 christos err(EXIT_FAILURE, "Can't open `%s'", file); 1405 1.56 agc } 1406 1.75 agc len = strlen(name); 1407 1.56 agc for (ret = 0 ; fgets(buf, sizeof(buf), fp) != NULL ; ) { 1408 1.75 agc if (strncmp(buf, name, len) == 0 && buf[len] == ':') { 1409 1.56 agc ret = 1; 1410 1.56 agc break; 1411 1.56 agc } 1412 1.56 agc } 1413 1.82 christos (void)fclose(fp); 1414 1.56 agc return ret; 1415 1.56 agc } 1416 1.56 agc 1417 1.1 agc /* modify a user */ 1418 1.1 agc static int 1419 1.75 agc moduser(char *login_name, char *newlogin, user_t *up, int allow_samba) 1420 1.1 agc { 1421 1.121 christos struct passwd *pwp, pw; 1422 1.56 agc struct group *grp; 1423 1.56 agc const char *homedir; 1424 1.82 christos char *locked_pwd; 1425 1.56 agc size_t colonc; 1426 1.56 agc size_t loginc; 1427 1.56 agc size_t len; 1428 1.56 agc FILE *master; 1429 1.45 agc char newdir[MaxFileNameLen]; 1430 1.75 agc char buf[MaxEntryLen]; 1431 1.121 christos char pwbuf[MaxEntryLen]; 1432 1.56 agc char *colon; 1433 1.1 agc int masterfd; 1434 1.1 agc int ptmpfd; 1435 1.41 ad int error; 1436 1.1 agc 1437 1.75 agc if (!valid_login(newlogin, allow_samba)) { 1438 1.96 christos errx(EXIT_FAILURE, "Can't modify user `%s': invalid login name", 1439 1.82 christos login_name); 1440 1.1 agc } 1441 1.121 christos if (getpwnam_r(login_name, &pw, pwbuf, sizeof(pwbuf), &pwp) != 0 1442 1.121 christos || pwp == NULL) { 1443 1.96 christos errx(EXIT_FAILURE, "Can't modify user `%s': no such user", 1444 1.96 christos login_name); 1445 1.1 agc } 1446 1.58 agc if (!is_local(login_name, _PATH_MASTERPASSWD)) { 1447 1.96 christos errx(EXIT_FAILURE, "Can't modify user `%s': must be a local user", 1448 1.82 christos login_name); 1449 1.56 agc } 1450 1.35 wiz /* keep dir name in case we need it for '-m' */ 1451 1.35 wiz homedir = pwp->pw_dir; 1452 1.35 wiz 1453 1.20 agc if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) { 1454 1.96 christos err(EXIT_FAILURE, "Can't modify user `%s': can't open `%s'", 1455 1.96 christos login_name, _PATH_MASTERPASSWD); 1456 1.1 agc } 1457 1.1 agc if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) { 1458 1.96 christos err(EXIT_FAILURE, "Can't modify user `%s': can't lock `%s'", 1459 1.96 christos login_name, _PATH_MASTERPASSWD); 1460 1.1 agc } 1461 1.1 agc pw_init(); 1462 1.1 agc if ((ptmpfd = pw_lock(WAITSECS)) < 0) { 1463 1.82 christos int serrno = errno; 1464 1.82 christos (void)close(masterfd); 1465 1.82 christos errno = serrno; 1466 1.96 christos err(EXIT_FAILURE, "Can't modify user `%s': " 1467 1.96 christos "can't obtain pw_lock", login_name); 1468 1.1 agc } 1469 1.24 simonb if ((master = fdopen(masterfd, "r")) == NULL) { 1470 1.82 christos int serrno = errno; 1471 1.82 christos (void)close(masterfd); 1472 1.82 christos (void)close(ptmpfd); 1473 1.82 christos (void)pw_abort(); 1474 1.82 christos errno = serrno; 1475 1.96 christos err(EXIT_FAILURE, "Can't modify user `%s': " 1476 1.96 christos "fdopen fd for %s", login_name, _PATH_MASTERPASSWD); 1477 1.1 agc } 1478 1.24 simonb if (up != NULL) { 1479 1.26 simonb if (up->u_flags & F_USERNAME) { 1480 1.82 christos /* 1481 1.82 christos * If changing name, 1482 1.82 christos * check new name isn't already in use 1483 1.82 christos */ 1484 1.82 christos if (strcmp(login_name, newlogin) != 0 && 1485 1.82 christos getpwnam(newlogin) != NULL) { 1486 1.82 christos (void)close(ptmpfd); 1487 1.82 christos (void)pw_abort(); 1488 1.96 christos errx(EXIT_FAILURE, "Can't modify user `%s': " 1489 1.96 christos "`%s' is already a user", login_name, 1490 1.82 christos newlogin); 1491 1.26 simonb } 1492 1.35 wiz pwp->pw_name = newlogin; 1493 1.36 wiz 1494 1.36 wiz /* 1495 1.36 wiz * Provide a new directory name in case the 1496 1.36 wiz * home directory is to be moved. 1497 1.36 wiz */ 1498 1.36 wiz if (up->u_flags & F_MKDIR) { 1499 1.82 christos (void)snprintf(newdir, sizeof(newdir), "%s/%s", 1500 1.82 christos up->u_basedir, newlogin); 1501 1.36 wiz pwp->pw_dir = newdir; 1502 1.36 wiz } 1503 1.1 agc } 1504 1.26 simonb if (up->u_flags & F_PASSWORD) { 1505 1.62 agc if (up->u_password != NULL) { 1506 1.63 itojun if (!valid_password_length(up->u_password)) { 1507 1.82 christos (void)close(ptmpfd); 1508 1.82 christos (void)pw_abort(); 1509 1.82 christos errx(EXIT_FAILURE, 1510 1.96 christos "Can't modify user `%s': " 1511 1.96 christos "invalid password: `%s'", 1512 1.96 christos login_name, up->u_password); 1513 1.61 agc } 1514 1.82 christos if ((locked_pwd = 1515 1.82 christos strstr(pwp->pw_passwd, LOCKED)) != NULL) { 1516 1.82 christos /* 1517 1.82 christos * account is locked - keep it locked 1518 1.82 christos * and just change the password. 1519 1.82 christos */ 1520 1.82 christos if (asprintf(&locked_pwd, "%s%s", 1521 1.93 agc LOCKED, up->u_password) == -1) { 1522 1.95 christos (void)close(ptmpfd); 1523 1.95 christos (void)pw_abort(); 1524 1.82 christos err(EXIT_FAILURE, 1525 1.96 christos "Can't modify user `%s': " 1526 1.96 christos "asprintf failed", 1527 1.96 christos login_name); 1528 1.93 agc } 1529 1.82 christos pwp->pw_passwd = locked_pwd; 1530 1.93 agc } else { 1531 1.82 christos pwp->pw_passwd = up->u_password; 1532 1.93 agc } 1533 1.82 christos } 1534 1.82 christos } 1535 1.96 christos 1536 1.82 christos /* check whether we should lock the account. */ 1537 1.82 christos if (up->u_locked == LOCK) { 1538 1.82 christos /* check to see account if already locked. */ 1539 1.82 christos if ((locked_pwd = strstr(pwp->pw_passwd, LOCKED)) 1540 1.93 agc != NULL) { 1541 1.82 christos warnx("Account is already locked"); 1542 1.93 agc } else { 1543 1.82 christos if (asprintf(&locked_pwd, "%s%s", LOCKED, 1544 1.93 agc pwp->pw_passwd) == -1) { 1545 1.95 christos (void)close(ptmpfd); 1546 1.95 christos (void)pw_abort(); 1547 1.96 christos err(EXIT_FAILURE, 1548 1.96 christos "Can't modify user `%s': " 1549 1.96 christos "asprintf failed", login_name); 1550 1.93 agc } 1551 1.82 christos pwp->pw_passwd = locked_pwd; 1552 1.82 christos } 1553 1.82 christos } else if (up->u_locked == UNLOCK) { 1554 1.82 christos if ((locked_pwd = strstr(pwp->pw_passwd, LOCKED)) 1555 1.93 agc == NULL) { 1556 1.96 christos warnx("Can't modify user `%s': " 1557 1.96 christos "account is not locked", login_name); 1558 1.93 agc } else { 1559 1.82 christos pwp->pw_passwd = locked_pwd + strlen(LOCKED); 1560 1.93 agc } 1561 1.26 simonb } 1562 1.82 christos 1563 1.26 simonb if (up->u_flags & F_UID) { 1564 1.26 simonb /* check uid isn't already allocated */ 1565 1.82 christos if (!(up->u_flags & F_DUPUID) && 1566 1.82 christos getpwuid((uid_t)(up->u_uid)) != NULL) { 1567 1.82 christos (void)close(ptmpfd); 1568 1.82 christos (void)pw_abort(); 1569 1.96 christos errx(EXIT_FAILURE, "Can't modify user `%s': " 1570 1.96 christos "uid `%d' is already in use", login_name, 1571 1.82 christos up->u_uid); 1572 1.26 simonb } 1573 1.35 wiz pwp->pw_uid = up->u_uid; 1574 1.1 agc } 1575 1.26 simonb if (up->u_flags & F_GROUP) { 1576 1.26 simonb /* if -g=uid was specified, check gid is unused */ 1577 1.26 simonb if (strcmp(up->u_primgrp, "=uid") == 0) { 1578 1.123 mike if (getgrgid((gid_t)(pwp->pw_uid)) != NULL) { 1579 1.82 christos (void)close(ptmpfd); 1580 1.82 christos (void)pw_abort(); 1581 1.96 christos errx(EXIT_FAILURE, 1582 1.96 christos "Can't modify user `%s': " 1583 1.96 christos "gid %d is already in use", 1584 1.131 blymn login_name, pwp->pw_uid); 1585 1.26 simonb } 1586 1.123 mike pwp->pw_gid = pwp->pw_uid; 1587 1.130 blymn if (!creategid(newlogin, pwp->pw_uid, "")) { 1588 1.130 blymn errx(EXIT_FAILURE, 1589 1.130 blymn "Could not create group %s " 1590 1.130 blymn "with uid %d", newlogin, 1591 1.130 blymn up->u_uid); 1592 1.130 blymn } 1593 1.26 simonb } else if ((grp = getgrnam(up->u_primgrp)) != NULL) { 1594 1.35 wiz pwp->pw_gid = grp->gr_gid; 1595 1.26 simonb } else if (is_number(up->u_primgrp) && 1596 1.82 christos (grp = getgrgid( 1597 1.82 christos (gid_t)atoi(up->u_primgrp))) != NULL) { 1598 1.35 wiz pwp->pw_gid = grp->gr_gid; 1599 1.26 simonb } else { 1600 1.82 christos (void)close(ptmpfd); 1601 1.82 christos (void)pw_abort(); 1602 1.96 christos errx(EXIT_FAILURE, "Can't modify user `%s': " 1603 1.96 christos "group %s not found", login_name, 1604 1.82 christos up->u_primgrp); 1605 1.1 agc } 1606 1.1 agc } 1607 1.52 grant if (up->u_flags & F_INACTIVE) { 1608 1.64 agc if (!scantime(&pwp->pw_change, up->u_inactive)) { 1609 1.82 christos warnx("Warning: inactive time `%s' invalid, " 1610 1.82 christos "password expiry off", 1611 1.52 grant up->u_inactive); 1612 1.52 grant } 1613 1.52 grant } 1614 1.26 simonb if (up->u_flags & F_EXPIRE) { 1615 1.87 christos if (!scantime(&pwp->pw_expire, up->u_expire) || 1616 1.87 christos pwp->pw_expire == -1) { 1617 1.82 christos warnx("Warning: expire time `%s' invalid, " 1618 1.87 christos "account expiry off", 1619 1.52 grant up->u_expire); 1620 1.87 christos pwp->pw_expire = 0; 1621 1.52 grant } 1622 1.1 agc } 1623 1.93 agc if (up->u_flags & F_COMMENT) { 1624 1.35 wiz pwp->pw_gecos = up->u_comment; 1625 1.93 agc } 1626 1.93 agc if (up->u_flags & F_HOMEDIR) { 1627 1.35 wiz pwp->pw_dir = up->u_home; 1628 1.93 agc } 1629 1.93 agc if (up->u_flags & F_SHELL) { 1630 1.99 christos #ifdef EXTENSIONS 1631 1.99 christos if (!valid_shell(up->u_shell)) { 1632 1.104 christos int oerrno = errno; 1633 1.99 christos (void)close(ptmpfd); 1634 1.99 christos (void)pw_abort(); 1635 1.104 christos errno = oerrno; 1636 1.99 christos errx(EXIT_FAILURE, "Can't modify user `%s': " 1637 1.104 christos "Cannot access shell `%s'", 1638 1.104 christos login_name, up->u_shell); 1639 1.99 christos } 1640 1.99 christos pwp->pw_shell = up->u_shell; 1641 1.99 christos #else 1642 1.99 christos pwp->pw_shell = up->u_shell; 1643 1.99 christos #endif 1644 1.93 agc } 1645 1.42 christos #ifdef EXTENSIONS 1646 1.82 christos if (up->u_flags & F_CLASS) { 1647 1.82 christos if (!valid_class(up->u_class)) { 1648 1.82 christos (void)close(ptmpfd); 1649 1.82 christos (void)pw_abort(); 1650 1.96 christos errx(EXIT_FAILURE, "Can't modify user `%s': " 1651 1.96 christos "no such login class `%s'", login_name, 1652 1.82 christos up->u_class); 1653 1.82 christos } 1654 1.42 christos pwp->pw_class = up->u_class; 1655 1.82 christos } 1656 1.42 christos #endif 1657 1.1 agc } 1658 1.53 agc loginc = strlen(login_name); 1659 1.74 agc while (fgets(buf, sizeof(buf), master) != NULL) { 1660 1.74 agc if ((colon = strchr(buf, ':')) == NULL) { 1661 1.74 agc warnx("Malformed entry `%s'. Skipping", buf); 1662 1.1 agc continue; 1663 1.1 agc } 1664 1.74 agc colonc = (size_t)(colon - buf); 1665 1.74 agc if (strncmp(login_name, buf, loginc) == 0 && loginc == colonc) { 1666 1.24 simonb if (up != NULL) { 1667 1.74 agc len = snprintf(buf, sizeof(buf), "%s:%s:%d:%d:" 1668 1.42 christos #ifdef EXTENSIONS 1669 1.96 christos "%s" 1670 1.42 christos #endif 1671 1.82 christos ":%ld:%ld:%s:%s:%s\n", 1672 1.82 christos newlogin, 1673 1.82 christos pwp->pw_passwd, 1674 1.82 christos pwp->pw_uid, 1675 1.82 christos pwp->pw_gid, 1676 1.82 christos #ifdef EXTENSIONS 1677 1.82 christos pwp->pw_class, 1678 1.82 christos #endif 1679 1.82 christos (long)pwp->pw_change, 1680 1.82 christos (long)pwp->pw_expire, 1681 1.82 christos pwp->pw_gecos, 1682 1.82 christos pwp->pw_dir, 1683 1.82 christos pwp->pw_shell); 1684 1.26 simonb if (write(ptmpfd, buf, len) != len) { 1685 1.82 christos int serrno = errno; 1686 1.82 christos (void)close(ptmpfd); 1687 1.82 christos (void)pw_abort(); 1688 1.82 christos errno = serrno; 1689 1.96 christos err(EXIT_FAILURE, "Can't modify user " 1690 1.96 christos "`%s': write", login_name); 1691 1.1 agc } 1692 1.1 agc } 1693 1.74 agc } else { 1694 1.74 agc len = strlen(buf); 1695 1.108 joerg if (write(ptmpfd, buf, len) != len) { 1696 1.82 christos int serrno = errno; 1697 1.82 christos (void)close(masterfd); 1698 1.82 christos (void)close(ptmpfd); 1699 1.82 christos (void)pw_abort(); 1700 1.82 christos errno = serrno; 1701 1.96 christos err(EXIT_FAILURE, "Can't modify `%s': " 1702 1.96 christos "write", login_name); 1703 1.74 agc } 1704 1.1 agc } 1705 1.1 agc } 1706 1.24 simonb if (up != NULL) { 1707 1.26 simonb if ((up->u_flags & F_MKDIR) && 1708 1.129 dholland asystem("%s %s %s", _PATH_MV, homedir, pwp->pw_dir) != 0) { 1709 1.82 christos (void)close(ptmpfd); 1710 1.82 christos (void)pw_abort(); 1711 1.96 christos errx(EXIT_FAILURE, "Can't modify user `%s': " 1712 1.96 christos "can't move `%s' to `%s'", 1713 1.96 christos login_name, homedir, pwp->pw_dir); 1714 1.23 agc } 1715 1.23 agc if (up->u_groupc > 0 && 1716 1.23 agc !append_group(newlogin, up->u_groupc, up->u_groupv)) { 1717 1.82 christos (void)close(ptmpfd); 1718 1.82 christos (void)pw_abort(); 1719 1.96 christos errx(EXIT_FAILURE, "Can't modify user `%s': " 1720 1.96 christos "can't append `%s' to new groups", 1721 1.96 christos login_name, newlogin); 1722 1.23 agc } 1723 1.1 agc } 1724 1.82 christos (void)close(ptmpfd); 1725 1.109 jnemeth (void)fclose(master); 1726 1.45 agc #if PW_MKDB_ARGC == 2 1727 1.53 agc if (up != NULL && strcmp(login_name, newlogin) == 0) { 1728 1.53 agc error = pw_mkdb(login_name, 0); 1729 1.41 ad } else { 1730 1.41 ad error = pw_mkdb(NULL, 0); 1731 1.41 ad } 1732 1.45 agc #else 1733 1.45 agc error = pw_mkdb(); 1734 1.45 agc #endif 1735 1.41 ad if (error < 0) { 1736 1.82 christos (void)pw_abort(); 1737 1.96 christos errx(EXIT_FAILURE, "Can't modify user `%s': pw_mkdb failed", 1738 1.96 christos login_name); 1739 1.1 agc } 1740 1.59 agc if (up == NULL) { 1741 1.82 christos syslog(LOG_INFO, "User removed: name=%s", login_name); 1742 1.59 agc } else if (strcmp(login_name, newlogin) == 0) { 1743 1.82 christos syslog(LOG_INFO, "User information modified: name=%s, uid=%d, " 1744 1.96 christos "gid=%d, home=%s, shell=%s", 1745 1.82 christos login_name, pwp->pw_uid, pwp->pw_gid, pwp->pw_dir, 1746 1.82 christos pwp->pw_shell); 1747 1.59 agc } else { 1748 1.82 christos syslog(LOG_INFO, "User information modified: name=%s, " 1749 1.96 christos "new name=%s, uid=%d, gid=%d, home=%s, shell=%s", 1750 1.82 christos login_name, newlogin, pwp->pw_uid, pwp->pw_gid, 1751 1.82 christos pwp->pw_dir, pwp->pw_shell); 1752 1.59 agc } 1753 1.1 agc return 1; 1754 1.1 agc } 1755 1.1 agc 1756 1.9 agc #ifdef EXTENSIONS 1757 1.9 agc /* see if we can find out the user struct */ 1758 1.9 agc static struct passwd * 1759 1.81 agc find_user_info(const char *name) 1760 1.9 agc { 1761 1.9 agc struct passwd *pwp; 1762 1.9 agc 1763 1.24 simonb if ((pwp = getpwnam(name)) != NULL) { 1764 1.9 agc return pwp; 1765 1.9 agc } 1766 1.24 simonb if (is_number(name) && (pwp = getpwuid((uid_t)atoi(name))) != NULL) { 1767 1.9 agc return pwp; 1768 1.9 agc } 1769 1.24 simonb return NULL; 1770 1.9 agc } 1771 1.9 agc #endif 1772 1.9 agc 1773 1.9 agc /* see if we can find out the group struct */ 1774 1.9 agc static struct group * 1775 1.81 agc find_group_info(const char *name) 1776 1.9 agc { 1777 1.9 agc struct group *grp; 1778 1.9 agc 1779 1.24 simonb if ((grp = getgrnam(name)) != NULL) { 1780 1.9 agc return grp; 1781 1.9 agc } 1782 1.24 simonb if (is_number(name) && (grp = getgrgid((gid_t)atoi(name))) != NULL) { 1783 1.9 agc return grp; 1784 1.9 agc } 1785 1.24 simonb return NULL; 1786 1.9 agc } 1787 1.9 agc 1788 1.1 agc /* print out usage message, and then exit */ 1789 1.9 agc void 1790 1.38 cgd usermgmt_usage(const char *prog) 1791 1.1 agc { 1792 1.1 agc if (strcmp(prog, "useradd") == 0) { 1793 1.89 wiz (void)fprintf(stderr, "usage: %s -D [-F] [-b base-dir] " 1794 1.89 wiz "[-e expiry-time] [-f inactive-time]\n" 1795 1.89 wiz "\t[-g gid | name | =uid] [-k skel-dir] [-L login-class]\n" 1796 1.116 wiz "\t[-M homeperm] [-r lowuid..highuid] [-s shell]\n", prog); 1797 1.89 wiz (void)fprintf(stderr, "usage: %s [-moSv] [-b base-dir] " 1798 1.89 wiz "[-c comment] [-d home-dir] [-e expiry-time]\n" 1799 1.89 wiz "\t[-f inactive-time] [-G secondary-group] " 1800 1.116 wiz "[-g gid | name | =uid]\n" 1801 1.116 wiz "\t[-k skeletondir] [-L login-class] [-M homeperm] " 1802 1.116 wiz "[-p password]\n" 1803 1.116 wiz "\t[-r lowuid..highuid] [-s shell] [-u uid] user\n", 1804 1.16 soren prog); 1805 1.9 agc } else if (strcmp(prog, "usermod") == 0) { 1806 1.89 wiz (void)fprintf(stderr, "usage: %s [-FmoSv] [-C yes/no] " 1807 1.89 wiz "[-c comment] [-d home-dir] [-e expiry-time]\n" 1808 1.89 wiz "\t[-f inactive] [-G secondary-group] " 1809 1.89 wiz "[-g gid | name | =uid]\n" 1810 1.89 wiz "\t[-L login-class] [-l new-login] [-p password] " 1811 1.89 wiz "[-s shell] [-u uid]\n" 1812 1.89 wiz "\tuser\n", prog); 1813 1.7 lukem } else if (strcmp(prog, "userdel") == 0) { 1814 1.89 wiz (void)fprintf(stderr, "usage: %s -D [-p preserve-value]\n", prog); 1815 1.82 christos (void)fprintf(stderr, 1816 1.89 wiz "usage: %s [-rSv] [-p preserve-value] user\n", prog); 1817 1.9 agc #ifdef EXTENSIONS 1818 1.9 agc } else if (strcmp(prog, "userinfo") == 0) { 1819 1.119 reed (void)fprintf(stderr, "usage: %s [-e] user\n", prog); 1820 1.9 agc #endif 1821 1.1 agc } else if (strcmp(prog, "groupadd") == 0) { 1822 1.90 wiz (void)fprintf(stderr, "usage: %s [-ov] [-g gid]" 1823 1.90 wiz " [-r lowgid..highgid] group\n", prog); 1824 1.1 agc } else if (strcmp(prog, "groupdel") == 0) { 1825 1.82 christos (void)fprintf(stderr, "usage: %s [-v] group\n", prog); 1826 1.1 agc } else if (strcmp(prog, "groupmod") == 0) { 1827 1.82 christos (void)fprintf(stderr, 1828 1.90 wiz "usage: %s [-ov] [-g gid] [-n newname] group\n", prog); 1829 1.9 agc } else if (strcmp(prog, "user") == 0 || strcmp(prog, "group") == 0) { 1830 1.82 christos (void)fprintf(stderr, 1831 1.73 jmmv "usage: %s ( add | del | mod | info ) ...\n", prog); 1832 1.9 agc #ifdef EXTENSIONS 1833 1.9 agc } else if (strcmp(prog, "groupinfo") == 0) { 1834 1.90 wiz (void)fprintf(stderr, "usage: %s [-ev] group\n", prog); 1835 1.9 agc #endif 1836 1.1 agc } 1837 1.1 agc exit(EXIT_FAILURE); 1838 1.1 agc /* NOTREACHED */ 1839 1.1 agc } 1840 1.1 agc 1841 1.1 agc #ifdef EXTENSIONS 1842 1.115 elad #define ADD_OPT_EXTENSIONS "M:p:r:vL:S" 1843 1.1 agc #else 1844 1.96 christos #define ADD_OPT_EXTENSIONS 1845 1.1 agc #endif 1846 1.1 agc 1847 1.9 agc int 1848 1.1 agc useradd(int argc, char **argv) 1849 1.1 agc { 1850 1.125 mlelstv def_t def; 1851 1.125 mlelstv user_t *up = &def.user; 1852 1.1 agc int defaultfield; 1853 1.1 agc int bigD; 1854 1.1 agc int c; 1855 1.42 christos #ifdef EXTENSIONS 1856 1.1 agc int i; 1857 1.42 christos #endif 1858 1.1 agc 1859 1.125 mlelstv read_defaults(&def); 1860 1.125 mlelstv up->u_uid = -1; 1861 1.1 agc defaultfield = bigD = 0; 1862 1.87 christos while ((c = getopt(argc, argv, "DFG:b:c:d:e:f:g:k:mou:s:" 1863 1.82 christos ADD_OPT_EXTENSIONS)) != -1) { 1864 1.1 agc switch(c) { 1865 1.1 agc case 'D': 1866 1.1 agc bigD = 1; 1867 1.1 agc break; 1868 1.87 christos case 'F': 1869 1.87 christos /* 1870 1.87 christos * Setting -1 will force the new user to 1871 1.87 christos * change their password as soon as they 1872 1.89 wiz * next log in - passwd(5). 1873 1.87 christos */ 1874 1.87 christos defaultfield = 1; 1875 1.125 mlelstv memsave(&up->u_inactive, "-1", strlen("-1")); 1876 1.87 christos break; 1877 1.1 agc case 'G': 1878 1.125 mlelstv while (up->u_groupc < NGROUPS_MAX && 1879 1.125 mlelstv (up->u_groupv[up->u_groupc] = strsep(&optarg, ",")) != NULL) { 1880 1.125 mlelstv if (up->u_groupv[up->u_groupc][0] != 0) { 1881 1.125 mlelstv up->u_groupc++; 1882 1.20 agc } 1883 1.20 agc } 1884 1.20 agc if (optarg != NULL) { 1885 1.82 christos warnx("Truncated list of secondary groups " 1886 1.82 christos "to %d entries", NGROUPS_MAX); 1887 1.20 agc } 1888 1.1 agc break; 1889 1.75 agc #ifdef EXTENSIONS 1890 1.75 agc case 'S': 1891 1.125 mlelstv up->u_allow_samba = 1; 1892 1.75 agc break; 1893 1.75 agc #endif 1894 1.1 agc case 'b': 1895 1.1 agc defaultfield = 1; 1896 1.125 mlelstv memsave(&up->u_basedir, optarg, strlen(optarg)); 1897 1.1 agc break; 1898 1.1 agc case 'c': 1899 1.125 mlelstv memsave(&up->u_comment, optarg, strlen(optarg)); 1900 1.1 agc break; 1901 1.1 agc case 'd': 1902 1.125 mlelstv memsave(&up->u_home, optarg, strlen(optarg)); 1903 1.125 mlelstv up->u_flags |= F_HOMEDIR; 1904 1.1 agc break; 1905 1.1 agc case 'e': 1906 1.1 agc defaultfield = 1; 1907 1.125 mlelstv memsave(&up->u_expire, optarg, strlen(optarg)); 1908 1.1 agc break; 1909 1.1 agc case 'f': 1910 1.1 agc defaultfield = 1; 1911 1.125 mlelstv memsave(&up->u_inactive, optarg, strlen(optarg)); 1912 1.1 agc break; 1913 1.1 agc case 'g': 1914 1.1 agc defaultfield = 1; 1915 1.125 mlelstv memsave(&up->u_primgrp, optarg, strlen(optarg)); 1916 1.1 agc break; 1917 1.1 agc case 'k': 1918 1.50 agc defaultfield = 1; 1919 1.125 mlelstv memsave(&up->u_skeldir, optarg, strlen(optarg)); 1920 1.1 agc break; 1921 1.42 christos #ifdef EXTENSIONS 1922 1.42 christos case 'L': 1923 1.42 christos defaultfield = 1; 1924 1.125 mlelstv memsave(&up->u_class, optarg, strlen(optarg)); 1925 1.42 christos break; 1926 1.42 christos #endif 1927 1.1 agc case 'm': 1928 1.125 mlelstv up->u_flags |= F_MKDIR; 1929 1.1 agc break; 1930 1.115 elad #ifdef EXTENSIONS 1931 1.115 elad case 'M': 1932 1.115 elad defaultfield = 1; 1933 1.125 mlelstv up->u_homeperm = strtoul(optarg, NULL, 8); 1934 1.115 elad break; 1935 1.115 elad #endif 1936 1.1 agc case 'o': 1937 1.125 mlelstv up->u_flags |= F_DUPUID; 1938 1.1 agc break; 1939 1.1 agc #ifdef EXTENSIONS 1940 1.1 agc case 'p': 1941 1.125 mlelstv memsave(&up->u_password, optarg, strlen(optarg)); 1942 1.1 agc break; 1943 1.1 agc #endif 1944 1.1 agc #ifdef EXTENSIONS 1945 1.1 agc case 'r': 1946 1.1 agc defaultfield = 1; 1947 1.125 mlelstv (void)save_range(&up->u_r, optarg); 1948 1.1 agc break; 1949 1.1 agc #endif 1950 1.1 agc case 's': 1951 1.125 mlelstv up->u_flags |= F_SHELL; 1952 1.1 agc defaultfield = 1; 1953 1.125 mlelstv memsave(&up->u_shell, optarg, strlen(optarg)); 1954 1.1 agc break; 1955 1.1 agc case 'u': 1956 1.125 mlelstv up->u_uid = check_numeric(optarg, "uid"); 1957 1.1 agc break; 1958 1.1 agc #ifdef EXTENSIONS 1959 1.1 agc case 'v': 1960 1.1 agc verbose = 1; 1961 1.1 agc break; 1962 1.1 agc #endif 1963 1.69 agc default: 1964 1.69 agc usermgmt_usage("useradd"); 1965 1.69 agc /* NOTREACHED */ 1966 1.1 agc } 1967 1.1 agc } 1968 1.1 agc if (bigD) { 1969 1.1 agc if (defaultfield) { 1970 1.9 agc checkeuid(); 1971 1.125 mlelstv return setdefaults(up) ? EXIT_SUCCESS : EXIT_FAILURE; 1972 1.1 agc } 1973 1.125 mlelstv (void)printf("group\t\t%s\n", up->u_primgrp); 1974 1.125 mlelstv (void)printf("base_dir\t%s\n", up->u_basedir); 1975 1.125 mlelstv (void)printf("skel_dir\t%s\n", up->u_skeldir); 1976 1.125 mlelstv (void)printf("shell\t\t%s\n", up->u_shell); 1977 1.125 mlelstv #ifdef EXTENSIONS 1978 1.125 mlelstv (void)printf("class\t\t%s\n", up->u_class); 1979 1.125 mlelstv (void)printf("homeperm\t0%o\n", up->u_homeperm); 1980 1.125 mlelstv #endif 1981 1.125 mlelstv (void)printf("inactive\t%s\n", (up->u_inactive == NULL) ? 1982 1.125 mlelstv UNSET_INACTIVE : up->u_inactive); 1983 1.125 mlelstv (void)printf("expire\t\t%s\n", (up->u_expire == NULL) ? 1984 1.125 mlelstv UNSET_EXPIRY : up->u_expire); 1985 1.1 agc #ifdef EXTENSIONS 1986 1.125 mlelstv for (i = 0 ; i < up->u_rc ; i++) { 1987 1.82 christos (void)printf("range\t\t%d..%d\n", 1988 1.125 mlelstv up->u_rv[i].r_from, up->u_rv[i].r_to); 1989 1.1 agc } 1990 1.1 agc #endif 1991 1.1 agc return EXIT_SUCCESS; 1992 1.1 agc } 1993 1.39 itojun argc -= optind; 1994 1.39 itojun argv += optind; 1995 1.39 itojun if (argc != 1) { 1996 1.9 agc usermgmt_usage("useradd"); 1997 1.1 agc } 1998 1.9 agc checkeuid(); 1999 1.59 agc openlog("useradd", LOG_PID, LOG_USER); 2000 1.125 mlelstv return adduser(*argv, up) ? EXIT_SUCCESS : EXIT_FAILURE; 2001 1.1 agc } 2002 1.1 agc 2003 1.1 agc #ifdef EXTENSIONS 2004 1.75 agc #define MOD_OPT_EXTENSIONS "p:vL:S" 2005 1.1 agc #else 2006 1.96 christos #define MOD_OPT_EXTENSIONS 2007 1.1 agc #endif 2008 1.1 agc 2009 1.9 agc int 2010 1.1 agc usermod(int argc, char **argv) 2011 1.1 agc { 2012 1.125 mlelstv def_t def; 2013 1.125 mlelstv user_t *up = &def.user; 2014 1.1 agc char newuser[MaxUserNameLen + 1]; 2015 1.26 simonb int c, have_new_user; 2016 1.1 agc 2017 1.82 christos (void)memset(newuser, 0, sizeof(newuser)); 2018 1.125 mlelstv read_defaults(&def); 2019 1.1 agc have_new_user = 0; 2020 1.125 mlelstv up->u_locked = -1; 2021 1.87 christos while ((c = getopt(argc, argv, "C:FG:c:d:e:f:g:l:mos:u:" 2022 1.82 christos MOD_OPT_EXTENSIONS)) != -1) { 2023 1.1 agc switch(c) { 2024 1.1 agc case 'G': 2025 1.125 mlelstv while (up->u_groupc < NGROUPS_MAX && 2026 1.125 mlelstv (up->u_groupv[up->u_groupc] = 2027 1.106 dmcmahil strsep(&optarg, ",")) != NULL) { 2028 1.125 mlelstv if (up->u_groupv[up->u_groupc][0] != 0) { 2029 1.125 mlelstv up->u_groupc++; 2030 1.20 agc } 2031 1.20 agc } 2032 1.20 agc if (optarg != NULL) { 2033 1.82 christos warnx("Truncated list of secondary groups " 2034 1.82 christos "to %d entries", NGROUPS_MAX); 2035 1.20 agc } 2036 1.125 mlelstv up->u_flags |= F_SECGROUP; 2037 1.1 agc break; 2038 1.75 agc #ifdef EXTENSIONS 2039 1.75 agc case 'S': 2040 1.125 mlelstv up->u_allow_samba = 1; 2041 1.75 agc break; 2042 1.75 agc #endif 2043 1.1 agc case 'c': 2044 1.125 mlelstv memsave(&up->u_comment, optarg, strlen(optarg)); 2045 1.125 mlelstv up->u_flags |= F_COMMENT; 2046 1.1 agc break; 2047 1.82 christos case 'C': 2048 1.93 agc if (strcasecmp(optarg, "yes") == 0) { 2049 1.125 mlelstv up->u_locked = LOCK; 2050 1.93 agc } else if (strcasecmp(optarg, "no") == 0) { 2051 1.125 mlelstv up->u_locked = UNLOCK; 2052 1.93 agc } else { 2053 1.82 christos /* No idea. */ 2054 1.93 agc errx(EXIT_FAILURE, 2055 1.93 agc "Please type 'yes' or 'no'"); 2056 1.93 agc } 2057 1.96 christos break; 2058 1.87 christos case 'F': 2059 1.125 mlelstv memsave(&up->u_inactive, "-1", strlen("-1")); 2060 1.125 mlelstv up->u_flags |= F_INACTIVE; 2061 1.87 christos break; 2062 1.1 agc case 'd': 2063 1.125 mlelstv memsave(&up->u_home, optarg, strlen(optarg)); 2064 1.125 mlelstv up->u_flags |= F_HOMEDIR; 2065 1.1 agc break; 2066 1.1 agc case 'e': 2067 1.125 mlelstv memsave(&up->u_expire, optarg, strlen(optarg)); 2068 1.125 mlelstv up->u_flags |= F_EXPIRE; 2069 1.1 agc break; 2070 1.1 agc case 'f': 2071 1.125 mlelstv memsave(&up->u_inactive, optarg, strlen(optarg)); 2072 1.125 mlelstv up->u_flags |= F_INACTIVE; 2073 1.1 agc break; 2074 1.1 agc case 'g': 2075 1.125 mlelstv memsave(&up->u_primgrp, optarg, strlen(optarg)); 2076 1.125 mlelstv up->u_flags |= F_GROUP; 2077 1.1 agc break; 2078 1.1 agc case 'l': 2079 1.82 christos (void)strlcpy(newuser, optarg, sizeof(newuser)); 2080 1.1 agc have_new_user = 1; 2081 1.125 mlelstv up->u_flags |= F_USERNAME; 2082 1.1 agc break; 2083 1.42 christos #ifdef EXTENSIONS 2084 1.42 christos case 'L': 2085 1.125 mlelstv memsave(&up->u_class, optarg, strlen(optarg)); 2086 1.125 mlelstv up->u_flags |= F_CLASS; 2087 1.42 christos break; 2088 1.42 christos #endif 2089 1.1 agc case 'm': 2090 1.125 mlelstv up->u_flags |= F_MKDIR; 2091 1.1 agc break; 2092 1.1 agc case 'o': 2093 1.125 mlelstv up->u_flags |= F_DUPUID; 2094 1.1 agc break; 2095 1.1 agc #ifdef EXTENSIONS 2096 1.1 agc case 'p': 2097 1.125 mlelstv memsave(&up->u_password, optarg, strlen(optarg)); 2098 1.125 mlelstv up->u_flags |= F_PASSWORD; 2099 1.1 agc break; 2100 1.1 agc #endif 2101 1.1 agc case 's': 2102 1.125 mlelstv memsave(&up->u_shell, optarg, strlen(optarg)); 2103 1.125 mlelstv up->u_flags |= F_SHELL; 2104 1.1 agc break; 2105 1.1 agc case 'u': 2106 1.125 mlelstv up->u_uid = check_numeric(optarg, "uid"); 2107 1.125 mlelstv up->u_flags |= F_UID; 2108 1.1 agc break; 2109 1.1 agc #ifdef EXTENSIONS 2110 1.1 agc case 'v': 2111 1.1 agc verbose = 1; 2112 1.1 agc break; 2113 1.1 agc #endif 2114 1.69 agc default: 2115 1.69 agc usermgmt_usage("usermod"); 2116 1.69 agc /* NOTREACHED */ 2117 1.1 agc } 2118 1.1 agc } 2119 1.125 mlelstv if ((up->u_flags & F_MKDIR) && !(up->u_flags & F_HOMEDIR) && 2120 1.125 mlelstv !(up->u_flags & F_USERNAME)) { 2121 1.82 christos warnx("Option 'm' useless without 'd' or 'l' -- ignored"); 2122 1.125 mlelstv up->u_flags &= ~F_MKDIR; 2123 1.35 wiz } 2124 1.39 itojun argc -= optind; 2125 1.39 itojun argv += optind; 2126 1.39 itojun if (argc != 1) { 2127 1.9 agc usermgmt_usage("usermod"); 2128 1.1 agc } 2129 1.16 soren checkeuid(); 2130 1.59 agc openlog("usermod", LOG_PID, LOG_USER); 2131 1.125 mlelstv return moduser(*argv, (have_new_user) ? newuser : *argv, up, 2132 1.125 mlelstv up->u_allow_samba) ? EXIT_SUCCESS : EXIT_FAILURE; 2133 1.1 agc } 2134 1.1 agc 2135 1.1 agc #ifdef EXTENSIONS 2136 1.75 agc #define DEL_OPT_EXTENSIONS "Dp:vS" 2137 1.1 agc #else 2138 1.96 christos #define DEL_OPT_EXTENSIONS 2139 1.1 agc #endif 2140 1.1 agc 2141 1.9 agc int 2142 1.1 agc userdel(int argc, char **argv) 2143 1.1 agc { 2144 1.1 agc struct passwd *pwp; 2145 1.125 mlelstv def_t def; 2146 1.125 mlelstv user_t *up = &def.user; 2147 1.1 agc char password[PasswordLength + 1]; 2148 1.1 agc int defaultfield; 2149 1.1 agc int rmhome; 2150 1.1 agc int bigD; 2151 1.1 agc int c; 2152 1.1 agc 2153 1.125 mlelstv read_defaults(&def); 2154 1.1 agc defaultfield = bigD = rmhome = 0; 2155 1.1 agc while ((c = getopt(argc, argv, "r" DEL_OPT_EXTENSIONS)) != -1) { 2156 1.1 agc switch(c) { 2157 1.1 agc #ifdef EXTENSIONS 2158 1.1 agc case 'D': 2159 1.1 agc bigD = 1; 2160 1.1 agc break; 2161 1.1 agc #endif 2162 1.1 agc #ifdef EXTENSIONS 2163 1.75 agc case 'S': 2164 1.125 mlelstv up->u_allow_samba = 1; 2165 1.75 agc break; 2166 1.75 agc #endif 2167 1.75 agc #ifdef EXTENSIONS 2168 1.1 agc case 'p': 2169 1.1 agc defaultfield = 1; 2170 1.125 mlelstv up->u_preserve = (strcmp(optarg, "true") == 0) ? 1 : 2171 1.1 agc (strcmp(optarg, "yes") == 0) ? 1 : 2172 1.1 agc atoi(optarg); 2173 1.1 agc break; 2174 1.1 agc #endif 2175 1.1 agc case 'r': 2176 1.1 agc rmhome = 1; 2177 1.1 agc break; 2178 1.1 agc #ifdef EXTENSIONS 2179 1.1 agc case 'v': 2180 1.1 agc verbose = 1; 2181 1.1 agc break; 2182 1.1 agc #endif 2183 1.69 agc default: 2184 1.69 agc usermgmt_usage("userdel"); 2185 1.69 agc /* NOTREACHED */ 2186 1.1 agc } 2187 1.1 agc } 2188 1.1 agc #ifdef EXTENSIONS 2189 1.1 agc if (bigD) { 2190 1.1 agc if (defaultfield) { 2191 1.9 agc checkeuid(); 2192 1.125 mlelstv return setdefaults(up) ? EXIT_SUCCESS : EXIT_FAILURE; 2193 1.1 agc } 2194 1.125 mlelstv (void)printf("preserve\t%s\n", (up->u_preserve) ? "true" : 2195 1.82 christos "false"); 2196 1.1 agc return EXIT_SUCCESS; 2197 1.1 agc } 2198 1.1 agc #endif 2199 1.39 itojun argc -= optind; 2200 1.39 itojun argv += optind; 2201 1.39 itojun if (argc != 1) { 2202 1.9 agc usermgmt_usage("userdel"); 2203 1.1 agc } 2204 1.9 agc checkeuid(); 2205 1.39 itojun if ((pwp = getpwnam(*argv)) == NULL) { 2206 1.39 itojun warnx("No such user `%s'", *argv); 2207 1.1 agc return EXIT_FAILURE; 2208 1.1 agc } 2209 1.93 agc if (rmhome) { 2210 1.96 christos (void)removehomedir(pwp); 2211 1.93 agc } 2212 1.125 mlelstv if (up->u_preserve) { 2213 1.125 mlelstv up->u_flags |= F_SHELL; 2214 1.129 dholland memsave(&up->u_shell, _PATH_SBIN_NOLOGIN, 2215 1.129 dholland strlen(_PATH_SBIN_NOLOGIN)); 2216 1.82 christos (void)memset(password, '*', DES_Len); 2217 1.65 agc password[DES_Len] = 0; 2218 1.125 mlelstv memsave(&up->u_password, password, strlen(password)); 2219 1.125 mlelstv up->u_flags |= F_PASSWORD; 2220 1.59 agc openlog("userdel", LOG_PID, LOG_USER); 2221 1.125 mlelstv return moduser(*argv, *argv, up, up->u_allow_samba) ? 2222 1.82 christos EXIT_SUCCESS : EXIT_FAILURE; 2223 1.53 agc } 2224 1.53 agc if (!rm_user_from_groups(*argv)) { 2225 1.53 agc return 0; 2226 1.1 agc } 2227 1.59 agc openlog("userdel", LOG_PID, LOG_USER); 2228 1.125 mlelstv return moduser(*argv, *argv, NULL, up->u_allow_samba) ? 2229 1.82 christos EXIT_SUCCESS : EXIT_FAILURE; 2230 1.1 agc } 2231 1.1 agc 2232 1.1 agc #ifdef EXTENSIONS 2233 1.77 jmmv #define GROUP_ADD_OPT_EXTENSIONS "r:v" 2234 1.1 agc #else 2235 1.96 christos #define GROUP_ADD_OPT_EXTENSIONS 2236 1.1 agc #endif 2237 1.1 agc 2238 1.1 agc /* add a group */ 2239 1.1 agc int 2240 1.1 agc groupadd(int argc, char **argv) 2241 1.1 agc { 2242 1.125 mlelstv def_t def; 2243 1.125 mlelstv group_t *gp = &def.group; 2244 1.1 agc int dupgid; 2245 1.1 agc int gid; 2246 1.1 agc int c; 2247 1.1 agc 2248 1.1 agc gid = -1; 2249 1.1 agc dupgid = 0; 2250 1.125 mlelstv read_defaults(&def); 2251 1.1 agc while ((c = getopt(argc, argv, "g:o" GROUP_ADD_OPT_EXTENSIONS)) != -1) { 2252 1.1 agc switch(c) { 2253 1.1 agc case 'g': 2254 1.82 christos gid = check_numeric(optarg, "gid"); 2255 1.1 agc break; 2256 1.1 agc case 'o': 2257 1.1 agc dupgid = 1; 2258 1.1 agc break; 2259 1.1 agc #ifdef EXTENSIONS 2260 1.77 jmmv case 'r': 2261 1.125 mlelstv (void)save_range(&gp->g_r, optarg); 2262 1.77 jmmv break; 2263 1.1 agc case 'v': 2264 1.1 agc verbose = 1; 2265 1.1 agc break; 2266 1.1 agc #endif 2267 1.69 agc default: 2268 1.69 agc usermgmt_usage("groupadd"); 2269 1.69 agc /* NOTREACHED */ 2270 1.1 agc } 2271 1.1 agc } 2272 1.39 itojun argc -= optind; 2273 1.39 itojun argv += optind; 2274 1.39 itojun if (argc != 1) { 2275 1.9 agc usermgmt_usage("groupadd"); 2276 1.1 agc } 2277 1.125 mlelstv if (gp->g_rc == 0) { 2278 1.125 mlelstv gp->g_rv[gp->g_rc].r_from = DEF_LOWUID; 2279 1.125 mlelstv gp->g_rv[gp->g_rc].r_to = DEF_HIGHUID; 2280 1.125 mlelstv gp->g_rc += 1; 2281 1.125 mlelstv } 2282 1.125 mlelstv gp->g_defrc = gp->g_rc; 2283 1.16 soren checkeuid(); 2284 1.125 mlelstv if (gid == -1) { 2285 1.125 mlelstv int got_id = 0; 2286 1.125 mlelstv int i; 2287 1.125 mlelstv 2288 1.125 mlelstv /* 2289 1.125 mlelstv * Look for a free GID in the command line ranges (if any). 2290 1.125 mlelstv * These start after the ranges specified in the config file. 2291 1.125 mlelstv */ 2292 1.125 mlelstv for (i = gp->g_defrc; !got_id && i < gp->g_rc ; i++) { 2293 1.125 mlelstv got_id = getnextgid(&gid, 2294 1.125 mlelstv gp->g_rv[i].r_from, gp->g_rv[i].r_to); 2295 1.125 mlelstv } 2296 1.125 mlelstv /* 2297 1.125 mlelstv * If there were no free GIDs in the command line ranges, 2298 1.125 mlelstv * try the ranges from the config file (there will always 2299 1.125 mlelstv * be at least one default). 2300 1.125 mlelstv */ 2301 1.125 mlelstv for (i = 0; !got_id && i < gp->g_defrc; i++) { 2302 1.125 mlelstv got_id = getnextgid(&gid, 2303 1.125 mlelstv gp->g_rv[i].r_from, gp->g_rv[i].r_to); 2304 1.125 mlelstv } 2305 1.125 mlelstv if (!got_id) 2306 1.125 mlelstv errx(EXIT_FAILURE, "Can't add group: can't get next gid"); 2307 1.1 agc } 2308 1.24 simonb if (!dupgid && getgrgid((gid_t) gid) != NULL) { 2309 1.82 christos errx(EXIT_FAILURE, "Can't add group: gid %d is a duplicate", 2310 1.82 christos gid); 2311 1.1 agc } 2312 1.39 itojun if (!valid_group(*argv)) { 2313 1.82 christos warnx("Invalid group name `%s'", *argv); 2314 1.1 agc } 2315 1.59 agc openlog("groupadd", LOG_PID, LOG_USER); 2316 1.96 christos if (!creategid(*argv, gid, "")) 2317 1.96 christos exit(EXIT_FAILURE); 2318 1.96 christos 2319 1.1 agc return EXIT_SUCCESS; 2320 1.1 agc } 2321 1.1 agc 2322 1.1 agc #ifdef EXTENSIONS 2323 1.1 agc #define GROUP_DEL_OPT_EXTENSIONS "v" 2324 1.1 agc #else 2325 1.96 christos #define GROUP_DEL_OPT_EXTENSIONS 2326 1.1 agc #endif 2327 1.1 agc 2328 1.1 agc /* remove a group */ 2329 1.1 agc int 2330 1.1 agc groupdel(int argc, char **argv) 2331 1.1 agc { 2332 1.1 agc int c; 2333 1.1 agc 2334 1.1 agc while ((c = getopt(argc, argv, "" GROUP_DEL_OPT_EXTENSIONS)) != -1) { 2335 1.1 agc switch(c) { 2336 1.1 agc #ifdef EXTENSIONS 2337 1.1 agc case 'v': 2338 1.1 agc verbose = 1; 2339 1.1 agc break; 2340 1.1 agc #endif 2341 1.69 agc default: 2342 1.69 agc usermgmt_usage("groupdel"); 2343 1.69 agc /* NOTREACHED */ 2344 1.1 agc } 2345 1.1 agc } 2346 1.39 itojun argc -= optind; 2347 1.39 itojun argv += optind; 2348 1.39 itojun if (argc != 1) { 2349 1.9 agc usermgmt_usage("groupdel"); 2350 1.1 agc } 2351 1.80 agc if (getgrnam(*argv) == NULL) { 2352 1.80 agc errx(EXIT_FAILURE, "No such group `%s'", *argv); 2353 1.80 agc } 2354 1.16 soren checkeuid(); 2355 1.59 agc openlog("groupdel", LOG_PID, LOG_USER); 2356 1.96 christos if (!modify_gid(*argv, NULL)) 2357 1.96 christos exit(EXIT_FAILURE); 2358 1.96 christos 2359 1.1 agc return EXIT_SUCCESS; 2360 1.1 agc } 2361 1.1 agc 2362 1.1 agc #ifdef EXTENSIONS 2363 1.1 agc #define GROUP_MOD_OPT_EXTENSIONS "v" 2364 1.1 agc #else 2365 1.96 christos #define GROUP_MOD_OPT_EXTENSIONS 2366 1.1 agc #endif 2367 1.1 agc 2368 1.1 agc /* modify a group */ 2369 1.1 agc int 2370 1.1 agc groupmod(int argc, char **argv) 2371 1.1 agc { 2372 1.1 agc struct group *grp; 2373 1.1 agc char buf[MaxEntryLen]; 2374 1.1 agc char *newname; 2375 1.1 agc char **cpp; 2376 1.1 agc int dupgid; 2377 1.1 agc int gid; 2378 1.1 agc int cc; 2379 1.1 agc int c; 2380 1.1 agc 2381 1.1 agc gid = -1; 2382 1.1 agc dupgid = 0; 2383 1.15 soren newname = NULL; 2384 1.1 agc while ((c = getopt(argc, argv, "g:on:" GROUP_MOD_OPT_EXTENSIONS)) != -1) { 2385 1.1 agc switch(c) { 2386 1.1 agc case 'g': 2387 1.82 christos gid = check_numeric(optarg, "gid"); 2388 1.1 agc break; 2389 1.1 agc case 'o': 2390 1.1 agc dupgid = 1; 2391 1.1 agc break; 2392 1.1 agc case 'n': 2393 1.1 agc memsave(&newname, optarg, strlen(optarg)); 2394 1.1 agc break; 2395 1.1 agc #ifdef EXTENSIONS 2396 1.1 agc case 'v': 2397 1.1 agc verbose = 1; 2398 1.1 agc break; 2399 1.1 agc #endif 2400 1.69 agc default: 2401 1.69 agc usermgmt_usage("groupmod"); 2402 1.69 agc /* NOTREACHED */ 2403 1.1 agc } 2404 1.1 agc } 2405 1.39 itojun argc -= optind; 2406 1.39 itojun argv += optind; 2407 1.39 itojun if (argc != 1) { 2408 1.9 agc usermgmt_usage("groupmod"); 2409 1.1 agc } 2410 1.16 soren checkeuid(); 2411 1.15 soren if (gid < 0 && newname == NULL) { 2412 1.81 agc errx(EXIT_FAILURE, "Nothing to change"); 2413 1.1 agc } 2414 1.1 agc if (dupgid && gid < 0) { 2415 1.81 agc errx(EXIT_FAILURE, "Duplicate which gid?"); 2416 1.1 agc } 2417 1.122 mike if (!dupgid && getgrgid((gid_t) gid) != NULL) { 2418 1.122 mike errx(EXIT_FAILURE, "Can't modify group: gid %d is a duplicate", 2419 1.122 mike gid); 2420 1.122 mike } 2421 1.81 agc if ((grp = find_group_info(*argv)) == NULL) { 2422 1.82 christos errx(EXIT_FAILURE, "Can't find group `%s' to modify", *argv); 2423 1.58 agc } 2424 1.58 agc if (!is_local(*argv, _PATH_GROUP)) { 2425 1.58 agc errx(EXIT_FAILURE, "Group `%s' must be a local group", *argv); 2426 1.1 agc } 2427 1.15 soren if (newname != NULL && !valid_group(newname)) { 2428 1.82 christos warnx("Invalid group name `%s'", newname); 2429 1.1 agc } 2430 1.1 agc cc = snprintf(buf, sizeof(buf), "%s:%s:%d:", 2431 1.1 agc (newname) ? newname : grp->gr_name, 2432 1.1 agc grp->gr_passwd, 2433 1.1 agc (gid < 0) ? grp->gr_gid : gid); 2434 1.1 agc for (cpp = grp->gr_mem ; *cpp && cc < sizeof(buf) ; cpp++) { 2435 1.1 agc cc += snprintf(&buf[cc], sizeof(buf) - cc, "%s%s", *cpp, 2436 1.15 soren (cpp[1] == NULL) ? "" : ","); 2437 1.1 agc } 2438 1.37 lukem cc += snprintf(&buf[cc], sizeof(buf) - cc, "\n"); 2439 1.109 jnemeth if (newname != NULL) 2440 1.109 jnemeth free(newname); 2441 1.59 agc openlog("groupmod", LOG_PID, LOG_USER); 2442 1.96 christos if (!modify_gid(*argv, buf)) 2443 1.96 christos exit(EXIT_FAILURE); 2444 1.96 christos 2445 1.1 agc return EXIT_SUCCESS; 2446 1.1 agc } 2447 1.1 agc 2448 1.9 agc #ifdef EXTENSIONS 2449 1.9 agc /* display user information */ 2450 1.9 agc int 2451 1.9 agc userinfo(int argc, char **argv) 2452 1.9 agc { 2453 1.9 agc struct passwd *pwp; 2454 1.9 agc struct group *grp; 2455 1.9 agc char buf[MaxEntryLen]; 2456 1.9 agc char **cpp; 2457 1.9 agc int exists; 2458 1.9 agc int cc; 2459 1.9 agc int i; 2460 1.1 agc 2461 1.9 agc exists = 0; 2462 1.112 pavel buf[0] = '\0'; 2463 1.119 reed while ((i = getopt(argc, argv, "e")) != -1) { 2464 1.9 agc switch(i) { 2465 1.9 agc case 'e': 2466 1.9 agc exists = 1; 2467 1.9 agc break; 2468 1.69 agc default: 2469 1.69 agc usermgmt_usage("userinfo"); 2470 1.69 agc /* NOTREACHED */ 2471 1.9 agc } 2472 1.9 agc } 2473 1.39 itojun argc -= optind; 2474 1.39 itojun argv += optind; 2475 1.39 itojun if (argc != 1) { 2476 1.9 agc usermgmt_usage("userinfo"); 2477 1.9 agc } 2478 1.39 itojun pwp = find_user_info(*argv); 2479 1.9 agc if (exists) { 2480 1.9 agc exit((pwp) ? EXIT_SUCCESS : EXIT_FAILURE); 2481 1.9 agc } 2482 1.24 simonb if (pwp == NULL) { 2483 1.82 christos errx(EXIT_FAILURE, "Can't find user `%s'", *argv); 2484 1.9 agc } 2485 1.82 christos (void)printf("login\t%s\n", pwp->pw_name); 2486 1.82 christos (void)printf("passwd\t%s\n", pwp->pw_passwd); 2487 1.82 christos (void)printf("uid\t%d\n", pwp->pw_uid); 2488 1.24 simonb for (cc = 0 ; (grp = getgrent()) != NULL ; ) { 2489 1.9 agc for (cpp = grp->gr_mem ; *cpp ; cpp++) { 2490 1.82 christos if (strcmp(*cpp, *argv) == 0 && 2491 1.82 christos grp->gr_gid != pwp->pw_gid) { 2492 1.82 christos cc += snprintf(&buf[cc], sizeof(buf) - cc, 2493 1.82 christos "%s ", grp->gr_name); 2494 1.9 agc } 2495 1.9 agc } 2496 1.9 agc } 2497 1.24 simonb if ((grp = getgrgid(pwp->pw_gid)) == NULL) { 2498 1.82 christos (void)printf("groups\t%d %s\n", pwp->pw_gid, buf); 2499 1.9 agc } else { 2500 1.82 christos (void)printf("groups\t%s %s\n", grp->gr_name, buf); 2501 1.9 agc } 2502 1.92 christos (void)printf("change\t%s", pwp->pw_change > 0 ? 2503 1.92 christos ctime(&pwp->pw_change) : pwp->pw_change == -1 ? 2504 1.92 christos "NEXT LOGIN\n" : "NEVER\n"); 2505 1.82 christos (void)printf("class\t%s\n", pwp->pw_class); 2506 1.82 christos (void)printf("gecos\t%s\n", pwp->pw_gecos); 2507 1.82 christos (void)printf("dir\t%s\n", pwp->pw_dir); 2508 1.82 christos (void)printf("shell\t%s\n", pwp->pw_shell); 2509 1.82 christos (void)printf("expire\t%s", pwp->pw_expire ? 2510 1.82 christos ctime(&pwp->pw_expire) : "NEVER\n"); 2511 1.9 agc return EXIT_SUCCESS; 2512 1.9 agc } 2513 1.9 agc #endif 2514 1.1 agc 2515 1.9 agc #ifdef EXTENSIONS 2516 1.9 agc /* display user information */ 2517 1.1 agc int 2518 1.9 agc groupinfo(int argc, char **argv) 2519 1.1 agc { 2520 1.9 agc struct group *grp; 2521 1.9 agc char **cpp; 2522 1.9 agc int exists; 2523 1.9 agc int i; 2524 1.1 agc 2525 1.9 agc exists = 0; 2526 1.9 agc while ((i = getopt(argc, argv, "ev")) != -1) { 2527 1.9 agc switch(i) { 2528 1.9 agc case 'e': 2529 1.9 agc exists = 1; 2530 1.9 agc break; 2531 1.9 agc case 'v': 2532 1.9 agc verbose = 1; 2533 1.9 agc break; 2534 1.69 agc default: 2535 1.69 agc usermgmt_usage("groupinfo"); 2536 1.69 agc /* NOTREACHED */ 2537 1.9 agc } 2538 1.9 agc } 2539 1.39 itojun argc -= optind; 2540 1.39 itojun argv += optind; 2541 1.39 itojun if (argc != 1) { 2542 1.9 agc usermgmt_usage("groupinfo"); 2543 1.9 agc } 2544 1.39 itojun grp = find_group_info(*argv); 2545 1.9 agc if (exists) { 2546 1.9 agc exit((grp) ? EXIT_SUCCESS : EXIT_FAILURE); 2547 1.9 agc } 2548 1.24 simonb if (grp == NULL) { 2549 1.82 christos errx(EXIT_FAILURE, "Can't find group `%s'", *argv); 2550 1.9 agc } 2551 1.82 christos (void)printf("name\t%s\n", grp->gr_name); 2552 1.82 christos (void)printf("passwd\t%s\n", grp->gr_passwd); 2553 1.82 christos (void)printf("gid\t%d\n", grp->gr_gid); 2554 1.82 christos (void)printf("members\t"); 2555 1.9 agc for (cpp = grp->gr_mem ; *cpp ; cpp++) { 2556 1.82 christos (void)printf("%s", *cpp); 2557 1.93 agc if (*(cpp + 1)) { 2558 1.81 agc (void) printf(", "); 2559 1.93 agc } 2560 1.9 agc } 2561 1.82 christos (void)fputc('\n', stdout); 2562 1.9 agc return EXIT_SUCCESS; 2563 1.1 agc } 2564 1.9 agc #endif 2565