1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright (c) 2013 Gary Mills
23 *
24 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
25 * Use is subject to license terms.
26 */
27
28 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
29 /* All Rights Reserved */
30
31 /*
32 * Copyright (c) 2013 RackTop Systems.
33 */
34
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/param.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <ctype.h>
41 #include <limits.h>
42 #include <string.h>
43 #include <userdefs.h>
44 #include <user_attr.h>
45 #include <nss_dbdefs.h>
46 #include <errno.h>
47 #include <project.h>
48 #include "users.h"
49 #include "messages.h"
50 #include "funcs.h"
51
52 /*
53 * usermod [-u uid [-o] | -g group | -G group [[,group]...] | -d dir [-m]
54 * | -s shell | -c comment | -l new_logname]
55 * | -f inactive | -e expire ]
56 * [ -A authorization [, authorization ...]]
57 * [ -P profile [, profile ...]]
58 * [ -R role [, role ...]]
59 * [ -K key=value ]
60 * [ -p project [, project]] login
61 *
62 * This command adds new user logins to the system. Arguments are:
63 *
64 * uid - an integer less than MAXUID
65 * group - an existing group's integer ID or char string name
66 * dir - a directory
67 * shell - a program to be used as a shell
68 * comment - any text string
69 * skel_dir - a directory
70 * base_dir - a directory
71 * rid - an integer less than 2**16 (USHORT)
72 * login - a string of printable chars except colon (:)
73 * inactive - number of days a login maybe inactive before it is locked
74 * expire - date when a login is no longer valid
75 * authorization - One or more comma separated authorizations defined
76 * in auth_attr(4).
77 * profile - One or more comma separated execution profiles defined
78 * in prof_attr(4)
79 * role - One or more comma-separated role names defined in user_attr(4)
80 * key=value - One or more -K options each specifying a valid user_attr(4)
81 * attribute.
82 *
83 */
84
85 extern int **valid_lgroup(), isbusy();
86 extern int valid_uid(), check_perm(), create_home(), move_dir();
87 extern int valid_expire(), edit_group(), call_passmgmt();
88 extern projid_t **valid_lproject();
89
90 static uid_t uid; /* new uid */
91 static gid_t gid; /* gid of new login */
92 static char *new_logname = NULL; /* new login name with -l option */
93 static char *uidstr = NULL; /* uid from command line */
94 static char *group = NULL; /* group from command line */
95 static char *grps = NULL; /* multi groups from command line */
96 static char *dir = NULL; /* home dir from command line */
97 static char *shell = NULL; /* shell from command line */
98 static char *comment = NULL; /* comment from command line */
99 static char *logname = NULL; /* login name to add */
100 static char *inactstr = NULL; /* inactive from command line */
101 static char *expire = NULL; /* expiration date from command line */
102 static char *projects = NULL; /* project ids from command line */
103 static char *usertype;
104
105 char *cmdname;
106 static char gidstring[32], uidstring[32];
107 char inactstring[10];
108
109 char *
strcpmalloc(str)110 strcpmalloc(str)
111 char *str;
112 {
113 if (str == NULL)
114 return (NULL);
115
116 return (strdup(str));
117 }
118 struct passwd *
passwd_cpmalloc(opw)119 passwd_cpmalloc(opw)
120 struct passwd *opw;
121 {
122 struct passwd *npw;
123
124 if (opw == NULL)
125 return (NULL);
126
127
128 npw = malloc(sizeof (struct passwd));
129
130 npw->pw_name = strcpmalloc(opw->pw_name);
131 npw->pw_passwd = strcpmalloc(opw->pw_passwd);
132 npw->pw_uid = opw->pw_uid;
133 npw->pw_gid = opw->pw_gid;
134 npw->pw_age = strcpmalloc(opw->pw_age);
135 npw->pw_comment = strcpmalloc(opw->pw_comment);
136 npw->pw_gecos = strcpmalloc(opw->pw_gecos);
137 npw->pw_dir = strcpmalloc(opw->pw_dir);
138 npw->pw_shell = strcpmalloc(opw->pw_shell);
139
140 return (npw);
141 }
142
143 int
main(argc,argv)144 main(argc, argv)
145 int argc;
146 char **argv;
147 {
148 int ch, ret = EX_SUCCESS, call_pass = 0, oflag = 0;
149 int tries, mflag = 0, inact, **gidlist, flag = 0;
150 boolean_t fail_if_busy = B_FALSE;
151 char *ptr;
152 struct passwd *pstruct; /* password struct for login */
153 struct passwd *pw;
154 struct group *g_ptr; /* validated group from -g */
155 struct stat statbuf; /* status buffer for stat */
156 #ifndef att
157 FILE *pwf; /* fille ptr for opened passwd file */
158 #endif
159 int warning;
160 projid_t **projlist;
161 char **nargv; /* arguments for execvp of passmgmt */
162 int argindex; /* argument index into nargv */
163 userattr_t *ua;
164 char *val;
165 int isrole; /* current account is role */
166
167 cmdname = argv[0];
168
169 if (geteuid() != 0) {
170 errmsg(M_PERM_DENIED);
171 exit(EX_NO_PERM);
172 }
173
174 opterr = 0; /* no print errors from getopt */
175 /* get user type based on the program name */
176 usertype = getusertype(argv[0]);
177
178 while ((ch = getopt(argc, argv,
179 "c:d:e:f:G:g:l:mop:s:u:A:P:R:K:")) != EOF)
180 switch (ch) {
181 case 'c':
182 comment = optarg;
183 flag++;
184 break;
185 case 'd':
186 dir = optarg;
187 fail_if_busy = B_TRUE;
188 flag++;
189 break;
190 case 'e':
191 expire = optarg;
192 flag++;
193 break;
194 case 'f':
195 inactstr = optarg;
196 flag++;
197 break;
198 case 'G':
199 grps = optarg;
200 flag++;
201 break;
202 case 'g':
203 group = optarg;
204 fail_if_busy = B_TRUE;
205 flag++;
206 break;
207 case 'l':
208 new_logname = optarg;
209 fail_if_busy = B_TRUE;
210 flag++;
211 break;
212 case 'm':
213 mflag++;
214 flag++;
215 fail_if_busy = B_TRUE;
216 break;
217 case 'o':
218 oflag++;
219 flag++;
220 fail_if_busy = B_TRUE;
221 break;
222 case 'p':
223 projects = optarg;
224 flag++;
225 break;
226 case 's':
227 shell = optarg;
228 flag++;
229 break;
230 case 'u':
231 uidstr = optarg;
232 flag++;
233 fail_if_busy = B_TRUE;
234 break;
235 case 'A':
236 change_key(USERATTR_AUTHS_KW, optarg);
237 flag++;
238 break;
239 case 'P':
240 change_key(USERATTR_PROFILES_KW, optarg);
241 flag++;
242 break;
243 case 'R':
244 change_key(USERATTR_ROLES_KW, optarg);
245 flag++;
246 break;
247 case 'K':
248 change_key(NULL, optarg);
249 flag++;
250 break;
251 default:
252 case '?':
253 if (is_role(usertype))
254 errmsg(M_MRUSAGE);
255 else
256 errmsg(M_MUSAGE);
257 exit(EX_SYNTAX);
258 }
259
260 if (optind != argc - 1 || flag == 0) {
261 if (is_role(usertype))
262 errmsg(M_MRUSAGE);
263 else
264 errmsg(M_MUSAGE);
265 exit(EX_SYNTAX);
266 }
267
268 if ((!uidstr && oflag) || (mflag && !dir)) {
269 if (is_role(usertype))
270 errmsg(M_MRUSAGE);
271 else
272 errmsg(M_MUSAGE);
273 exit(EX_SYNTAX);
274 }
275
276 logname = argv[optind];
277
278 /* Determine whether the account is a role or not */
279 if ((ua = getusernam(logname)) == NULL ||
280 (val = kva_match(ua->attr, USERATTR_TYPE_KW)) == NULL ||
281 strcmp(val, USERATTR_TYPE_NONADMIN_KW) != 0)
282 isrole = 0;
283 else
284 isrole = 1;
285
286 /* Verify that rolemod is used for roles and usermod for users */
287 if (isrole != is_role(usertype)) {
288 if (isrole)
289 errmsg(M_ISROLE);
290 else
291 errmsg(M_ISUSER);
292 exit(EX_SYNTAX);
293 }
294
295 /* Set the usertype key; defaults to the commandline */
296 usertype = getsetdefval(USERATTR_TYPE_KW, usertype);
297
298 if (is_role(usertype)) {
299 /* Roles can't have roles */
300 if (getsetdefval(USERATTR_ROLES_KW, NULL) != NULL) {
301 errmsg(M_MRUSAGE);
302 exit(EX_SYNTAX);
303 }
304 /* If it was an ordinary user, delete its roles */
305 if (!isrole)
306 change_key(USERATTR_ROLES_KW, "");
307 }
308
309 #ifdef att
310 pw = getpwnam(logname);
311 #else
312 /*
313 * Do this with fgetpwent to make sure we are only looking on local
314 * system (since passmgmt only works on local system).
315 */
316 if ((pwf = fopen("/etc/passwd", "r")) == NULL) {
317 errmsg(M_OOPS, "open", "/etc/passwd");
318 exit(EX_FAILURE);
319 }
320 while ((pw = fgetpwent(pwf)) != NULL)
321 if (strcmp(pw->pw_name, logname) == 0)
322 break;
323
324 fclose(pwf);
325 #endif
326
327 if (pw == NULL) {
328 char pwdb[NSS_BUFLEN_PASSWD];
329 struct passwd pwd;
330
331 if (getpwnam_r(logname, &pwd, pwdb, sizeof (pwdb)) == NULL) {
332 /* This user does not exist. */
333 errmsg(M_EXIST, logname);
334 exit(EX_NAME_NOT_EXIST);
335 } else {
336 /* This user exists in non-local name service. */
337 errmsg(M_NONLOCAL, logname);
338 exit(EX_NOT_LOCAL);
339 }
340 }
341
342 pstruct = passwd_cpmalloc(pw);
343
344 /*
345 * We can't modify a logged in user if any of the following
346 * are being changed:
347 * uid (-u & -o), group (-g), home dir (-m), loginname (-l).
348 * If none of those are specified it is okay to go ahead
349 * some types of changes only take effect on next login, some
350 * like authorisations and profiles take effect instantly.
351 * One might think that -K type=role should require that the
352 * user not be logged in, however this would make it very
353 * difficult to make the root account a role using this command.
354 */
355 if (isbusy(logname)) {
356 if (fail_if_busy) {
357 errmsg(M_BUSY, logname, "change");
358 exit(EX_BUSY);
359 }
360 warningmsg(WARN_LOGGED_IN, logname);
361 }
362
363 if (new_logname && strcmp(new_logname, logname)) {
364 switch (valid_login(new_logname, (struct passwd **)NULL,
365 &warning)) {
366 case INVALID:
367 errmsg(M_INVALID, new_logname, "login name");
368 exit(EX_BADARG);
369 /*NOTREACHED*/
370
371 case NOTUNIQUE:
372 errmsg(M_USED, new_logname);
373 exit(EX_NAME_EXISTS);
374 /*NOTREACHED*/
375
376 case LONGNAME:
377 errmsg(M_TOO_LONG, new_logname);
378 exit(EX_BADARG);
379 /*NOTREACHED*/
380
381 default:
382 call_pass = 1;
383 break;
384 }
385 if (warning)
386 warningmsg(warning, logname);
387 }
388
389 if (uidstr) {
390 /* convert uidstr to integer */
391 errno = 0;
392 uid = (uid_t)strtol(uidstr, &ptr, (int)10);
393 if (*ptr || errno == ERANGE) {
394 errmsg(M_INVALID, uidstr, "user id");
395 exit(EX_BADARG);
396 }
397
398 if (uid != pstruct->pw_uid) {
399 switch (valid_uid(uid, NULL)) {
400 case NOTUNIQUE:
401 if (!oflag) {
402 /* override not specified */
403 errmsg(M_UID_USED, uid);
404 exit(EX_ID_EXISTS);
405 }
406 break;
407 case RESERVED:
408 errmsg(M_RESERVED, uid);
409 break;
410 case TOOBIG:
411 errmsg(M_TOOBIG, "uid", uid);
412 exit(EX_BADARG);
413 break;
414 }
415
416 call_pass = 1;
417
418 } else {
419 /* uid's the same, so don't change anything */
420 uidstr = NULL;
421 oflag = 0;
422 }
423
424 } else uid = pstruct->pw_uid;
425
426 if (group) {
427 switch (valid_group(group, &g_ptr, &warning)) {
428 case INVALID:
429 errmsg(M_INVALID, group, "group id");
430 exit(EX_BADARG);
431 /*NOTREACHED*/
432 case TOOBIG:
433 errmsg(M_TOOBIG, "gid", group);
434 exit(EX_BADARG);
435 /*NOTREACHED*/
436 case UNIQUE:
437 errmsg(M_GRP_NOTUSED, group);
438 exit(EX_NAME_NOT_EXIST);
439 /*NOTREACHED*/
440 case RESERVED:
441 gid = (gid_t)strtol(group, &ptr, (int)10);
442 errmsg(M_RESERVED_GID, gid);
443 break;
444 }
445 if (warning)
446 warningmsg(warning, group);
447
448 if (g_ptr != NULL)
449 gid = g_ptr->gr_gid;
450 else
451 gid = pstruct->pw_gid;
452
453 /* call passmgmt if gid is different, else ignore group */
454 if (gid != pstruct->pw_gid)
455 call_pass = 1;
456 else group = NULL;
457
458 } else gid = pstruct->pw_gid;
459
460 if (grps && *grps) {
461 if (!(gidlist = valid_lgroup(grps, gid)))
462 exit(EX_BADARG);
463 } else
464 gidlist = (int **)0;
465
466 if (projects && *projects) {
467 if (! (projlist = valid_lproject(projects)))
468 exit(EX_BADARG);
469 } else
470 projlist = (projid_t **)0;
471
472 if (dir) {
473 if (REL_PATH(dir)) {
474 errmsg(M_RELPATH, dir);
475 exit(EX_BADARG);
476 }
477 if (strcmp(pstruct->pw_dir, dir) == 0) {
478 /* home directory is the same so ignore dflag & mflag */
479 dir = NULL;
480 mflag = 0;
481 } else call_pass = 1;
482 }
483
484 if (mflag) {
485 if (stat(dir, &statbuf) == 0) {
486 /* Home directory exists */
487 if (check_perm(statbuf, pstruct->pw_uid,
488 pstruct->pw_gid, S_IWOTH|S_IXOTH) != 0) {
489 errmsg(M_NO_PERM, logname, dir);
490 exit(EX_NO_PERM);
491 }
492
493 } else ret = create_home(dir, NULL, uid, gid);
494
495 if (ret == EX_SUCCESS)
496 ret = move_dir(pstruct->pw_dir, dir, logname);
497
498 if (ret != EX_SUCCESS)
499 exit(ret);
500 }
501
502 if (shell) {
503 if (REL_PATH(shell)) {
504 errmsg(M_RELPATH, shell);
505 exit(EX_BADARG);
506 }
507 if (strcmp(pstruct->pw_shell, shell) == 0) {
508 /* ignore s option if shell is not different */
509 shell = NULL;
510 } else {
511 if (stat(shell, &statbuf) < 0 ||
512 (statbuf.st_mode & S_IFMT) != S_IFREG ||
513 (statbuf.st_mode & 0555) != 0555) {
514
515 errmsg(M_INVALID, shell, "shell");
516 exit(EX_BADARG);
517 }
518
519 call_pass = 1;
520 }
521 }
522
523 if (comment) {
524 /* ignore comment if comment is not changed */
525 if (strcmp(pstruct->pw_comment, comment))
526 call_pass = 1;
527 else
528 comment = NULL;
529 }
530
531 /* inactive string is a positive integer */
532 if (inactstr) {
533 /* convert inactstr to integer */
534 inact = (int)strtol(inactstr, &ptr, 10);
535 if (*ptr || inact < 0) {
536 errmsg(M_INVALID, inactstr, "inactivity period");
537 exit(EX_BADARG);
538 }
539 call_pass = 1;
540 }
541
542 /* expiration string is a date, newer than today */
543 if (expire) {
544 if (*expire &&
545 valid_expire(expire, (time_t *)0) == INVALID) {
546 errmsg(M_INVALID, expire, "expiration date");
547 exit(EX_BADARG);
548 }
549 call_pass = 1;
550 }
551
552 if (nkeys > 0)
553 call_pass = 1;
554
555 /* that's it for validations - now do the work */
556
557 if (grps) {
558 /* redefine login's supplentary group memberships */
559 ret = edit_group(logname, new_logname, gidlist, 1);
560 if (ret != EX_SUCCESS) {
561 errmsg(M_UPDATE, "modified");
562 exit(ret);
563 }
564 }
565 if (projects) {
566 ret = edit_project(logname, (char *)NULL, projlist, 0);
567 if (ret != EX_SUCCESS) {
568 errmsg(M_UPDATE, "modified");
569 exit(ret);
570 }
571 }
572
573
574 if (!call_pass) exit(ret);
575
576 /* only get to here if need to call passmgmt */
577 /* set up arguments to passmgmt in nargv array */
578 nargv = malloc((30 + nkeys * 2) * sizeof (char *));
579
580 argindex = 0;
581 nargv[argindex++] = PASSMGMT;
582 nargv[argindex++] = "-m"; /* modify */
583
584 if (comment) { /* comment */
585 nargv[argindex++] = "-c";
586 nargv[argindex++] = comment;
587 }
588
589 if (dir) {
590 /* flags for home directory */
591 nargv[argindex++] = "-h";
592 nargv[argindex++] = dir;
593 }
594
595 if (group) {
596 /* set gid flag */
597 nargv[argindex++] = "-g";
598 (void) sprintf(gidstring, "%u", gid);
599 nargv[argindex++] = gidstring;
600 }
601
602 if (shell) { /* shell */
603 nargv[argindex++] = "-s";
604 nargv[argindex++] = shell;
605 }
606
607 if (inactstr) {
608 nargv[argindex++] = "-f";
609 nargv[argindex++] = inactstr;
610 }
611
612 if (expire) {
613 nargv[argindex++] = "-e";
614 nargv[argindex++] = expire;
615 }
616
617 if (uidstr) { /* set uid flag */
618 nargv[argindex++] = "-u";
619 (void) sprintf(uidstring, "%u", uid);
620 nargv[argindex++] = uidstring;
621 }
622
623 if (oflag) nargv[argindex++] = "-o";
624
625 if (new_logname) { /* redefine login name */
626 nargv[argindex++] = "-l";
627 nargv[argindex++] = new_logname;
628 }
629
630 if (nkeys > 0)
631 addkey_args(nargv, &argindex);
632
633 /* finally - login name */
634 nargv[argindex++] = logname;
635
636 /* set the last to null */
637 nargv[argindex++] = NULL;
638
639 /* now call passmgmt */
640 ret = PEX_FAILED;
641 for (tries = 3; ret != PEX_SUCCESS && tries--; ) {
642 switch (ret = call_passmgmt(nargv)) {
643 case PEX_SUCCESS:
644 case PEX_BUSY:
645 break;
646
647 case PEX_HOSED_FILES:
648 errmsg(M_HOSED_FILES);
649 exit(EX_INCONSISTENT);
650 break;
651
652 case PEX_SYNTAX:
653 case PEX_BADARG:
654 /* should NEVER occur that passmgmt usage is wrong */
655 if (is_role(usertype))
656 errmsg(M_MRUSAGE);
657 else
658 errmsg(M_MUSAGE);
659 exit(EX_SYNTAX);
660 break;
661
662 case PEX_BADUID:
663 /* uid in use - shouldn't happen print message anyway */
664 errmsg(M_UID_USED, uid);
665 exit(EX_ID_EXISTS);
666 break;
667
668 case PEX_BADNAME:
669 /* invalid loname */
670 errmsg(M_USED, logname);
671 exit(EX_NAME_EXISTS);
672 break;
673
674 default:
675 errmsg(M_UPDATE, "modified");
676 exit(ret);
677 break;
678 }
679 }
680 if (tries == 0) {
681 errmsg(M_UPDATE, "modified");
682 }
683
684 exit(ret);
685 /*NOTREACHED*/
686 }
687