Print this page
5110 want pam_timestamp module
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/cmd/su/su.c
+++ new/usr/src/cmd/su/su.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
13 13 * When distributing Covered Code, include this CDDL HEADER in each
↓ open down ↓ |
13 lines elided |
↑ open up ↑ |
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21 /*
22 22 * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
23 23 * Copyright 2012 Milan Jurik. All rights reserved.
24 + * Copyright 2014 Nexenta Systems, Inc.
24 25 */
25 26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
26 27 /* All Rights Reserved */
27 28
28 29 /* Copyright (c) 1987, 1988 Microsoft Corporation */
29 30 /* All Rights Reserved */
30 31
31 32 /*
32 33 * su [-] [name [arg ...]] change userid, `-' changes environment.
33 34 * If SULOG is defined, all attempts to su to another user are
34 35 * logged there.
35 36 * If CONSOLE is defined, all successful attempts to su to uid 0
36 37 * are also logged there.
37 38 *
38 39 * If su cannot create, open, or write entries into SULOG,
39 40 * (or on the CONSOLE, if defined), the entry will not
40 41 * be logged -- thus losing a record of the su's attempted
41 42 * during this period.
42 43 */
43 44
44 45 #include <stdio.h>
45 46 #include <sys/types.h>
46 47 #include <sys/stat.h>
47 48 #include <sys/param.h>
48 49 #include <unistd.h>
49 50 #include <stdlib.h>
50 51 #include <crypt.h>
51 52 #include <pwd.h>
52 53 #include <shadow.h>
53 54 #include <time.h>
54 55 #include <signal.h>
55 56 #include <fcntl.h>
56 57 #include <string.h>
57 58 #include <locale.h>
58 59 #include <syslog.h>
59 60 #include <sys/utsname.h>
60 61 #include <sys/wait.h>
61 62 #include <grp.h>
62 63 #include <deflt.h>
63 64 #include <limits.h>
64 65 #include <errno.h>
65 66 #include <stdarg.h>
66 67 #include <user_attr.h>
67 68 #include <priv.h>
68 69
69 70 #include <bsm/adt.h>
70 71 #include <bsm/adt_event.h>
71 72
72 73 #include <security/pam_appl.h>
73 74
74 75 #define PATH "/usr/bin:" /* path for users other than root */
75 76 #define SUPATH "/usr/sbin:/usr/bin" /* path for root */
76 77 #define SUPRMT "PS1=# " /* primary prompt for root */
77 78 #define ELIM 128
78 79 #define ROOT 0
79 80 #ifdef DYNAMIC_SU
80 81 #define EMBEDDED_NAME "embedded_su"
81 82 #define DEF_ATTEMPTS 3 /* attempts to change password */
82 83 #endif /* DYNAMIC_SU */
83 84
84 85 #define PW_FALSE 1 /* no password change */
85 86 #define PW_TRUE 2 /* successful password change */
86 87 #define PW_FAILED 3 /* failed password change */
87 88
88 89 /*
89 90 * Intervals to sleep after failed su
90 91 */
91 92 #ifndef SLEEPTIME
92 93 #define SLEEPTIME 4
93 94 #endif
94 95
95 96 #define DEFAULT_LOGIN "/etc/default/login"
96 97 #define DEFFILE "/etc/default/su"
97 98
98 99
99 100 char *Sulog, *Console;
100 101 char *Path, *Supath;
101 102
102 103 /*
103 104 * Locale variables to be propagated to "su -" environment
104 105 */
105 106 static char *initvar;
106 107 static char *initenv[] = {
107 108 "TZ", "LANG", "LC_CTYPE",
108 109 "LC_NUMERIC", "LC_TIME", "LC_COLLATE",
109 110 "LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0};
110 111 static char mail[30] = { "MAIL=/var/mail/" };
111 112
112 113 static void envalt(void);
113 114 static void log(char *, char *, int);
114 115 static void to(int);
115 116
116 117 enum messagemode { USAGE, ERR, WARN };
117 118 static void message(enum messagemode, char *, ...);
118 119
119 120 static char *alloc_vsprintf(const char *, va_list);
120 121 static char *tail(char *);
121 122
122 123 static void audit_success(int, struct passwd *);
123 124 static void audit_logout(adt_session_data_t *, au_event_t);
124 125 static void audit_failure(int, struct passwd *, char *, int);
125 126
126 127 #ifdef DYNAMIC_SU
127 128 static void validate(char *, int *);
128 129 static int legalenvvar(char *);
129 130 static int su_conv(int, struct pam_message **, struct pam_response **, void *);
130 131 static int emb_su_conv(int, struct pam_message **, struct pam_response **,
131 132 void *);
132 133 static void freeresponse(int, struct pam_response **response);
133 134 static struct pam_conv pam_conv = {su_conv, NULL};
134 135 static struct pam_conv emb_pam_conv = {emb_su_conv, NULL};
135 136 static void quotemsg(char *, ...);
136 137 static void readinitblock(void);
137 138 #else /* !DYNAMIC_SU */
138 139 static void update_audit(struct passwd *pwd);
139 140 #endif /* DYNAMIC_SU */
140 141
141 142 static pam_handle_t *pamh = NULL; /* Authentication handle */
142 143 struct passwd pwd;
143 144 char pwdbuf[1024]; /* buffer for getpwnam_r() */
144 145 char shell[] = "/usr/bin/sh"; /* default shell */
145 146 char safe_shell[] = "/sbin/sh"; /* "fallback" shell */
146 147 char su[PATH_MAX] = "su"; /* arg0 for exec of shprog */
147 148 char homedir[PATH_MAX] = "HOME=";
148 149 char logname[20] = "LOGNAME=";
149 150 char *suprmt = SUPRMT;
150 151 char termtyp[PATH_MAX] = "TERM=";
151 152 char *term;
152 153 char shelltyp[PATH_MAX] = "SHELL=";
153 154 char *hz;
154 155 char tznam[PATH_MAX];
155 156 char hzname[10] = "HZ=";
156 157 char path[PATH_MAX] = "PATH=";
157 158 char supath[PATH_MAX] = "PATH=";
158 159 char *envinit[ELIM];
159 160 extern char **environ;
160 161 char *ttyn;
161 162 char *username; /* the invoker */
162 163 static int dosyslog = 0; /* use syslog? */
163 164 char *myname;
164 165 #ifdef DYNAMIC_SU
165 166 int pam_flags = 0;
166 167 boolean_t embedded = B_FALSE;
167 168 #endif /* DYNAMIC_SU */
168 169
169 170 int
170 171 main(int argc, char **argv)
171 172 {
172 173 #ifndef DYNAMIC_SU
173 174 struct spwd sp;
174 175 char spbuf[1024]; /* buffer for getspnam_r() */
175 176 char *password;
176 177 #endif /* !DYNAMIC_SU */
177 178 char *nptr;
178 179 char *pshell;
179 180 int eflag = 0;
180 181 int envidx = 0;
181 182 uid_t uid;
182 183 gid_t gid;
183 184 char *dir, *shprog, *name;
184 185 char *ptr;
185 186 char *prog = argv[0];
186 187 #ifdef DYNAMIC_SU
187 188 int sleeptime = SLEEPTIME;
188 189 char **pam_env = 0;
189 190 int flags = 0;
190 191 int retcode;
191 192 int idx = 0;
192 193 #endif /* DYNAMIC_SU */
193 194 int pw_change = PW_FALSE;
194 195
195 196 (void) setlocale(LC_ALL, "");
196 197 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
197 198 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it wasn't */
198 199 #endif
199 200 (void) textdomain(TEXT_DOMAIN);
200 201
201 202 myname = tail(argv[0]);
202 203
203 204 #ifdef DYNAMIC_SU
204 205 if (strcmp(myname, EMBEDDED_NAME) == 0) {
205 206 embedded = B_TRUE;
206 207 setbuf(stdin, NULL);
207 208 setbuf(stdout, NULL);
208 209 readinitblock();
209 210 }
210 211 #endif /* DYNAMIC_SU */
211 212
212 213 if (argc > 1 && *argv[1] == '-') {
213 214 /* Explicitly check for just `-' (no trailing chars) */
214 215 if (strlen(argv[1]) == 1) {
215 216 eflag++; /* set eflag if `-' is specified */
216 217 argv++;
217 218 argc--;
218 219 } else {
219 220 message(USAGE,
220 221 gettext("Usage: %s [-] [ username [ arg ... ] ]"),
221 222 prog);
222 223 exit(1);
223 224 }
224 225 }
225 226
226 227 /*
227 228 * Determine specified userid, get their password file entry,
228 229 * and set variables to values in password file entry fields.
229 230 */
230 231 if (argc > 1) {
231 232 /*
232 233 * Usernames can't start with a `-', so we check for that to
233 234 * catch bad usage (like "su - -c ls").
234 235 */
235 236 if (*argv[1] == '-') {
236 237 message(USAGE,
237 238 gettext("Usage: %s [-] [ username [ arg ... ] ]"),
238 239 prog);
239 240 exit(1);
240 241 } else
241 242 nptr = argv[1]; /* use valid command-line username */
242 243 } else
243 244 nptr = "root"; /* use default "root" username */
244 245
245 246 if (defopen(DEFFILE) == 0) {
246 247
247 248 if (Sulog = defread("SULOG="))
248 249 Sulog = strdup(Sulog);
249 250 if (Console = defread("CONSOLE="))
250 251 Console = strdup(Console);
251 252 if (Path = defread("PATH="))
252 253 Path = strdup(Path);
253 254 if (Supath = defread("SUPATH="))
254 255 Supath = strdup(Supath);
255 256 if ((ptr = defread("SYSLOG=")) != NULL)
256 257 dosyslog = strcmp(ptr, "YES") == 0;
257 258
258 259 (void) defopen(NULL);
259 260 }
260 261 (void) strlcat(path, (Path) ? Path : PATH, sizeof (path));
261 262 (void) strlcat(supath, (Supath) ? Supath : SUPATH, sizeof (supath));
262 263
263 264 if ((ttyn = ttyname(0)) == NULL)
264 265 if ((ttyn = ttyname(1)) == NULL)
265 266 if ((ttyn = ttyname(2)) == NULL)
266 267 ttyn = "/dev/???";
267 268 if ((username = cuserid(NULL)) == NULL)
268 269 username = "(null)";
269 270
270 271 /*
271 272 * if Sulog defined, create SULOG, if it does not exist, with
272 273 * mode read/write user. Change owner and group to root
273 274 */
274 275 if (Sulog != NULL) {
275 276 (void) close(open(Sulog, O_WRONLY | O_APPEND | O_CREAT,
↓ open down ↓ |
242 lines elided |
↑ open up ↑ |
276 277 (S_IRUSR|S_IWUSR)));
277 278 (void) chown(Sulog, (uid_t)ROOT, (gid_t)ROOT);
278 279 }
279 280
280 281 #ifdef DYNAMIC_SU
281 282 if (pam_start(embedded ? EMBEDDED_NAME : "su", nptr,
282 283 embedded ? &emb_pam_conv : &pam_conv, &pamh) != PAM_SUCCESS)
283 284 exit(1);
284 285 if (pam_set_item(pamh, PAM_TTY, ttyn) != PAM_SUCCESS)
285 286 exit(1);
287 + if (getpwuid_r(getuid(), &pwd, pwdbuf, sizeof (pwdbuf)) == NULL ||
288 + pam_set_item(pamh, PAM_AUSER, pwd.pw_name) != PAM_SUCCESS)
289 + exit(1);
286 290 #endif /* DYNAMIC_SU */
287 291
288 292 openlog("su", LOG_CONS, LOG_AUTH);
289 293
290 294 #ifdef DYNAMIC_SU
291 295
292 296 /*
293 297 * Use the same value of sleeptime and password required that
294 298 * login(1) uses.
295 299 * This is obtained by reading the file /etc/default/login
296 300 * using the def*() functions
297 301 */
298 302 if (defopen(DEFAULT_LOGIN) == 0) {
299 303 if ((ptr = defread("SLEEPTIME=")) != NULL) {
300 304 sleeptime = atoi(ptr);
301 305 if (sleeptime < 0 || sleeptime > 5)
302 306 sleeptime = SLEEPTIME;
303 307 }
304 308
305 309 if ((ptr = defread("PASSREQ=")) != NULL &&
306 310 strcasecmp("YES", ptr) == 0)
307 311 pam_flags |= PAM_DISALLOW_NULL_AUTHTOK;
308 312
309 313 (void) defopen((char *)NULL);
310 314 }
311 315 /*
312 316 * Ignore SIGQUIT and SIGINT
313 317 */
314 318 (void) signal(SIGQUIT, SIG_IGN);
315 319 (void) signal(SIGINT, SIG_IGN);
316 320
317 321 /* call pam_authenticate() to authenticate the user through PAM */
318 322 if (getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL)
319 323 retcode = PAM_USER_UNKNOWN;
320 324 else if ((flags = (getuid() != (uid_t)ROOT)) != 0) {
321 325 retcode = pam_authenticate(pamh, pam_flags);
322 326 } else /* root user does not need to authenticate */
323 327 retcode = PAM_SUCCESS;
324 328
325 329 if (retcode != PAM_SUCCESS) {
326 330 /*
327 331 * 1st step: audit and log the error.
328 332 * 2nd step: sleep.
329 333 * 3rd step: print out message to user.
330 334 */
331 335 /* don't let audit_failure distinguish a role here */
332 336 audit_failure(PW_FALSE, NULL, nptr, retcode);
333 337 switch (retcode) {
334 338 case PAM_USER_UNKNOWN:
335 339 closelog();
336 340 (void) sleep(sleeptime);
337 341 message(ERR, gettext("Unknown id: %s"), nptr);
338 342 break;
339 343
340 344 case PAM_AUTH_ERR:
341 345 if (Sulog != NULL)
342 346 log(Sulog, nptr, 0); /* log entry */
343 347 if (dosyslog)
344 348 syslog(LOG_CRIT, "'su %s' failed for %s on %s",
345 349 pwd.pw_name, username, ttyn);
346 350 closelog();
347 351 (void) sleep(sleeptime);
348 352 message(ERR, gettext("Sorry"));
349 353 break;
350 354
351 355 case PAM_CONV_ERR:
352 356 default:
353 357 if (dosyslog)
354 358 syslog(LOG_CRIT, "'su %s' failed for %s on %s",
355 359 pwd.pw_name, username, ttyn);
356 360 closelog();
357 361 (void) sleep(sleeptime);
358 362 message(ERR, gettext("Sorry"));
359 363 break;
360 364 }
361 365
362 366 (void) signal(SIGQUIT, SIG_DFL);
363 367 (void) signal(SIGINT, SIG_DFL);
364 368 exit(1);
365 369 }
366 370 if (flags)
367 371 validate(username, &pw_change);
368 372 if (pam_setcred(pamh, PAM_REINITIALIZE_CRED) != PAM_SUCCESS) {
369 373 message(ERR, gettext("unable to set credentials"));
370 374 exit(2);
371 375 }
372 376 if (dosyslog)
373 377 syslog(pwd.pw_uid == 0 ? LOG_NOTICE : LOG_INFO,
374 378 "'su %s' succeeded for %s on %s",
375 379 pwd.pw_name, username, ttyn);
376 380 closelog();
377 381 (void) signal(SIGQUIT, SIG_DFL);
378 382 (void) signal(SIGINT, SIG_DFL);
379 383 #else /* !DYNAMIC_SU */
380 384 if ((getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL) ||
381 385 (getspnam_r(nptr, &sp, spbuf, sizeof (spbuf)) == NULL)) {
382 386 message(ERR, gettext("Unknown id: %s"), nptr);
383 387 audit_failure(PW_FALSE, NULL, nptr, PAM_USER_UNKNOWN);
384 388 closelog();
385 389 exit(1);
386 390 }
387 391
388 392 /*
389 393 * Prompt for password if invoking user is not root or
390 394 * if specified(new) user requires a password
391 395 */
392 396 if (sp.sp_pwdp[0] == '\0' || getuid() == (uid_t)ROOT)
393 397 goto ok;
394 398 password = getpass(gettext("Password:"));
395 399
396 400 if ((strcmp(sp.sp_pwdp, crypt(password, sp.sp_pwdp)) != 0)) {
397 401 /* clear password file entry */
398 402 (void) memset((void *)spbuf, 0, sizeof (spbuf));
399 403 if (Sulog != NULL)
400 404 log(Sulog, nptr, 0); /* log entry */
401 405 message(ERR, gettext("Sorry"));
402 406 audit_failure(PW_FALSE, NULL, nptr, PAM_AUTH_ERR);
403 407 if (dosyslog)
404 408 syslog(LOG_CRIT, "'su %s' failed for %s on %s",
405 409 pwd.pw_name, username, ttyn);
406 410 closelog();
407 411 exit(2);
408 412 }
409 413 /* clear password file entry */
410 414 (void) memset((void *)spbuf, 0, sizeof (spbuf));
411 415 ok:
412 416 /* update audit session in a non-pam environment */
413 417 update_audit(&pwd);
414 418 if (dosyslog)
415 419 syslog(pwd.pw_uid == 0 ? LOG_NOTICE : LOG_INFO,
416 420 "'su %s' succeeded for %s on %s",
417 421 pwd.pw_name, username, ttyn);
418 422 #endif /* DYNAMIC_SU */
419 423
420 424 audit_success(pw_change, &pwd);
421 425 uid = pwd.pw_uid;
422 426 gid = pwd.pw_gid;
423 427 dir = strdup(pwd.pw_dir);
424 428 shprog = strdup(pwd.pw_shell);
425 429 name = strdup(pwd.pw_name);
426 430
427 431 if (Sulog != NULL)
428 432 log(Sulog, nptr, 1); /* log entry */
429 433
430 434 /* set user and group ids to specified user */
431 435
432 436 /* set the real (and effective) GID */
433 437 if (setgid(gid) == -1) {
434 438 message(ERR, gettext("Invalid GID"));
435 439 exit(2);
436 440 }
437 441 /* Initialize the supplementary group access list. */
438 442 if (!nptr)
439 443 exit(2);
440 444 if (initgroups(nptr, gid) == -1) {
441 445 exit(2);
442 446 }
443 447 /* set the real (and effective) UID */
444 448 if (setuid(uid) == -1) {
445 449 message(ERR, gettext("Invalid UID"));
446 450 exit(2);
447 451 }
448 452
449 453 /*
450 454 * If new user's shell field is neither NULL nor equal to /usr/bin/sh,
451 455 * set:
452 456 *
453 457 * pshell = their shell
454 458 * su = [-]last component of shell's pathname
455 459 *
456 460 * Otherwise, set the shell to /usr/bin/sh and set argv[0] to '[-]su'.
457 461 */
458 462 if (shprog[0] != '\0' && strcmp(shell, shprog) != 0) {
459 463 char *p;
460 464
461 465 pshell = shprog;
462 466 (void) strcpy(su, eflag ? "-" : "");
463 467
464 468 if ((p = strrchr(pshell, '/')) != NULL)
465 469 (void) strlcat(su, p + 1, sizeof (su));
466 470 else
467 471 (void) strlcat(su, pshell, sizeof (su));
468 472 } else {
469 473 pshell = shell;
470 474 (void) strcpy(su, eflag ? "-su" : "su");
471 475 }
472 476
473 477 /*
474 478 * set environment variables for new user;
475 479 * arg0 for exec of shprog must now contain `-'
476 480 * so that environment of new user is given
477 481 */
478 482 if (eflag) {
479 483 int j;
480 484 char *var;
481 485
482 486 if (strlen(dir) == 0) {
483 487 (void) strcpy(dir, "/");
484 488 message(WARN, gettext("No directory! Using home=/"));
485 489 }
486 490 (void) strlcat(homedir, dir, sizeof (homedir));
487 491 (void) strlcat(logname, name, sizeof (logname));
488 492 if (hz = getenv("HZ"))
489 493 (void) strlcat(hzname, hz, sizeof (hzname));
490 494
491 495 (void) strlcat(shelltyp, pshell, sizeof (shelltyp));
492 496
493 497 if (chdir(dir) < 0) {
494 498 message(ERR, gettext("No directory!"));
495 499 exit(1);
496 500 }
497 501 envinit[envidx = 0] = homedir;
498 502 envinit[++envidx] = ((uid == (uid_t)ROOT) ? supath : path);
499 503 envinit[++envidx] = logname;
500 504 envinit[++envidx] = hzname;
501 505 if ((term = getenv("TERM")) != NULL) {
502 506 (void) strlcat(termtyp, term, sizeof (termtyp));
503 507 envinit[++envidx] = termtyp;
504 508 }
505 509 envinit[++envidx] = shelltyp;
506 510
507 511 (void) strlcat(mail, name, sizeof (mail));
508 512 envinit[++envidx] = mail;
509 513
510 514 /*
511 515 * Fetch the relevant locale/TZ environment variables from
512 516 * the inherited environment.
513 517 *
514 518 * We have a priority here for setting TZ. If TZ is set in
515 519 * in the inherited environment, that value remains top
516 520 * priority. If the file /etc/default/login has TIMEZONE set,
517 521 * that has second highest priority.
518 522 */
519 523 tznam[0] = '\0';
520 524 for (j = 0; initenv[j] != 0; j++) {
521 525 if (initvar = getenv(initenv[j])) {
522 526
523 527 /*
524 528 * Skip over values beginning with '/' for
525 529 * security.
526 530 */
527 531 if (initvar[0] == '/') continue;
528 532
529 533 if (strcmp(initenv[j], "TZ") == 0) {
530 534 (void) strcpy(tznam, "TZ=");
531 535 (void) strlcat(tznam, initvar,
532 536 sizeof (tznam));
533 537
534 538 } else {
535 539 var = (char *)
536 540 malloc(strlen(initenv[j])
537 541 + strlen(initvar)
538 542 + 2);
539 543 if (var == NULL) {
540 544 perror("malloc");
541 545 exit(4);
542 546 }
543 547 (void) strcpy(var, initenv[j]);
544 548 (void) strcat(var, "=");
545 549 (void) strcat(var, initvar);
546 550 envinit[++envidx] = var;
547 551 }
548 552 }
549 553 }
550 554
551 555 /*
552 556 * Check if TZ was found. If not then try to read it from
553 557 * /etc/default/login.
554 558 */
555 559 if (tznam[0] == '\0') {
556 560 if (defopen(DEFAULT_LOGIN) == 0) {
557 561 if (initvar = defread("TIMEZONE=")) {
558 562 (void) strcpy(tznam, "TZ=");
559 563 (void) strlcat(tznam, initvar,
560 564 sizeof (tznam));
561 565 }
562 566 (void) defopen(NULL);
563 567 }
564 568 }
565 569
566 570 if (tznam[0] != '\0')
567 571 envinit[++envidx] = tznam;
568 572
569 573 #ifdef DYNAMIC_SU
570 574 /*
571 575 * set the PAM environment variables -
572 576 * check for legal environment variables
573 577 */
574 578 if ((pam_env = pam_getenvlist(pamh)) != 0) {
575 579 while (pam_env[idx] != 0) {
576 580 if (envidx + 2 < ELIM &&
577 581 legalenvvar(pam_env[idx])) {
578 582 envinit[++envidx] = pam_env[idx];
579 583 }
580 584 idx++;
581 585 }
582 586 }
583 587 #endif /* DYNAMIC_SU */
584 588 envinit[++envidx] = NULL;
585 589 environ = envinit;
586 590 } else {
587 591 char **pp = environ, **qq, *p;
588 592
589 593 while ((p = *pp) != NULL) {
590 594 if (*p == 'L' && p[1] == 'D' && p[2] == '_') {
591 595 for (qq = pp; (*qq = qq[1]) != NULL; qq++)
592 596 ;
593 597 /* pp is not advanced */
594 598 } else {
595 599 pp++;
596 600 }
597 601 }
598 602 }
599 603
600 604 #ifdef DYNAMIC_SU
601 605 if (pamh)
602 606 (void) pam_end(pamh, PAM_SUCCESS);
603 607 #endif /* DYNAMIC_SU */
604 608
605 609 /*
606 610 * if new user is root:
607 611 * if CONSOLE defined, log entry there;
608 612 * if eflag not set, change environment to that of root.
609 613 */
610 614 if (uid == (uid_t)ROOT) {
611 615 if (Console != NULL)
612 616 if (strcmp(ttyn, Console) != 0) {
613 617 (void) signal(SIGALRM, to);
614 618 (void) alarm(30);
615 619 log(Console, nptr, 1);
616 620 (void) alarm(0);
617 621 }
618 622 if (!eflag)
619 623 envalt();
620 624 }
621 625
622 626 /*
623 627 * Default for SIGCPU and SIGXFSZ. Shells inherit
624 628 * signal disposition from parent. And the
625 629 * shells should have default dispositions for these
626 630 * signals.
627 631 */
628 632 (void) signal(SIGXCPU, SIG_DFL);
629 633 (void) signal(SIGXFSZ, SIG_DFL);
630 634
631 635 #ifdef DYNAMIC_SU
632 636 if (embedded) {
633 637 (void) puts("SUCCESS");
634 638 /*
635 639 * After this point, we're no longer talking the
636 640 * embedded_su protocol, so turn it off.
637 641 */
638 642 embedded = B_FALSE;
639 643 }
640 644 #endif /* DYNAMIC_SU */
641 645
642 646 /*
643 647 * if additional arguments, exec shell program with array
644 648 * of pointers to arguments:
645 649 * -> if shell = default, then su = [-]su
646 650 * -> if shell != default, then su = [-]last component of
647 651 * shell's pathname
648 652 *
649 653 * if no additional arguments, exec shell with arg0 of su
650 654 * where:
651 655 * -> if shell = default, then su = [-]su
652 656 * -> if shell != default, then su = [-]last component of
653 657 * shell's pathname
654 658 */
655 659 if (argc > 2) {
656 660 argv[1] = su;
657 661 (void) execv(pshell, &argv[1]);
658 662 } else
659 663 (void) execl(pshell, su, 0);
660 664
661 665
662 666 /*
663 667 * Try to clean up after an administrator who has made a mistake
664 668 * configuring root's shell; if root's shell is other than /sbin/sh,
665 669 * try exec'ing /sbin/sh instead.
666 670 */
667 671 if ((uid == (uid_t)ROOT) && (strcmp(name, "root") == 0) &&
668 672 (strcmp(safe_shell, pshell) != 0)) {
669 673 message(WARN,
670 674 gettext("No shell %s. Trying fallback shell %s."),
671 675 pshell, safe_shell);
672 676
673 677 if (eflag) {
674 678 (void) strcpy(su, "-sh");
675 679 (void) strlcpy(shelltyp + strlen("SHELL="),
676 680 safe_shell, sizeof (shelltyp) - strlen("SHELL="));
677 681 } else {
678 682 (void) strcpy(su, "sh");
679 683 }
680 684
681 685 if (argc > 2) {
682 686 argv[1] = su;
683 687 (void) execv(safe_shell, &argv[1]);
684 688 } else {
685 689 (void) execl(safe_shell, su, 0);
686 690 }
687 691 message(ERR, gettext("Couldn't exec fallback shell %s: %s"),
688 692 safe_shell, strerror(errno));
689 693 } else {
690 694 message(ERR, gettext("No shell"));
691 695 }
692 696 return (3);
693 697 }
694 698
695 699 /*
696 700 * Environment altering routine -
697 701 * This routine is called when a user is su'ing to root
698 702 * without specifying the - flag.
699 703 * The user's PATH and PS1 variables are reset
700 704 * to the correct value for root.
701 705 * All of the user's other environment variables retain
702 706 * their current values after the su (if they are exported).
703 707 */
704 708 static void
705 709 envalt(void)
706 710 {
707 711 /*
708 712 * If user has PATH variable in their environment, change its value
709 713 * to /bin:/etc:/usr/bin ;
710 714 * if user does not have PATH variable, add it to the user's
711 715 * environment;
712 716 * if either of the above fail, an error message is printed.
713 717 */
714 718 if (putenv(supath) != 0) {
715 719 message(ERR,
716 720 gettext("unable to obtain memory to expand environment"));
717 721 exit(4);
718 722 }
719 723
720 724 /*
721 725 * If user has PROMPT variable in their environment, change its value
722 726 * to # ;
723 727 * if user does not have PROMPT variable, add it to the user's
724 728 * environment;
725 729 * if either of the above fail, an error message is printed.
726 730 */
727 731 if (putenv(suprmt) != 0) {
728 732 message(ERR,
729 733 gettext("unable to obtain memory to expand environment"));
730 734 exit(4);
731 735 }
732 736 }
733 737
734 738 /*
735 739 * Logging routine -
736 740 * where = SULOG or CONSOLE
737 741 * towho = specified user ( user being su'ed to )
738 742 * how = 0 if su attempt failed; 1 if su attempt succeeded
739 743 */
740 744 static void
741 745 log(char *where, char *towho, int how)
742 746 {
743 747 FILE *logf;
744 748 time_t now;
745 749 struct tm *tmp;
746 750
747 751 /*
748 752 * open SULOG or CONSOLE - if open fails, return
749 753 */
750 754 if ((logf = fopen(where, "a")) == NULL)
751 755 return;
752 756
753 757 now = time(0);
754 758 tmp = localtime(&now);
755 759
756 760 /*
757 761 * write entry into SULOG or onto CONSOLE - if write fails, return
758 762 */
759 763 (void) fprintf(logf, "SU %.2d/%.2d %.2d:%.2d %c %s %s-%s\n",
760 764 tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min,
761 765 how ? '+' : '-', ttyn + sizeof ("/dev/") - 1, username, towho);
762 766
763 767 (void) fclose(logf); /* close SULOG or CONSOLE */
764 768 }
765 769
766 770 /*ARGSUSED*/
767 771 static void
768 772 to(int sig)
769 773 {}
770 774
771 775 /*
772 776 * audit_success - audit successful su
773 777 *
774 778 * Entry process audit context established -- i.e., pam_setcred()
775 779 * or equivalent called.
776 780 * pw_change = PW_TRUE, if successful password change audit
777 781 * required.
778 782 * pwd = passwd entry for new user.
779 783 */
780 784
781 785 static void
782 786 audit_success(int pw_change, struct passwd *pwd)
783 787 {
784 788 adt_session_data_t *ah = NULL;
785 789 adt_event_data_t *event;
786 790 au_event_t event_id = ADT_su;
787 791 userattr_t *user_entry;
788 792 char *kva_value;
789 793
790 794 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
791 795 syslog(LOG_AUTH | LOG_ALERT,
792 796 "adt_start_session(ADT_su): %m");
793 797 return;
794 798 }
795 799 if (((user_entry = getusernam(pwd->pw_name)) != NULL) &&
796 800 ((kva_value = kva_match((kva_t *)user_entry->attr,
797 801 USERATTR_TYPE_KW)) != NULL) &&
798 802 ((strcmp(kva_value, USERATTR_TYPE_NONADMIN_KW) == 0) ||
799 803 (strcmp(kva_value, USERATTR_TYPE_ADMIN_KW) == 0))) {
800 804 event_id = ADT_role_login;
801 805 }
802 806 free_userattr(user_entry); /* OK to use, checks for NULL */
803 807
804 808 /* since proc uid/gid not yet updated */
805 809 if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid,
806 810 pwd->pw_gid, NULL, ADT_USER) != 0) {
807 811 syslog(LOG_AUTH | LOG_ERR,
808 812 "adt_set_user(ADT_su, ADT_FAILURE): %m");
809 813 }
810 814 if ((event = adt_alloc_event(ah, event_id)) == NULL) {
811 815 syslog(LOG_AUTH | LOG_ALERT, "adt_alloc_event(ADT_su): %m");
812 816 } else if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
813 817 syslog(LOG_AUTH | LOG_ALERT,
814 818 "adt_put_event(ADT_su, ADT_SUCCESS): %m");
815 819 }
816 820
817 821 if (pw_change == PW_TRUE) {
818 822 /* Also audit password change */
819 823 adt_free_event(event);
820 824 if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) {
821 825 syslog(LOG_AUTH | LOG_ALERT,
822 826 "adt_alloc_event(ADT_passwd): %m");
823 827 } else if (adt_put_event(event, ADT_SUCCESS,
824 828 ADT_SUCCESS) != 0) {
825 829 syslog(LOG_AUTH | LOG_ALERT,
826 830 "adt_put_event(ADT_passwd, ADT_SUCCESS): %m");
827 831 }
828 832 }
829 833 adt_free_event(event);
830 834 /*
831 835 * The preceeding code is a noop if audit isn't enabled,
832 836 * but, let's not make a new process when it's not necessary.
833 837 */
834 838 if (adt_audit_state(AUC_AUDITING)) {
835 839 audit_logout(ah, event_id); /* fork to catch logout */
836 840 }
837 841 (void) adt_end_session(ah);
838 842 }
839 843
840 844
841 845 /*
842 846 * audit_logout - audit successful su logout
843 847 *
844 848 * Entry ah = Successful su audit handle
845 849 * event_id = su event ID: ADT_su, ADT_role_login
846 850 *
847 851 * Exit Errors are just ignored and we go on.
848 852 * su logout event written.
849 853 */
850 854 static void
851 855 audit_logout(adt_session_data_t *ah, au_event_t event_id)
852 856 {
853 857 adt_event_data_t *event;
854 858 int status; /* wait status */
855 859 pid_t pid;
856 860 priv_set_t *priv; /* waiting process privs */
857 861
858 862 if (event_id == ADT_su) {
859 863 event_id = ADT_su_logout;
860 864 } else {
861 865 event_id = ADT_role_logout;
862 866 }
863 867 if ((event = adt_alloc_event(ah, event_id)) == NULL) {
864 868 syslog(LOG_AUTH | LOG_ALERT,
865 869 "adt_alloc_event(ADT_su_logout): %m");
866 870 return;
867 871 }
868 872 if ((priv = priv_allocset()) == NULL) {
869 873 syslog(LOG_AUTH | LOG_ALERT,
870 874 "su audit_logout: could not alloc basic privs: %m");
871 875 adt_free_event(event);
872 876 return;
873 877 }
874 878
875 879 /*
876 880 * The child returns and continues su processing.
877 881 * The parent's sole job is to wait for child exit, write the
878 882 * logout audit record, and replay the child's exit code.
879 883 */
880 884 if ((pid = fork()) == 0) {
881 885 /* child */
882 886
883 887 adt_free_event(event);
884 888 priv_freeset(priv);
885 889 return;
886 890 }
887 891 if (pid == -1) {
888 892 /* failure */
889 893
890 894 syslog(LOG_AUTH | LOG_ALERT,
891 895 "su audit_logout: could not fork: %m");
892 896 adt_free_event(event);
893 897 priv_freeset(priv);
894 898 return;
895 899 }
896 900
897 901 /* parent process */
898 902
899 903 /*
900 904 * When this routine is called, the current working
901 905 * directory is the unknown and there are unknown open
902 906 * files. For the waiting process, change the current
903 907 * directory to root and close open files so that
904 908 * directories can be unmounted if necessary.
905 909 */
906 910 if (chdir("/") != 0) {
907 911 syslog(LOG_AUTH | LOG_ALERT,
908 912 "su audit_logout: could not chdir /: %m");
909 913 }
910 914 /*
911 915 * Reduce privileges to just those needed.
912 916 */
913 917 priv_basicset(priv);
914 918 (void) priv_delset(priv, PRIV_PROC_EXEC);
915 919 (void) priv_delset(priv, PRIV_PROC_FORK);
916 920 (void) priv_delset(priv, PRIV_PROC_INFO);
917 921 (void) priv_delset(priv, PRIV_PROC_SESSION);
918 922 (void) priv_delset(priv, PRIV_FILE_LINK_ANY);
919 923 if ((priv_addset(priv, PRIV_PROC_AUDIT) != 0) ||
920 924 (setppriv(PRIV_SET, PRIV_PERMITTED, priv) != 0)) {
921 925 syslog(LOG_AUTH | LOG_ALERT,
922 926 "su audit_logout: could not reduce privs: %m");
923 927 }
924 928 closefrom(0);
925 929 priv_freeset(priv);
926 930
927 931 for (;;) {
928 932 if (pid != waitpid(pid, &status, WUNTRACED)) {
929 933 if (errno == ECHILD) {
930 934 /*
931 935 * No existing child with the given pid. Lets
932 936 * audit the logout.
933 937 */
934 938 break;
935 939 }
936 940 continue;
937 941 }
938 942
939 943 if (WIFEXITED(status) || WIFSIGNALED(status)) {
940 944 /*
941 945 * The child shell exited or was terminated by
942 946 * a signal. Lets audit logout.
943 947 */
944 948 break;
945 949 } else if (WIFSTOPPED(status)) {
946 950 pid_t pgid;
947 951 int fd;
948 952 void (*sg_handler)();
949 953 /*
950 954 * The child shell has been stopped/suspended.
951 955 * We need to suspend here as well and pass down
952 956 * the control to the parent process.
953 957 */
954 958 sg_handler = signal(WSTOPSIG(status), SIG_DFL);
955 959 (void) sigsend(P_PGID, getpgrp(), WSTOPSIG(status));
956 960 /*
957 961 * We stop here. When resumed, mark the child
958 962 * shell group as foreground process group
959 963 * which gives the child shell a control over
960 964 * the controlling terminal.
961 965 */
962 966 (void) signal(WSTOPSIG(status), sg_handler);
963 967
964 968 pgid = getpgid(pid);
965 969 if ((fd = open("/dev/tty", O_RDWR)) != -1) {
966 970 /*
967 971 * Pass down the control over the controlling
968 972 * terminal iff we are in a foreground process
969 973 * group. Otherwise, we are in a background
970 974 * process group and the kernel will send
971 975 * SIGTTOU signal to stop us (by default).
972 976 */
973 977 if (tcgetpgrp(fd) == getpgrp()) {
974 978 (void) tcsetpgrp(fd, pgid);
975 979 }
976 980 (void) close(fd);
977 981 }
978 982 /* Wake up the child shell */
979 983 (void) sigsend(P_PGID, pgid, SIGCONT);
980 984 }
981 985 }
982 986
983 987 (void) adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS);
984 988 adt_free_event(event);
985 989 (void) adt_end_session(ah);
986 990 exit(WEXITSTATUS(status));
987 991 }
988 992
989 993
990 994 /*
991 995 * audit_failure - audit failed su
992 996 *
993 997 * Entry New audit context not set.
994 998 * pw_change == PW_FALSE, if no password change requested.
995 999 * PW_FAILED, if failed password change audit
996 1000 * required.
997 1001 * pwd = NULL, or password entry to use.
998 1002 * user = username entered. Add to record if pwd == NULL.
999 1003 * pamerr = PAM error code; reason for failure.
1000 1004 */
1001 1005
1002 1006 static void
1003 1007 audit_failure(int pw_change, struct passwd *pwd, char *user, int pamerr)
1004 1008 {
1005 1009 adt_session_data_t *ah; /* audit session handle */
1006 1010 adt_event_data_t *event; /* event to generate */
1007 1011 au_event_t event_id = ADT_su;
1008 1012 userattr_t *user_entry;
1009 1013 char *kva_value;
1010 1014
1011 1015 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
1012 1016 syslog(LOG_AUTH | LOG_ALERT,
1013 1017 "adt_start_session(ADT_su, ADT_FAILURE): %m");
1014 1018 return;
1015 1019 }
1016 1020
1017 1021 if (pwd != NULL) {
1018 1022 /* target user authenticated, merge audit state */
1019 1023 if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid,
1020 1024 pwd->pw_gid, NULL, ADT_UPDATE) != 0) {
1021 1025 syslog(LOG_AUTH | LOG_ERR,
1022 1026 "adt_set_user(ADT_su, ADT_FAILURE): %m");
1023 1027 }
1024 1028 if (((user_entry = getusernam(pwd->pw_name)) != NULL) &&
1025 1029 ((kva_value = kva_match((kva_t *)user_entry->attr,
1026 1030 USERATTR_TYPE_KW)) != NULL) &&
1027 1031 ((strcmp(kva_value, USERATTR_TYPE_NONADMIN_KW) == 0) ||
1028 1032 (strcmp(kva_value, USERATTR_TYPE_ADMIN_KW) == 0))) {
1029 1033 event_id = ADT_role_login;
1030 1034 }
1031 1035 free_userattr(user_entry); /* OK to use, checks for NULL */
1032 1036 }
1033 1037 if ((event = adt_alloc_event(ah, event_id)) == NULL) {
1034 1038 syslog(LOG_AUTH | LOG_ALERT,
1035 1039 "adt_alloc_event(ADT_su, ADT_FAILURE): %m");
1036 1040 return;
1037 1041 }
1038 1042 /*
1039 1043 * can't tell if user not found is a role, so always use su
1040 1044 * If we do pass in pwd when the JNI is fixed, then can
1041 1045 * distinguish and set name in both su and role_login
1042 1046 */
1043 1047 if (pwd == NULL) {
1044 1048 /*
1045 1049 * this should be "fail_user" rather than "message"
1046 1050 * see adt_xml. The JNI breaks, so for now we leave
1047 1051 * this alone.
1048 1052 */
1049 1053 event->adt_su.message = user;
1050 1054 }
1051 1055 if (adt_put_event(event, ADT_FAILURE,
1052 1056 ADT_FAIL_PAM + pamerr) != 0) {
1053 1057 syslog(LOG_AUTH | LOG_ALERT,
1054 1058 "adt_put_event(ADT_su(ADT_FAIL, %s): %m",
1055 1059 pam_strerror(pamh, pamerr));
1056 1060 }
1057 1061 if (pw_change != PW_FALSE) {
1058 1062 /* Also audit password change failed */
1059 1063 adt_free_event(event);
1060 1064 if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) {
1061 1065 syslog(LOG_AUTH | LOG_ALERT,
1062 1066 "su: adt_alloc_event(ADT_passwd): %m");
1063 1067 } else if (adt_put_event(event, ADT_FAILURE,
1064 1068 ADT_FAIL_PAM + pamerr) != 0) {
1065 1069 syslog(LOG_AUTH | LOG_ALERT,
1066 1070 "su: adt_put_event(ADT_passwd, ADT_FAILURE): %m");
1067 1071 }
1068 1072 }
1069 1073 adt_free_event(event);
1070 1074 (void) adt_end_session(ah);
1071 1075 }
1072 1076
1073 1077 #ifdef DYNAMIC_SU
1074 1078 /*
1075 1079 * su_conv():
1076 1080 * This is the conv (conversation) function called from
1077 1081 * a PAM authentication module to print error messages
1078 1082 * or garner information from the user.
1079 1083 */
1080 1084 /*ARGSUSED*/
1081 1085 static int
1082 1086 su_conv(int num_msg, struct pam_message **msg, struct pam_response **response,
1083 1087 void *appdata_ptr)
1084 1088 {
1085 1089 struct pam_message *m;
1086 1090 struct pam_response *r;
1087 1091 char *temp;
1088 1092 int k;
1089 1093 char respbuf[PAM_MAX_RESP_SIZE];
1090 1094
1091 1095 if (num_msg <= 0)
1092 1096 return (PAM_CONV_ERR);
1093 1097
1094 1098 *response = (struct pam_response *)calloc(num_msg,
1095 1099 sizeof (struct pam_response));
1096 1100 if (*response == NULL)
1097 1101 return (PAM_BUF_ERR);
1098 1102
1099 1103 k = num_msg;
1100 1104 m = *msg;
1101 1105 r = *response;
1102 1106 while (k--) {
1103 1107
1104 1108 switch (m->msg_style) {
1105 1109
1106 1110 case PAM_PROMPT_ECHO_OFF:
1107 1111 errno = 0;
1108 1112 temp = getpassphrase(m->msg);
1109 1113 if (errno == EINTR)
1110 1114 return (PAM_CONV_ERR);
1111 1115 if (temp != NULL) {
1112 1116 r->resp = strdup(temp);
1113 1117 if (r->resp == NULL) {
1114 1118 freeresponse(num_msg, response);
1115 1119 return (PAM_BUF_ERR);
1116 1120 }
1117 1121 }
1118 1122 break;
1119 1123
1120 1124 case PAM_PROMPT_ECHO_ON:
1121 1125 if (m->msg != NULL) {
1122 1126 (void) fputs(m->msg, stdout);
1123 1127 }
1124 1128
1125 1129 (void) fgets(respbuf, sizeof (respbuf), stdin);
1126 1130 temp = strchr(respbuf, '\n');
1127 1131 if (temp != NULL)
1128 1132 *temp = '\0';
1129 1133
1130 1134 r->resp = strdup(respbuf);
1131 1135 if (r->resp == NULL) {
1132 1136 freeresponse(num_msg, response);
1133 1137 return (PAM_BUF_ERR);
1134 1138 }
1135 1139 break;
1136 1140
1137 1141 case PAM_ERROR_MSG:
1138 1142 if (m->msg != NULL) {
1139 1143 (void) fputs(m->msg, stderr);
1140 1144 (void) fputs("\n", stderr);
1141 1145 }
1142 1146 break;
1143 1147
1144 1148 case PAM_TEXT_INFO:
1145 1149 if (m->msg != NULL) {
1146 1150 (void) fputs(m->msg, stdout);
1147 1151 (void) fputs("\n", stdout);
1148 1152 }
1149 1153 break;
1150 1154
1151 1155 default:
1152 1156 break;
1153 1157 }
1154 1158 m++;
1155 1159 r++;
1156 1160 }
1157 1161 return (PAM_SUCCESS);
1158 1162 }
1159 1163
1160 1164 /*
1161 1165 * emb_su_conv():
1162 1166 * This is the conv (conversation) function called from
1163 1167 * a PAM authentication module to print error messages
1164 1168 * or garner information from the user.
1165 1169 * This version is used for embedded_su.
1166 1170 */
1167 1171 /*ARGSUSED*/
1168 1172 static int
1169 1173 emb_su_conv(int num_msg, struct pam_message **msg,
1170 1174 struct pam_response **response, void *appdata_ptr)
1171 1175 {
1172 1176 struct pam_message *m;
1173 1177 struct pam_response *r;
1174 1178 char *temp;
1175 1179 int k;
1176 1180 char respbuf[PAM_MAX_RESP_SIZE];
1177 1181
1178 1182 if (num_msg <= 0)
1179 1183 return (PAM_CONV_ERR);
1180 1184
1181 1185 *response = (struct pam_response *)calloc(num_msg,
1182 1186 sizeof (struct pam_response));
1183 1187 if (*response == NULL)
1184 1188 return (PAM_BUF_ERR);
1185 1189
1186 1190 /* First, send the prompts */
1187 1191 (void) printf("CONV %d\n", num_msg);
1188 1192 k = num_msg;
1189 1193 m = *msg;
1190 1194 while (k--) {
1191 1195 switch (m->msg_style) {
1192 1196
1193 1197 case PAM_PROMPT_ECHO_OFF:
1194 1198 (void) puts("PAM_PROMPT_ECHO_OFF");
1195 1199 goto msg_common;
1196 1200
1197 1201 case PAM_PROMPT_ECHO_ON:
1198 1202 (void) puts("PAM_PROMPT_ECHO_ON");
1199 1203 goto msg_common;
1200 1204
1201 1205 case PAM_ERROR_MSG:
1202 1206 (void) puts("PAM_ERROR_MSG");
1203 1207 goto msg_common;
1204 1208
1205 1209 case PAM_TEXT_INFO:
1206 1210 (void) puts("PAM_TEXT_INFO");
1207 1211 /* fall through to msg_common */
1208 1212 msg_common:
1209 1213 if (m->msg == NULL)
1210 1214 quotemsg(NULL);
1211 1215 else
1212 1216 quotemsg("%s", m->msg);
1213 1217 break;
1214 1218
1215 1219 default:
1216 1220 break;
1217 1221 }
1218 1222 m++;
1219 1223 }
1220 1224
1221 1225 /* Next, collect the responses */
1222 1226 k = num_msg;
1223 1227 m = *msg;
1224 1228 r = *response;
1225 1229 while (k--) {
1226 1230
1227 1231 switch (m->msg_style) {
1228 1232
1229 1233 case PAM_PROMPT_ECHO_OFF:
1230 1234 case PAM_PROMPT_ECHO_ON:
1231 1235 (void) fgets(respbuf, sizeof (respbuf), stdin);
1232 1236
1233 1237 temp = strchr(respbuf, '\n');
1234 1238 if (temp != NULL)
1235 1239 *temp = '\0';
1236 1240
1237 1241 r->resp = strdup(respbuf);
1238 1242 if (r->resp == NULL) {
1239 1243 freeresponse(num_msg, response);
1240 1244 return (PAM_BUF_ERR);
1241 1245 }
1242 1246
1243 1247 break;
1244 1248
1245 1249 case PAM_ERROR_MSG:
1246 1250 case PAM_TEXT_INFO:
1247 1251 break;
1248 1252
1249 1253 default:
1250 1254 break;
1251 1255 }
1252 1256 m++;
1253 1257 r++;
1254 1258 }
1255 1259 return (PAM_SUCCESS);
1256 1260 }
1257 1261
1258 1262 static void
1259 1263 freeresponse(int num_msg, struct pam_response **response)
1260 1264 {
1261 1265 struct pam_response *r;
1262 1266 int i;
1263 1267
1264 1268 /* free responses */
1265 1269 r = *response;
1266 1270 for (i = 0; i < num_msg; i++, r++) {
1267 1271 if (r->resp != NULL) {
1268 1272 /* Zap it in case it's a password */
1269 1273 (void) memset(r->resp, '\0', strlen(r->resp));
1270 1274 free(r->resp);
1271 1275 }
1272 1276 }
1273 1277 free(*response);
1274 1278 *response = NULL;
1275 1279 }
1276 1280
1277 1281 /*
1278 1282 * Print a message, applying quoting for lines starting with '.'.
1279 1283 *
1280 1284 * I18n note: \n is "safe" in all locales, and all locales use
1281 1285 * a high-bit-set character to start multibyte sequences, so
1282 1286 * scanning for a \n followed by a '.' is safe.
1283 1287 */
1284 1288 static void
1285 1289 quotemsg(char *fmt, ...)
1286 1290 {
1287 1291 if (fmt != NULL) {
1288 1292 char *msg;
1289 1293 char *p;
1290 1294 boolean_t bol;
1291 1295 va_list v;
1292 1296
1293 1297 va_start(v, fmt);
1294 1298 msg = alloc_vsprintf(fmt, v);
1295 1299 va_end(v);
1296 1300
1297 1301 bol = B_TRUE;
1298 1302 for (p = msg; *p != '\0'; p++) {
1299 1303 if (bol) {
1300 1304 if (*p == '.')
1301 1305 (void) putchar('.');
1302 1306 bol = B_FALSE;
1303 1307 }
1304 1308 (void) putchar(*p);
1305 1309 if (*p == '\n')
1306 1310 bol = B_TRUE;
1307 1311 }
1308 1312 (void) putchar('\n');
1309 1313 free(msg);
1310 1314 }
1311 1315 (void) putchar('.');
1312 1316 (void) putchar('\n');
1313 1317 }
1314 1318
1315 1319 /*
1316 1320 * validate - Check that the account is valid for switching to.
1317 1321 */
1318 1322 static void
1319 1323 validate(char *usernam, int *pw_change)
1320 1324 {
1321 1325 int error;
1322 1326 int tries;
1323 1327
1324 1328 if ((error = pam_acct_mgmt(pamh, pam_flags)) != PAM_SUCCESS) {
1325 1329 if (Sulog != NULL)
1326 1330 log(Sulog, pwd.pw_name, 0); /* log entry */
1327 1331 if (error == PAM_NEW_AUTHTOK_REQD) {
1328 1332 tries = 0;
1329 1333 message(ERR, gettext("Password for user "
1330 1334 "'%s' has expired"), pwd.pw_name);
1331 1335 while ((error = pam_chauthtok(pamh,
1332 1336 PAM_CHANGE_EXPIRED_AUTHTOK)) != PAM_SUCCESS) {
1333 1337 if ((error == PAM_AUTHTOK_ERR ||
1334 1338 error == PAM_TRY_AGAIN) &&
1335 1339 (tries++ < DEF_ATTEMPTS)) {
1336 1340 continue;
1337 1341 }
1338 1342 message(ERR, gettext("Sorry"));
1339 1343 audit_failure(PW_FAILED, &pwd, NULL, error);
1340 1344 if (dosyslog)
1341 1345 syslog(LOG_CRIT,
1342 1346 "'su %s' failed for %s on %s",
1343 1347 pwd.pw_name, usernam, ttyn);
1344 1348 closelog();
1345 1349 exit(1);
1346 1350 }
1347 1351 *pw_change = PW_TRUE;
1348 1352 return;
1349 1353 } else {
1350 1354 message(ERR, gettext("Sorry"));
1351 1355 audit_failure(PW_FALSE, &pwd, NULL, error);
1352 1356 if (dosyslog)
1353 1357 syslog(LOG_CRIT, "'su %s' failed for %s on %s",
1354 1358 pwd.pw_name, usernam, ttyn);
1355 1359 closelog();
1356 1360 exit(3);
1357 1361 }
1358 1362 }
1359 1363 }
1360 1364
1361 1365 static char *illegal[] = {
1362 1366 "SHELL=",
1363 1367 "HOME=",
1364 1368 "LOGNAME=",
1365 1369 #ifndef NO_MAIL
1366 1370 "MAIL=",
1367 1371 #endif
1368 1372 "CDPATH=",
1369 1373 "IFS=",
1370 1374 "PATH=",
1371 1375 "TZ=",
1372 1376 "HZ=",
1373 1377 "TERM=",
1374 1378 0
1375 1379 };
1376 1380
1377 1381 /*
1378 1382 * legalenvvar - can PAM modules insert this environmental variable?
1379 1383 */
1380 1384
1381 1385 static int
1382 1386 legalenvvar(char *s)
1383 1387 {
1384 1388 register char **p;
1385 1389
1386 1390 for (p = illegal; *p; p++)
1387 1391 if (strncmp(s, *p, strlen(*p)) == 0)
1388 1392 return (0);
1389 1393
1390 1394 if (s[0] == 'L' && s[1] == 'D' && s[2] == '_')
1391 1395 return (0);
1392 1396
1393 1397 return (1);
1394 1398 }
1395 1399
1396 1400 /*
1397 1401 * The embedded_su protocol allows the client application to supply
1398 1402 * an initialization block terminated by a line with just a "." on it.
1399 1403 *
1400 1404 * This initialization block is currently unused, reserved for future
1401 1405 * expansion. Ignore it. This is made very slightly more complex by
1402 1406 * the desire to cleanly ignore input lines of any length, while still
1403 1407 * correctly detecting a line with just a "." on it.
1404 1408 *
1405 1409 * I18n note: It appears that none of the Solaris-supported locales
1406 1410 * use 0x0a for any purpose other than newline, so looking for '\n'
1407 1411 * seems safe.
1408 1412 * All locales use high-bit-set leadin characters for their multi-byte
1409 1413 * sequences, so a line consisting solely of ".\n" is what it appears
1410 1414 * to be.
1411 1415 */
1412 1416 static void
1413 1417 readinitblock(void)
1414 1418 {
1415 1419 char buf[100];
1416 1420 boolean_t bol;
1417 1421
1418 1422 bol = B_TRUE;
1419 1423 for (;;) {
1420 1424 if (fgets(buf, sizeof (buf), stdin) == NULL)
1421 1425 return;
1422 1426 if (bol && strcmp(buf, ".\n") == 0)
1423 1427 return;
1424 1428 bol = (strchr(buf, '\n') != NULL);
1425 1429 }
1426 1430 }
1427 1431 #else /* !DYNAMIC_SU */
1428 1432 static void
1429 1433 update_audit(struct passwd *pwd)
1430 1434 {
1431 1435 adt_session_data_t *ah; /* audit session handle */
1432 1436
1433 1437 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
1434 1438 message(ERR, gettext("Sorry"));
1435 1439 if (dosyslog)
1436 1440 syslog(LOG_CRIT, "'su %s' failed for %s "
1437 1441 "cannot start audit session %m",
1438 1442 pwd->pw_name, username);
1439 1443 closelog();
1440 1444 exit(2);
1441 1445 }
1442 1446 if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid,
1443 1447 pwd->pw_gid, NULL, ADT_UPDATE) != 0) {
1444 1448 if (dosyslog)
1445 1449 syslog(LOG_CRIT, "'su %s' failed for %s "
1446 1450 "cannot update audit session %m",
1447 1451 pwd->pw_name, username);
1448 1452 closelog();
1449 1453 exit(2);
1450 1454 }
1451 1455 }
1452 1456 #endif /* DYNAMIC_SU */
1453 1457
1454 1458 /*
1455 1459 * Report an error, either a fatal one, a warning, or a usage message,
1456 1460 * depending on the mode parameter.
1457 1461 */
1458 1462 /*ARGSUSED*/
1459 1463 static void
1460 1464 message(enum messagemode mode, char *fmt, ...)
1461 1465 {
1462 1466 char *s;
1463 1467 va_list v;
1464 1468
1465 1469 va_start(v, fmt);
1466 1470 s = alloc_vsprintf(fmt, v);
1467 1471 va_end(v);
1468 1472
1469 1473 #ifdef DYNAMIC_SU
1470 1474 if (embedded) {
1471 1475 if (mode == WARN) {
1472 1476 (void) printf("CONV 1\n");
1473 1477 (void) printf("PAM_ERROR_MSG\n");
1474 1478 } else { /* ERR, USAGE */
1475 1479 (void) printf("ERROR\n");
1476 1480 }
1477 1481 if (mode == USAGE) {
1478 1482 quotemsg("%s", s);
1479 1483 } else { /* ERR, WARN */
1480 1484 quotemsg("%s: %s", myname, s);
1481 1485 }
1482 1486 } else {
1483 1487 #endif /* DYNAMIC_SU */
1484 1488 if (mode == USAGE) {
1485 1489 (void) fprintf(stderr, "%s\n", s);
1486 1490 } else { /* ERR, WARN */
1487 1491 (void) fprintf(stderr, "%s: %s\n", myname, s);
1488 1492 }
1489 1493 #ifdef DYNAMIC_SU
1490 1494 }
1491 1495 #endif /* DYNAMIC_SU */
1492 1496
1493 1497 free(s);
1494 1498 }
1495 1499
1496 1500 /*
1497 1501 * Return a pointer to the last path component of a.
1498 1502 */
1499 1503 static char *
1500 1504 tail(char *a)
1501 1505 {
1502 1506 char *p;
1503 1507
1504 1508 p = strrchr(a, '/');
1505 1509 if (p == NULL)
1506 1510 p = a;
1507 1511 else
1508 1512 p++; /* step over the '/' */
1509 1513
1510 1514 return (p);
1511 1515 }
1512 1516
1513 1517 static char *
1514 1518 alloc_vsprintf(const char *fmt, va_list ap1)
1515 1519 {
1516 1520 va_list ap2;
1517 1521 int n;
1518 1522 char buf[1];
1519 1523 char *s;
1520 1524
1521 1525 /*
1522 1526 * We need to scan the argument list twice. Save off a copy
1523 1527 * of the argument list pointer(s) for the second pass. Note that
1524 1528 * we are responsible for va_end'ing our copy.
1525 1529 */
1526 1530 va_copy(ap2, ap1);
1527 1531
1528 1532 /*
1529 1533 * vsnprintf into a dummy to get a length. One might
1530 1534 * think that passing 0 as the length to snprintf would
1531 1535 * do what we want, but it's defined not to.
1532 1536 *
1533 1537 * Perhaps we should sprintf into a 100 character buffer
1534 1538 * or something like that, to avoid two calls to snprintf
1535 1539 * in most cases.
1536 1540 */
1537 1541 n = vsnprintf(buf, sizeof (buf), fmt, ap2);
1538 1542 va_end(ap2);
1539 1543
1540 1544 /*
1541 1545 * Allocate an appropriately-sized buffer.
1542 1546 */
1543 1547 s = malloc(n + 1);
1544 1548 if (s == NULL) {
1545 1549 perror("malloc");
1546 1550 exit(4);
1547 1551 }
1548 1552
1549 1553 (void) vsnprintf(s, n+1, fmt, ap1);
1550 1554
1551 1555 return (s);
1552 1556 }
↓ open down ↓ |
1257 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX