]> git.cameronkatri.com Git - pw-darwin.git/blob - pw/pw_user.c
Remove '-q' support for pw [user|group] next
[pw-darwin.git] / pw / pw_user.c
1 /*-
2 * Copyright (C) 1996
3 * David L. Nugent. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 */
27
28 #ifndef lint
29 static const char rcsid[] =
30 "$FreeBSD$";
31 #endif /* not lint */
32
33 #include <ctype.h>
34 #include <err.h>
35 #include <fcntl.h>
36 #include <sys/param.h>
37 #include <dirent.h>
38 #include <paths.h>
39 #include <termios.h>
40 #include <sys/types.h>
41 #include <sys/time.h>
42 #include <sys/resource.h>
43 #include <login_cap.h>
44 #include <pwd.h>
45 #include <grp.h>
46 #include <libutil.h>
47 #include "pw.h"
48 #include "bitmap.h"
49
50 #define LOGNAMESIZE (MAXLOGNAME-1)
51
52 static char locked_str[] = "*LOCKED*";
53
54 static int delete_user(struct userconf *cnf, struct passwd *pwd,
55 char *name, int delete, int mode);
56 static int print_user(struct passwd * pwd);
57 static uid_t pw_uidpolicy(struct userconf * cnf, long id);
58 static uid_t pw_gidpolicy(struct cargs * args, char *nam, gid_t prefer);
59 static time_t pw_pwdpolicy(struct userconf * cnf, struct cargs * args);
60 static time_t pw_exppolicy(struct userconf * cnf, struct cargs * args);
61 static char *pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user);
62 static char *pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell);
63 static char *pw_password(struct userconf * cnf, struct cargs * args, char const * user);
64 static char *shell_path(char const * path, char *shells[], char *sh);
65 static void rmat(uid_t uid);
66 static void rmopie(char const * name);
67
68 static void
69 create_and_populate_homedir(int mode, struct passwd *pwd)
70 {
71 char *homedir, *dotdir;
72 struct userconf *cnf = conf.userconf;
73
74 homedir = dotdir = NULL;
75
76 if (conf.rootdir[0] != '\0') {
77 asprintf(&homedir, "%s/%s", conf.rootdir, pwd->pw_dir);
78 if (homedir == NULL)
79 errx(EX_OSERR, "out of memory");
80 asprintf(&dotdir, "%s/%s", conf.rootdir, cnf->dotdir);
81 }
82
83 copymkdir(homedir ? homedir : pwd->pw_dir, dotdir ? dotdir: cnf->dotdir,
84 cnf->homemode, pwd->pw_uid, pwd->pw_gid);
85 pw_log(cnf, mode, W_USER, "%s(%u) home %s made", pwd->pw_name,
86 pwd->pw_uid, pwd->pw_dir);
87 }
88
89 /*-
90 * -C config configuration file
91 * -q quiet operation
92 * -n name login name
93 * -u uid user id
94 * -c comment user name/comment
95 * -d directory home directory
96 * -e date account expiry date
97 * -p date password expiry date
98 * -g grp primary group
99 * -G grp1,grp2 additional groups
100 * -m [ -k dir ] create and set up home
101 * -s shell name of login shell
102 * -o duplicate uid ok
103 * -L class user class
104 * -l name new login name
105 * -h fd password filehandle
106 * -H fd encrypted password filehandle
107 * -F force print or add
108 * Setting defaults:
109 * -D set user defaults
110 * -b dir default home root dir
111 * -e period default expiry period
112 * -p period default password change period
113 * -g group default group
114 * -G grp1,grp2.. default additional groups
115 * -L class default login class
116 * -k dir default home skeleton
117 * -s shell default shell
118 * -w method default password method
119 */
120
121 int
122 pw_user(int mode, char *name, long id, struct cargs * args)
123 {
124 int rc, edited = 0;
125 char *p = NULL;
126 char *passtmp;
127 struct carg *arg;
128 struct passwd *pwd = NULL;
129 struct group *grp;
130 struct stat st;
131 struct userconf *cnf;
132 char line[_PASSWORD_LEN+1];
133 char path[MAXPATHLEN];
134 FILE *fp;
135 char *dmode_c;
136 void *set = NULL;
137
138 static struct passwd fakeuser =
139 {
140 NULL,
141 "*",
142 -1,
143 -1,
144 0,
145 "",
146 "User &",
147 "/nonexistent",
148 "/bin/sh",
149 0
150 #if defined(__FreeBSD__)
151 ,0
152 #endif
153 };
154
155 cnf = conf.userconf;
156
157 /*
158 * With M_NEXT, we only need to return the
159 * next uid to stdout
160 */
161 if (mode == M_NEXT) {
162 printf("%u:", pw_uidpolicy(cnf, id));
163 pw_group(mode, name, -1, args);
164 return (EXIT_SUCCESS);
165 }
166
167 /*
168 * We can do all of the common legwork here
169 */
170
171 if ((arg = getarg(args, 'b')) != NULL) {
172 cnf->home = arg->val;
173 }
174
175 if ((arg = getarg(args, 'M')) != NULL) {
176 dmode_c = arg->val;
177 if ((set = setmode(dmode_c)) == NULL)
178 errx(EX_DATAERR, "invalid directory creation mode '%s'",
179 dmode_c);
180 cnf->homemode = getmode(set, _DEF_DIRMODE);
181 free(set);
182 }
183
184 /*
185 * If we'll need to use it or we're updating it,
186 * then create the base home directory if necessary
187 */
188 if (arg != NULL || getarg(args, 'm') != NULL) {
189 int l = strlen(cnf->home);
190
191 if (l > 1 && cnf->home[l-1] == '/') /* Shave off any trailing path delimiter */
192 cnf->home[--l] = '\0';
193
194 if (l < 2 || *cnf->home != '/') /* Check for absolute path name */
195 errx(EX_DATAERR, "invalid base directory for home '%s'", cnf->home);
196
197 if (stat(cnf->home, &st) == -1) {
198 char dbuf[MAXPATHLEN];
199
200 /*
201 * This is a kludge especially for Joerg :)
202 * If the home directory would be created in the root partition, then
203 * we really create it under /usr which is likely to have more space.
204 * But we create a symlink from cnf->home -> "/usr" -> cnf->home
205 */
206 if (strchr(cnf->home+1, '/') == NULL) {
207 snprintf(dbuf, MAXPATHLEN, "/usr%s", cnf->home);
208 if (mkdir(dbuf, _DEF_DIRMODE) != -1 || errno == EEXIST) {
209 chown(dbuf, 0, 0);
210 /*
211 * Skip first "/" and create symlink:
212 * /home -> usr/home
213 */
214 symlink(dbuf+1, cnf->home);
215 }
216 /* If this falls, fall back to old method */
217 }
218 strlcpy(dbuf, cnf->home, sizeof(dbuf));
219 p = dbuf;
220 if (stat(dbuf, &st) == -1) {
221 while ((p = strchr(p + 1, '/')) != NULL) {
222 *p = '\0';
223 if (stat(dbuf, &st) == -1) {
224 if (mkdir(dbuf, _DEF_DIRMODE) == -1)
225 goto direrr;
226 chown(dbuf, 0, 0);
227 } else if (!S_ISDIR(st.st_mode))
228 errx(EX_OSFILE, "'%s' (root home parent) is not a directory", dbuf);
229 *p = '/';
230 }
231 }
232 if (stat(dbuf, &st) == -1) {
233 if (mkdir(dbuf, _DEF_DIRMODE) == -1) {
234 direrr: err(EX_OSFILE, "mkdir '%s'", dbuf);
235 }
236 chown(dbuf, 0, 0);
237 }
238 } else if (!S_ISDIR(st.st_mode))
239 errx(EX_OSFILE, "root home `%s' is not a directory", cnf->home);
240 }
241
242 if ((arg = getarg(args, 'e')) != NULL)
243 cnf->expire_days = atoi(arg->val);
244
245 if ((arg = getarg(args, 'y')) != NULL)
246 cnf->nispasswd = arg->val;
247
248 if ((arg = getarg(args, 'p')) != NULL && arg->val)
249 cnf->password_days = atoi(arg->val);
250
251 if ((arg = getarg(args, 'g')) != NULL) {
252 if (!*(p = arg->val)) /* Handle empty group list specially */
253 cnf->default_group = "";
254 else {
255 if ((grp = GETGRNAM(p)) == NULL) {
256 if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL)
257 errx(EX_NOUSER, "group `%s' does not exist", p);
258 }
259 cnf->default_group = newstr(grp->gr_name);
260 }
261 }
262 if ((arg = getarg(args, 'L')) != NULL)
263 cnf->default_class = pw_checkname(arg->val, 0);
264
265 if ((arg = getarg(args, 'G')) != NULL && arg->val) {
266 int i = 0;
267
268 for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
269 if ((grp = GETGRNAM(p)) == NULL) {
270 if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL)
271 errx(EX_NOUSER, "group `%s' does not exist", p);
272 }
273 if (extendarray(&cnf->groups, &cnf->numgroups, i + 2) != -1)
274 cnf->groups[i++] = newstr(grp->gr_name);
275 }
276 while (i < cnf->numgroups)
277 cnf->groups[i++] = NULL;
278 }
279
280 if ((arg = getarg(args, 'k')) != NULL) {
281 if (stat(cnf->dotdir = arg->val, &st) == -1 || !S_ISDIR(st.st_mode))
282 errx(EX_OSFILE, "skeleton `%s' is not a directory or does not exist", cnf->dotdir);
283 }
284
285 if ((arg = getarg(args, 's')) != NULL)
286 cnf->shell_default = arg->val;
287
288 if ((arg = getarg(args, 'w')) != NULL)
289 cnf->default_password = boolean_val(arg->val, cnf->default_password);
290 if (mode == M_ADD && getarg(args, 'D')) {
291 if (name != NULL)
292 errx(EX_DATAERR, "can't combine `-D' with `-n name'");
293 if ((arg = getarg(args, 'u')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) {
294 if ((cnf->min_uid = (uid_t) atoi(p)) == 0)
295 cnf->min_uid = 1000;
296 if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_uid = (uid_t) atoi(p)) < cnf->min_uid)
297 cnf->max_uid = 32000;
298 }
299 if ((arg = getarg(args, 'i')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) {
300 if ((cnf->min_gid = (gid_t) atoi(p)) == 0)
301 cnf->min_gid = 1000;
302 if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_gid = (gid_t) atoi(p)) < cnf->min_gid)
303 cnf->max_gid = 32000;
304 }
305
306 if (write_userconfig(conf.config))
307 return (EXIT_SUCCESS);
308 err(EX_IOERR, "config udpate");
309 }
310
311 if (mode == M_PRINT && getarg(args, 'a')) {
312 SETPWENT();
313 while ((pwd = GETPWENT()) != NULL)
314 print_user(pwd);
315 ENDPWENT();
316 return EXIT_SUCCESS;
317 }
318
319 if (name != NULL)
320 pwd = GETPWNAM(pw_checkname(name, 0));
321
322 if (id < 0 && name == NULL)
323 errx(EX_DATAERR, "user name or id required");
324
325 /*
326 * Update, delete & print require that the user exists
327 */
328 if (mode == M_UPDATE || mode == M_DELETE ||
329 mode == M_PRINT || mode == M_LOCK || mode == M_UNLOCK) {
330
331 if (name == NULL && pwd == NULL) /* Try harder */
332 pwd = GETPWUID(id);
333
334 if (pwd == NULL) {
335 if (mode == M_PRINT && getarg(args, 'F')) {
336 fakeuser.pw_name = name ? name : "nouser";
337 fakeuser.pw_uid = (uid_t) id;
338 return print_user(&fakeuser);
339 }
340 if (name == NULL)
341 errx(EX_NOUSER, "no such uid `%ld'", id);
342 errx(EX_NOUSER, "no such user `%s'", name);
343 }
344
345 if (name == NULL)
346 name = pwd->pw_name;
347
348 /*
349 * The M_LOCK and M_UNLOCK functions simply add or remove
350 * a "*LOCKED*" prefix from in front of the password to
351 * prevent it decoding correctly, and therefore prevents
352 * access. Of course, this only prevents access via
353 * password authentication (not ssh, kerberos or any
354 * other method that does not use the UNIX password) but
355 * that is a known limitation.
356 */
357
358 if (mode == M_LOCK) {
359 if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) == 0)
360 errx(EX_DATAERR, "user '%s' is already locked", pwd->pw_name);
361 asprintf(&passtmp, "%s%s", locked_str, pwd->pw_passwd);
362 if (passtmp == NULL) /* disaster */
363 errx(EX_UNAVAILABLE, "out of memory");
364 pwd->pw_passwd = passtmp;
365 edited = 1;
366 } else if (mode == M_UNLOCK) {
367 if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) != 0)
368 errx(EX_DATAERR, "user '%s' is not locked", pwd->pw_name);
369 pwd->pw_passwd += sizeof(locked_str)-1;
370 edited = 1;
371 } else if (mode == M_DELETE)
372 return (delete_user(cnf, pwd, name,
373 getarg(args, 'r') != NULL, mode));
374 else if (mode == M_PRINT)
375 return print_user(pwd);
376
377 /*
378 * The rest is edit code
379 */
380 if (conf.newname != NULL) {
381 if (strcmp(pwd->pw_name, "root") == 0)
382 errx(EX_DATAERR, "can't rename `root' account");
383 pwd->pw_name = pw_checkname(conf.newname, 0);
384 edited = 1;
385 }
386
387 if (id > 0 && isdigit((unsigned char)*arg->val)) {
388 pwd->pw_uid = (uid_t)id;
389 edited = 1;
390 if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0)
391 errx(EX_DATAERR, "can't change uid of `root' account");
392 if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0)
393 warnx("WARNING: account `%s' will have a uid of 0 (superuser access!)", pwd->pw_name);
394 }
395
396 if ((arg = getarg(args, 'g')) != NULL && pwd->pw_uid != 0) { /* Already checked this */
397 gid_t newgid = (gid_t) GETGRNAM(cnf->default_group)->gr_gid;
398 if (newgid != pwd->pw_gid) {
399 edited = 1;
400 pwd->pw_gid = newgid;
401 }
402 }
403
404 if ((arg = getarg(args, 'p')) != NULL) {
405 if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) {
406 if (pwd->pw_change != 0) {
407 pwd->pw_change = 0;
408 edited = 1;
409 }
410 }
411 else {
412 time_t now = time(NULL);
413 time_t expire = parse_date(now, arg->val);
414
415 if (pwd->pw_change != expire) {
416 pwd->pw_change = expire;
417 edited = 1;
418 }
419 }
420 }
421
422 if ((arg = getarg(args, 'e')) != NULL) {
423 if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) {
424 if (pwd->pw_expire != 0) {
425 pwd->pw_expire = 0;
426 edited = 1;
427 }
428 }
429 else {
430 time_t now = time(NULL);
431 time_t expire = parse_date(now, arg->val);
432
433 if (pwd->pw_expire != expire) {
434 pwd->pw_expire = expire;
435 edited = 1;
436 }
437 }
438 }
439
440 if ((arg = getarg(args, 's')) != NULL) {
441 char *shell = shell_path(cnf->shelldir, cnf->shells, arg->val);
442 if (shell == NULL)
443 shell = "";
444 if (strcmp(shell, pwd->pw_shell) != 0) {
445 pwd->pw_shell = shell;
446 edited = 1;
447 }
448 }
449
450 if (getarg(args, 'L')) {
451 if (cnf->default_class == NULL)
452 cnf->default_class = "";
453 if (strcmp(pwd->pw_class, cnf->default_class) != 0) {
454 pwd->pw_class = cnf->default_class;
455 edited = 1;
456 }
457 }
458
459 if ((arg = getarg(args, 'd')) != NULL) {
460 if (strcmp(pwd->pw_dir, arg->val))
461 edited = 1;
462 if (stat(pwd->pw_dir = arg->val, &st) == -1) {
463 if (getarg(args, 'm') == NULL && strcmp(pwd->pw_dir, "/nonexistent") != 0)
464 warnx("WARNING: home `%s' does not exist", pwd->pw_dir);
465 } else if (!S_ISDIR(st.st_mode))
466 warnx("WARNING: home `%s' is not a directory", pwd->pw_dir);
467 }
468
469 if ((arg = getarg(args, 'w')) != NULL &&
470 getarg(args, 'h') == NULL && getarg(args, 'H') == NULL) {
471 login_cap_t *lc;
472
473 lc = login_getpwclass(pwd);
474 if (lc == NULL ||
475 login_setcryptfmt(lc, "sha512", NULL) == NULL)
476 warn("setting crypt(3) format");
477 login_close(lc);
478 pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name);
479 edited = 1;
480 }
481
482 } else {
483 login_cap_t *lc;
484
485 /*
486 * Add code
487 */
488
489 if (name == NULL) /* Required */
490 errx(EX_DATAERR, "login name required");
491 else if ((pwd = GETPWNAM(name)) != NULL) /* Exists */
492 errx(EX_DATAERR, "login name `%s' already exists", name);
493
494 /*
495 * Now, set up defaults for a new user
496 */
497 pwd = &fakeuser;
498 pwd->pw_name = name;
499 pwd->pw_class = cnf->default_class ? cnf->default_class : "";
500 pwd->pw_uid = pw_uidpolicy(cnf, id);
501 pwd->pw_gid = pw_gidpolicy(args, pwd->pw_name, (gid_t) pwd->pw_uid);
502 pwd->pw_change = pw_pwdpolicy(cnf, args);
503 pwd->pw_expire = pw_exppolicy(cnf, args);
504 pwd->pw_dir = pw_homepolicy(cnf, args, pwd->pw_name);
505 pwd->pw_shell = pw_shellpolicy(cnf, args, NULL);
506 lc = login_getpwclass(pwd);
507 if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL)
508 warn("setting crypt(3) format");
509 login_close(lc);
510 pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name);
511 edited = 1;
512
513 if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0)
514 warnx("WARNING: new account `%s' has a uid of 0 (superuser access!)", pwd->pw_name);
515 }
516
517 /*
518 * Shared add/edit code
519 */
520 if ((arg = getarg(args, 'c')) != NULL) {
521 char *gecos = pw_checkname(arg->val, 1);
522 if (strcmp(pwd->pw_gecos, gecos) != 0) {
523 pwd->pw_gecos = gecos;
524 edited = 1;
525 }
526 }
527
528 if ((arg = getarg(args, 'h')) != NULL ||
529 (arg = getarg(args, 'H')) != NULL) {
530 if (strcmp(arg->val, "-") == 0) {
531 if (!pwd->pw_passwd || *pwd->pw_passwd != '*') {
532 pwd->pw_passwd = "*"; /* No access */
533 edited = 1;
534 }
535 } else {
536 int fd = atoi(arg->val);
537 int precrypt = (arg->ch == 'H');
538 int b;
539 int istty = isatty(fd);
540 struct termios t;
541 login_cap_t *lc;
542
543 if (istty) {
544 if (tcgetattr(fd, &t) == -1)
545 istty = 0;
546 else {
547 struct termios n = t;
548
549 /* Disable echo */
550 n.c_lflag &= ~(ECHO);
551 tcsetattr(fd, TCSANOW, &n);
552 printf("%s%spassword for user %s:",
553 (mode == M_UPDATE) ? "new " : "",
554 precrypt ? "encrypted " : "",
555 pwd->pw_name);
556 fflush(stdout);
557 }
558 }
559 b = read(fd, line, sizeof(line) - 1);
560 if (istty) { /* Restore state */
561 tcsetattr(fd, TCSANOW, &t);
562 fputc('\n', stdout);
563 fflush(stdout);
564 }
565 if (b < 0)
566 err(EX_IOERR, "-%c file descriptor",
567 precrypt ? 'H' : 'h');
568 line[b] = '\0';
569 if ((p = strpbrk(line, "\r\n")) != NULL)
570 *p = '\0';
571 if (!*line)
572 errx(EX_DATAERR, "empty password read on file descriptor %d", fd);
573 if (precrypt) {
574 if (strchr(line, ':') != NULL)
575 return EX_DATAERR;
576 pwd->pw_passwd = line;
577 } else {
578 lc = login_getpwclass(pwd);
579 if (lc == NULL ||
580 login_setcryptfmt(lc, "sha512", NULL) == NULL)
581 warn("setting crypt(3) format");
582 login_close(lc);
583 pwd->pw_passwd = pw_pwcrypt(line);
584 }
585 edited = 1;
586 }
587 }
588
589 /*
590 * Special case: -N only displays & exits
591 */
592 if (conf.dryrun)
593 return print_user(pwd);
594
595 if (mode == M_ADD) {
596 edited = 1; /* Always */
597 rc = addpwent(pwd);
598 if (rc == -1)
599 errx(EX_IOERR, "user '%s' already exists",
600 pwd->pw_name);
601 else if (rc != 0)
602 err(EX_IOERR, "passwd file update");
603 if (cnf->nispasswd && *cnf->nispasswd=='/') {
604 rc = addnispwent(cnf->nispasswd, pwd);
605 if (rc == -1)
606 warnx("User '%s' already exists in NIS passwd", pwd->pw_name);
607 else
608 warn("NIS passwd update");
609 /* NOTE: we treat NIS-only update errors as non-fatal */
610 }
611 } else if (mode == M_UPDATE || mode == M_LOCK || mode == M_UNLOCK) {
612 if (edited) { /* Only updated this if required */
613 rc = chgpwent(name, pwd);
614 if (rc == -1)
615 errx(EX_IOERR, "user '%s' does not exist (NIS?)", pwd->pw_name);
616 else if (rc != 0)
617 err(EX_IOERR, "passwd file update");
618 if ( cnf->nispasswd && *cnf->nispasswd=='/') {
619 rc = chgnispwent(cnf->nispasswd, name, pwd);
620 if (rc == -1)
621 warn("User '%s' not found in NIS passwd", pwd->pw_name);
622 else
623 warn("NIS passwd update");
624 /* NOTE: NIS-only update errors are not fatal */
625 }
626 }
627 }
628
629 /*
630 * Ok, user is created or changed - now edit group file
631 */
632
633 if (mode == M_ADD || getarg(args, 'G') != NULL) {
634 int i, j;
635 /* First remove the user from all group */
636 SETGRENT();
637 while ((grp = GETGRENT()) != NULL) {
638 char group[MAXLOGNAME];
639 if (grp->gr_mem == NULL)
640 continue;
641 for (i = 0; grp->gr_mem[i] != NULL; i++) {
642 if (strcmp(grp->gr_mem[i] , pwd->pw_name) != 0)
643 continue;
644 for (j = i; grp->gr_mem[j] != NULL ; j++)
645 grp->gr_mem[j] = grp->gr_mem[j+1];
646 strlcpy(group, grp->gr_name, MAXLOGNAME);
647 chggrent(group, grp);
648 }
649 }
650 ENDGRENT();
651
652 /* now add to group where needed */
653 for (i = 0; cnf->groups[i] != NULL; i++) {
654 grp = GETGRNAM(cnf->groups[i]);
655 grp = gr_add(grp, pwd->pw_name);
656 /*
657 * grp can only be NULL in 2 cases:
658 * - the new member is already a member
659 * - a problem with memory occurs
660 * in both cases we want to skip now.
661 */
662 if (grp == NULL)
663 continue;
664 chggrent(cnf->groups[i], grp);
665 free(grp);
666 }
667 }
668
669
670 /* go get a current version of pwd */
671 pwd = GETPWNAM(name);
672 if (pwd == NULL) {
673 /* This will fail when we rename, so special case that */
674 if (mode == M_UPDATE && conf.newname != NULL) {
675 name = conf.newname; /* update new name */
676 pwd = GETPWNAM(name); /* refetch renamed rec */
677 }
678 }
679 if (pwd == NULL) /* can't go on without this */
680 errx(EX_NOUSER, "user '%s' disappeared during update", name);
681
682 grp = GETGRGID(pwd->pw_gid);
683 pw_log(cnf, mode, W_USER, "%s(%u):%s(%u):%s:%s:%s",
684 pwd->pw_name, pwd->pw_uid,
685 grp ? grp->gr_name : "unknown", (grp ? grp->gr_gid : (uid_t)-1),
686 pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);
687
688 /*
689 * If adding, let's touch and chown the user's mail file. This is not
690 * strictly necessary under BSD with a 0755 maildir but it also
691 * doesn't hurt anything to create the empty mailfile
692 */
693 if (mode == M_ADD) {
694 if (PWALTDIR() != PWF_ALT) {
695 arg = getarg(args, 'R');
696 snprintf(path, sizeof(path), "%s%s/%s",
697 arg ? arg->val : "", _PATH_MAILDIR, pwd->pw_name);
698 close(open(path, O_RDWR | O_CREAT, 0600)); /* Preserve contents &
699 * mtime */
700 chown(path, pwd->pw_uid, pwd->pw_gid);
701 }
702 }
703
704 /*
705 * Let's create and populate the user's home directory. Note
706 * that this also `works' for editing users if -m is used, but
707 * existing files will *not* be overwritten.
708 */
709 if (PWALTDIR() != PWF_ALT && getarg(args, 'm') != NULL && pwd->pw_dir &&
710 *pwd->pw_dir == '/' && pwd->pw_dir[1])
711 create_and_populate_homedir(mode, pwd);
712
713 /*
714 * Finally, send mail to the new user as well, if we are asked to
715 */
716 if (mode == M_ADD && !PWALTDIR() && cnf->newmail && *cnf->newmail && (fp = fopen(cnf->newmail, "r")) != NULL) {
717 FILE *pfp = popen(_PATH_SENDMAIL " -t", "w");
718
719 if (pfp == NULL)
720 warn("sendmail");
721 else {
722 fprintf(pfp, "From: root\n" "To: %s\n" "Subject: Welcome!\n\n", pwd->pw_name);
723 while (fgets(line, sizeof(line), fp) != NULL) {
724 /* Do substitutions? */
725 fputs(line, pfp);
726 }
727 pclose(pfp);
728 pw_log(cnf, mode, W_USER, "%s(%u) new user mail sent",
729 pwd->pw_name, pwd->pw_uid);
730 }
731 fclose(fp);
732 }
733
734 return EXIT_SUCCESS;
735 }
736
737
738 static uid_t
739 pw_uidpolicy(struct userconf * cnf, long id)
740 {
741 struct passwd *pwd;
742 uid_t uid = (uid_t) - 1;
743
744 /*
745 * Check the given uid, if any
746 */
747 if (id > 0) {
748 uid = (uid_t) id;
749
750 if ((pwd = GETPWUID(uid)) != NULL && conf.checkduplicate)
751 errx(EX_DATAERR, "uid `%u' has already been allocated", pwd->pw_uid);
752 } else {
753 struct bitmap bm;
754
755 /*
756 * We need to allocate the next available uid under one of
757 * two policies a) Grab the first unused uid b) Grab the
758 * highest possible unused uid
759 */
760 if (cnf->min_uid >= cnf->max_uid) { /* Sanity
761 * claus^H^H^H^Hheck */
762 cnf->min_uid = 1000;
763 cnf->max_uid = 32000;
764 }
765 bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1);
766
767 /*
768 * Now, let's fill the bitmap from the password file
769 */
770 SETPWENT();
771 while ((pwd = GETPWENT()) != NULL)
772 if (pwd->pw_uid >= (uid_t) cnf->min_uid && pwd->pw_uid <= (uid_t) cnf->max_uid)
773 bm_setbit(&bm, pwd->pw_uid - cnf->min_uid);
774 ENDPWENT();
775
776 /*
777 * Then apply the policy, with fallback to reuse if necessary
778 */
779 if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid)
780 uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid);
781
782 /*
783 * Another sanity check
784 */
785 if (uid < cnf->min_uid || uid > cnf->max_uid)
786 errx(EX_SOFTWARE, "unable to allocate a new uid - range fully used");
787 bm_dealloc(&bm);
788 }
789 return uid;
790 }
791
792
793 static uid_t
794 pw_gidpolicy(struct cargs * args, char *nam, gid_t prefer)
795 {
796 struct group *grp;
797 gid_t gid = (uid_t) - 1;
798 struct carg *a_gid = getarg(args, 'g');
799 struct userconf *cnf = conf.userconf;
800
801 /*
802 * If no arg given, see if default can help out
803 */
804 if (a_gid == NULL && cnf->default_group && *cnf->default_group)
805 a_gid = addarg(args, 'g', cnf->default_group);
806
807 /*
808 * Check the given gid, if any
809 */
810 SETGRENT();
811 if (a_gid != NULL) {
812 if ((grp = GETGRNAM(a_gid->val)) == NULL) {
813 gid = (gid_t) atol(a_gid->val);
814 if ((gid == 0 && !isdigit((unsigned char)*a_gid->val)) || (grp = GETGRGID(gid)) == NULL)
815 errx(EX_NOUSER, "group `%s' is not defined", a_gid->val);
816 }
817 gid = grp->gr_gid;
818 } else if ((grp = GETGRNAM(nam)) != NULL &&
819 (grp->gr_mem == NULL || grp->gr_mem[0] == NULL)) {
820 gid = grp->gr_gid; /* Already created? Use it anyway... */
821 } else {
822 struct cargs grpargs;
823 char tmp[32];
824
825 LIST_INIT(&grpargs);
826
827 /*
828 * We need to auto-create a group with the user's name. We
829 * can send all the appropriate output to our sister routine
830 * bit first see if we can create a group with gid==uid so we
831 * can keep the user and group ids in sync. We purposely do
832 * NOT check the gid range if we can force the sync. If the
833 * user's name dups an existing group, then the group add
834 * function will happily handle that case for us and exit.
835 */
836 if (GETGRGID(prefer) == NULL) {
837 snprintf(tmp, sizeof(tmp), "%u", prefer);
838 addarg(&grpargs, 'g', tmp);
839 }
840 if (conf.dryrun) {
841 addarg(&grpargs, 'q', NULL);
842 gid = pw_group(M_NEXT, nam, -1, &grpargs);
843 }
844 else
845 {
846 pw_group(M_ADD, nam, -1, &grpargs);
847 if ((grp = GETGRNAM(nam)) != NULL)
848 gid = grp->gr_gid;
849 }
850 a_gid = LIST_FIRST(&grpargs);
851 while (a_gid != NULL) {
852 struct carg *t = LIST_NEXT(a_gid, list);
853 LIST_REMOVE(a_gid, list);
854 a_gid = t;
855 }
856 }
857 ENDGRENT();
858 return gid;
859 }
860
861
862 static time_t
863 pw_pwdpolicy(struct userconf * cnf, struct cargs * args)
864 {
865 time_t result = 0;
866 time_t now = time(NULL);
867 struct carg *arg = getarg(args, 'p');
868
869 if (arg != NULL) {
870 if ((result = parse_date(now, arg->val)) == now)
871 errx(EX_DATAERR, "invalid date/time `%s'", arg->val);
872 } else if (cnf->password_days > 0)
873 result = now + ((long) cnf->password_days * 86400L);
874 return result;
875 }
876
877
878 static time_t
879 pw_exppolicy(struct userconf * cnf, struct cargs * args)
880 {
881 time_t result = 0;
882 time_t now = time(NULL);
883 struct carg *arg = getarg(args, 'e');
884
885 if (arg != NULL) {
886 if ((result = parse_date(now, arg->val)) == now)
887 errx(EX_DATAERR, "invalid date/time `%s'", arg->val);
888 } else if (cnf->expire_days > 0)
889 result = now + ((long) cnf->expire_days * 86400L);
890 return result;
891 }
892
893
894 static char *
895 pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user)
896 {
897 struct carg *arg = getarg(args, 'd');
898 static char home[128];
899
900 if (arg)
901 return (arg->val);
902
903 if (cnf->home == NULL || *cnf->home == '\0')
904 errx(EX_CONFIG, "no base home directory set");
905 snprintf(home, sizeof(home), "%s/%s", cnf->home, user);
906
907 return (home);
908 }
909
910 static char *
911 shell_path(char const * path, char *shells[], char *sh)
912 {
913 if (sh != NULL && (*sh == '/' || *sh == '\0'))
914 return sh; /* specified full path or forced none */
915 else {
916 char *p;
917 char paths[_UC_MAXLINE];
918
919 /*
920 * We need to search paths
921 */
922 strlcpy(paths, path, sizeof(paths));
923 for (p = strtok(paths, ": \t\r\n"); p != NULL; p = strtok(NULL, ": \t\r\n")) {
924 int i;
925 static char shellpath[256];
926
927 if (sh != NULL) {
928 snprintf(shellpath, sizeof(shellpath), "%s/%s", p, sh);
929 if (access(shellpath, X_OK) == 0)
930 return shellpath;
931 } else
932 for (i = 0; i < _UC_MAXSHELLS && shells[i] != NULL; i++) {
933 snprintf(shellpath, sizeof(shellpath), "%s/%s", p, shells[i]);
934 if (access(shellpath, X_OK) == 0)
935 return shellpath;
936 }
937 }
938 if (sh == NULL)
939 errx(EX_OSFILE, "can't find shell `%s' in shell paths", sh);
940 errx(EX_CONFIG, "no default shell available or defined");
941 return NULL;
942 }
943 }
944
945
946 static char *
947 pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell)
948 {
949 char *sh = newshell;
950 struct carg *arg = getarg(args, 's');
951
952 if (newshell == NULL && arg != NULL)
953 sh = arg->val;
954 return shell_path(cnf->shelldir, cnf->shells, sh ? sh : cnf->shell_default);
955 }
956
957 #define SALTSIZE 32
958
959 static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./";
960
961 char *
962 pw_pwcrypt(char *password)
963 {
964 int i;
965 char salt[SALTSIZE + 1];
966 char *cryptpw;
967
968 static char buf[256];
969
970 /*
971 * Calculate a salt value
972 */
973 for (i = 0; i < SALTSIZE; i++)
974 salt[i] = chars[arc4random_uniform(sizeof(chars) - 1)];
975 salt[SALTSIZE] = '\0';
976
977 cryptpw = crypt(password, salt);
978 if (cryptpw == NULL)
979 errx(EX_CONFIG, "crypt(3) failure");
980 return strcpy(buf, cryptpw);
981 }
982
983
984 static char *
985 pw_password(struct userconf * cnf, struct cargs * args, char const * user)
986 {
987 int i, l;
988 char pwbuf[32];
989
990 switch (cnf->default_password) {
991 case -1: /* Random password */
992 l = (arc4random() % 8 + 8); /* 8 - 16 chars */
993 for (i = 0; i < l; i++)
994 pwbuf[i] = chars[arc4random_uniform(sizeof(chars)-1)];
995 pwbuf[i] = '\0';
996
997 /*
998 * We give this information back to the user
999 */
1000 if (getarg(args, 'h') == NULL && getarg(args, 'H') == NULL &&
1001 !conf.dryrun) {
1002 if (isatty(STDOUT_FILENO))
1003 printf("Password for '%s' is: ", user);
1004 printf("%s\n", pwbuf);
1005 fflush(stdout);
1006 }
1007 break;
1008
1009 case -2: /* No password at all! */
1010 return "";
1011
1012 case 0: /* No login - default */
1013 default:
1014 return "*";
1015
1016 case 1: /* user's name */
1017 strlcpy(pwbuf, user, sizeof(pwbuf));
1018 break;
1019 }
1020 return pw_pwcrypt(pwbuf);
1021 }
1022
1023 static int
1024 delete_user(struct userconf *cnf, struct passwd *pwd, char *name,
1025 int delete, int mode)
1026 {
1027 char file[MAXPATHLEN];
1028 char home[MAXPATHLEN];
1029 uid_t uid = pwd->pw_uid;
1030 struct group *gr, *grp;
1031 char grname[LOGNAMESIZE];
1032 int rc;
1033 struct stat st;
1034
1035 if (strcmp(pwd->pw_name, "root") == 0)
1036 errx(EX_DATAERR, "cannot remove user 'root'");
1037
1038 if (!PWALTDIR()) {
1039 /*
1040 * Remove opie record from /etc/opiekeys
1041 */
1042
1043 rmopie(pwd->pw_name);
1044
1045 /*
1046 * Remove crontabs
1047 */
1048 snprintf(file, sizeof(file), "/var/cron/tabs/%s", pwd->pw_name);
1049 if (access(file, F_OK) == 0) {
1050 snprintf(file, sizeof(file), "crontab -u %s -r", pwd->pw_name);
1051 system(file);
1052 }
1053 }
1054 /*
1055 * Save these for later, since contents of pwd may be
1056 * invalidated by deletion
1057 */
1058 snprintf(file, sizeof(file), "%s/%s", _PATH_MAILDIR, pwd->pw_name);
1059 strlcpy(home, pwd->pw_dir, sizeof(home));
1060 gr = GETGRGID(pwd->pw_gid);
1061 if (gr != NULL)
1062 strlcpy(grname, gr->gr_name, LOGNAMESIZE);
1063 else
1064 grname[0] = '\0';
1065
1066 rc = delpwent(pwd);
1067 if (rc == -1)
1068 err(EX_IOERR, "user '%s' does not exist", pwd->pw_name);
1069 else if (rc != 0)
1070 err(EX_IOERR, "passwd update");
1071
1072 if (cnf->nispasswd && *cnf->nispasswd=='/') {
1073 rc = delnispwent(cnf->nispasswd, name);
1074 if (rc == -1)
1075 warnx("WARNING: user '%s' does not exist in NIS passwd", pwd->pw_name);
1076 else if (rc != 0)
1077 warn("WARNING: NIS passwd update");
1078 /* non-fatal */
1079 }
1080
1081 grp = GETGRNAM(name);
1082 if (grp != NULL &&
1083 (grp->gr_mem == NULL || *grp->gr_mem == NULL) &&
1084 strcmp(name, grname) == 0)
1085 delgrent(GETGRNAM(name));
1086 SETGRENT();
1087 while ((grp = GETGRENT()) != NULL) {
1088 int i, j;
1089 char group[MAXLOGNAME];
1090 if (grp->gr_mem == NULL)
1091 continue;
1092
1093 for (i = 0; grp->gr_mem[i] != NULL; i++) {
1094 if (strcmp(grp->gr_mem[i], name) != 0)
1095 continue;
1096
1097 for (j = i; grp->gr_mem[j] != NULL; j++)
1098 grp->gr_mem[j] = grp->gr_mem[j+1];
1099 strlcpy(group, grp->gr_name, MAXLOGNAME);
1100 chggrent(group, grp);
1101 }
1102 }
1103 ENDGRENT();
1104
1105 pw_log(cnf, mode, W_USER, "%s(%u) account removed", name, uid);
1106
1107 if (!PWALTDIR()) {
1108 /*
1109 * Remove mail file
1110 */
1111 remove(file);
1112
1113 /*
1114 * Remove at jobs
1115 */
1116 if (getpwuid(uid) == NULL)
1117 rmat(uid);
1118
1119 /*
1120 * Remove home directory and contents
1121 */
1122 if (delete && *home == '/' && getpwuid(uid) == NULL &&
1123 stat(home, &st) != -1) {
1124 rm_r(home, uid);
1125 pw_log(cnf, mode, W_USER, "%s(%u) home '%s' %sremoved",
1126 name, uid, home,
1127 stat(home, &st) == -1 ? "" : "not completely ");
1128 }
1129 }
1130
1131 return (EXIT_SUCCESS);
1132 }
1133
1134 static int
1135 print_user(struct passwd * pwd)
1136 {
1137 if (!conf.pretty) {
1138 char *buf;
1139
1140 buf = conf.v7 ? pw_make_v7(pwd) : pw_make(pwd);
1141 printf("%s\n", buf);
1142 free(buf);
1143 } else {
1144 int j;
1145 char *p;
1146 struct group *grp = GETGRGID(pwd->pw_gid);
1147 char uname[60] = "User &", office[60] = "[None]",
1148 wphone[60] = "[None]", hphone[60] = "[None]";
1149 char acexpire[32] = "[None]", pwexpire[32] = "[None]";
1150 struct tm * tptr;
1151
1152 if ((p = strtok(pwd->pw_gecos, ",")) != NULL) {
1153 strlcpy(uname, p, sizeof(uname));
1154 if ((p = strtok(NULL, ",")) != NULL) {
1155 strlcpy(office, p, sizeof(office));
1156 if ((p = strtok(NULL, ",")) != NULL) {
1157 strlcpy(wphone, p, sizeof(wphone));
1158 if ((p = strtok(NULL, "")) != NULL) {
1159 strlcpy(hphone, p,
1160 sizeof(hphone));
1161 }
1162 }
1163 }
1164 }
1165 /*
1166 * Handle '&' in gecos field
1167 */
1168 if ((p = strchr(uname, '&')) != NULL) {
1169 int l = strlen(pwd->pw_name);
1170 int m = strlen(p);
1171
1172 memmove(p + l, p + 1, m);
1173 memmove(p, pwd->pw_name, l);
1174 *p = (char) toupper((unsigned char)*p);
1175 }
1176 if (pwd->pw_expire > (time_t)0 && (tptr = localtime(&pwd->pw_expire)) != NULL)
1177 strftime(acexpire, sizeof acexpire, "%c", tptr);
1178 if (pwd->pw_change > (time_t)0 && (tptr = localtime(&pwd->pw_change)) != NULL)
1179 strftime(pwexpire, sizeof pwexpire, "%c", tptr);
1180 printf("Login Name: %-15s #%-12u Group: %-15s #%u\n"
1181 " Full Name: %s\n"
1182 " Home: %-26.26s Class: %s\n"
1183 " Shell: %-26.26s Office: %s\n"
1184 "Work Phone: %-26.26s Home Phone: %s\n"
1185 "Acc Expire: %-26.26s Pwd Expire: %s\n",
1186 pwd->pw_name, pwd->pw_uid,
1187 grp ? grp->gr_name : "(invalid)", pwd->pw_gid,
1188 uname, pwd->pw_dir, pwd->pw_class,
1189 pwd->pw_shell, office, wphone, hphone,
1190 acexpire, pwexpire);
1191 SETGRENT();
1192 j = 0;
1193 while ((grp=GETGRENT()) != NULL)
1194 {
1195 int i = 0;
1196 if (grp->gr_mem != NULL) {
1197 while (grp->gr_mem[i] != NULL)
1198 {
1199 if (strcmp(grp->gr_mem[i], pwd->pw_name)==0)
1200 {
1201 printf(j++ == 0 ? " Groups: %s" : ",%s", grp->gr_name);
1202 break;
1203 }
1204 ++i;
1205 }
1206 }
1207 }
1208 ENDGRENT();
1209 printf("%s", j ? "\n" : "");
1210 }
1211 return EXIT_SUCCESS;
1212 }
1213
1214 char *
1215 pw_checkname(char *name, int gecos)
1216 {
1217 char showch[8];
1218 const char *badchars, *ch, *showtype;
1219 int reject;
1220
1221 ch = name;
1222 reject = 0;
1223 if (gecos) {
1224 /* See if the name is valid as a gecos (comment) field. */
1225 badchars = ":!@";
1226 showtype = "gecos field";
1227 } else {
1228 /* See if the name is valid as a userid or group. */
1229 badchars = " ,\t:+&#%$^()!@~*?<>=|\\/\"";
1230 showtype = "userid/group name";
1231 /* Userids and groups can not have a leading '-'. */
1232 if (*ch == '-')
1233 reject = 1;
1234 }
1235 if (!reject) {
1236 while (*ch) {
1237 if (strchr(badchars, *ch) != NULL || *ch < ' ' ||
1238 *ch == 127) {
1239 reject = 1;
1240 break;
1241 }
1242 /* 8-bit characters are only allowed in GECOS fields */
1243 if (!gecos && (*ch & 0x80)) {
1244 reject = 1;
1245 break;
1246 }
1247 ch++;
1248 }
1249 }
1250 /*
1251 * A `$' is allowed as the final character for userids and groups,
1252 * mainly for the benefit of samba.
1253 */
1254 if (reject && !gecos) {
1255 if (*ch == '$' && *(ch + 1) == '\0') {
1256 reject = 0;
1257 ch++;
1258 }
1259 }
1260 if (reject) {
1261 snprintf(showch, sizeof(showch), (*ch >= ' ' && *ch < 127)
1262 ? "`%c'" : "0x%02x", *ch);
1263 errx(EX_DATAERR, "invalid character %s at position %td in %s",
1264 showch, (ch - name), showtype);
1265 }
1266 if (!gecos && (ch - name) > LOGNAMESIZE)
1267 errx(EX_DATAERR, "name too long `%s' (max is %d)", name,
1268 LOGNAMESIZE);
1269
1270 return (name);
1271 }
1272
1273
1274 static void
1275 rmat(uid_t uid)
1276 {
1277 DIR *d = opendir("/var/at/jobs");
1278
1279 if (d != NULL) {
1280 struct dirent *e;
1281
1282 while ((e = readdir(d)) != NULL) {
1283 struct stat st;
1284
1285 if (strncmp(e->d_name, ".lock", 5) != 0 &&
1286 stat(e->d_name, &st) == 0 &&
1287 !S_ISDIR(st.st_mode) &&
1288 st.st_uid == uid) {
1289 char tmp[MAXPATHLEN];
1290
1291 snprintf(tmp, sizeof(tmp), "/usr/bin/atrm %s", e->d_name);
1292 system(tmp);
1293 }
1294 }
1295 closedir(d);
1296 }
1297 }
1298
1299 static void
1300 rmopie(char const * name)
1301 {
1302 static const char etcopie[] = "/etc/opiekeys";
1303 FILE *fp = fopen(etcopie, "r+");
1304
1305 if (fp != NULL) {
1306 char tmp[1024];
1307 off_t atofs = 0;
1308 int length = strlen(name);
1309
1310 while (fgets(tmp, sizeof tmp, fp) != NULL) {
1311 if (strncmp(name, tmp, length) == 0 && tmp[length]==' ') {
1312 if (fseek(fp, atofs, SEEK_SET) == 0) {
1313 fwrite("#", 1, 1, fp); /* Comment username out */
1314 }
1315 break;
1316 }
1317 atofs = ftell(fp);
1318 }
1319 /*
1320 * If we got an error of any sort, don't update!
1321 */
1322 fclose(fp);
1323 }
1324 }
1325