login_pam.c revision 1.23 1 /* $NetBSD: login_pam.c,v 1.23 2013/10/18 20:47:06 christos Exp $ */
2
3 /*-
4 * Copyright (c) 1980, 1987, 1988, 1991, 1993, 1994
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __COPYRIGHT("@(#) Copyright (c) 1980, 1987, 1988, 1991, 1993, 1994\
35 The Regents of the University of California. All rights reserved.");
36 #endif /* not lint */
37
38 #ifndef lint
39 #if 0
40 static char sccsid[] = "@(#)login.c 8.4 (Berkeley) 4/2/94";
41 #endif
42 __RCSID("$NetBSD: login_pam.c,v 1.23 2013/10/18 20:47:06 christos Exp $");
43 #endif /* not lint */
44
45 /*
46 * login [ name ]
47 * login -h hostname (for telnetd, etc.)
48 * login -f name (for pre-authenticated login: datakit, xterm, etc.)
49 */
50
51 #include <sys/param.h>
52 #include <sys/stat.h>
53 #include <sys/time.h>
54 #include <sys/resource.h>
55 #include <sys/file.h>
56 #include <sys/wait.h>
57 #include <sys/socket.h>
58
59 #include <err.h>
60 #include <errno.h>
61 #include <grp.h>
62 #include <pwd.h>
63 #include <setjmp.h>
64 #include <signal.h>
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include <syslog.h>
69 #include <time.h>
70 #include <ttyent.h>
71 #include <tzfile.h>
72 #include <unistd.h>
73 #include <util.h>
74 #include <login_cap.h>
75 #include <vis.h>
76
77 #include <security/pam_appl.h>
78 #include <security/openpam.h>
79
80 #include "pathnames.h"
81 #include "common.h"
82
83 #if 0
84 static int rootterm(char *);
85 #endif
86 static void usage(void) __attribute__((__noreturn__));
87
88 static struct pam_conv pamc = { openpam_ttyconv, NULL };
89
90 #define TTYGRPNAME "tty" /* name of group to own ttys */
91
92 #define DEFAULT_BACKOFF 3
93 #define DEFAULT_RETRIES 10
94
95 static struct passwd pwres;
96 static char pwbuf[1024];
97 static struct group grs, *grp;
98 static char grbuf[1024];
99 extern char **environ;
100
101 int
102 main(int argc, char *argv[])
103 {
104 struct stat st;
105 int ask, ch, cnt, fflag, pflag, quietlog, rootlogin;
106 int auth_passed;
107 uid_t uid, saved_uid;
108 gid_t saved_gid, saved_gids[NGROUPS_MAX];
109 int nsaved_gids;
110 char *domain, *p, *ttyn;
111 char tbuf[MAXPATHLEN + 2], tname[sizeof(_PATH_TTY) + 10];
112 char localhost[MAXHOSTNAMELEN + 1];
113 int login_retries = DEFAULT_RETRIES,
114 login_backoff = DEFAULT_BACKOFF;
115 char *shell = NULL;
116 login_cap_t *lc = NULL;
117 pam_handle_t *pamh = NULL;
118 int pam_err;
119 sig_t oint, oabrt, oquit, oalrm;
120 const void *newuser;
121 int pam_silent = PAM_SILENT;
122 pid_t xpid, pid;
123 int status;
124 char *saved_term;
125 char **pamenv;
126
127 tbuf[0] = '\0';
128 nested = NULL;
129
130 oabrt = signal(SIGABRT, SIG_IGN);
131 oalrm = signal(SIGALRM, timedout);
132 oint = signal(SIGINT, SIG_IGN);
133 oquit = signal(SIGQUIT, SIG_IGN);
134
135 (void)alarm(timeout);
136 (void)setpriority(PRIO_PROCESS, 0, 0);
137
138 openlog("login", 0, LOG_AUTH);
139
140 /*
141 * -p is used by getty to tell login not to destroy the environment
142 * -f is used to skip a second login authentication
143 * -h is used by other servers to pass the name of the remote host to
144 * login so that it may be placed in utmp/utmpx and wtmp/wtmpx
145 * -a in addition to -h, a server my supply -a to pass the actual
146 * server address.
147 */
148 domain = NULL;
149 if (gethostname(localhost, sizeof(localhost)) < 0)
150 syslog(LOG_ERR, "couldn't get local hostname: %m");
151 else
152 domain = strchr(localhost, '.');
153 localhost[sizeof(localhost) - 1] = '\0';
154
155 fflag = pflag = 0;
156 have_ss = 0;
157 uid = getuid();
158 while ((ch = getopt(argc, argv, "a:fh:p")) != -1)
159 switch (ch) {
160 case 'a':
161 if (uid) {
162 errno = EPERM;
163 err(EXIT_FAILURE, "-a option");
164 }
165 decode_ss(optarg);
166 break;
167 case 'f':
168 fflag = 1;
169 break;
170 case 'h':
171 if (uid) {
172 errno = EPERM;
173 err(EXIT_FAILURE, "-h option");
174 }
175 if (domain && (p = strchr(optarg, '.')) != NULL &&
176 strcasecmp(p, domain) == 0)
177 *p = '\0';
178 hostname = optarg;
179 break;
180 case 'p':
181 pflag = 1;
182 break;
183 default:
184 case '?':
185 usage();
186 break;
187 }
188
189 setproctitle(NULL);
190 argc -= optind;
191 argv += optind;
192
193 if (*argv) {
194 username = trimloginname(*argv);
195 ask = 0;
196 } else
197 ask = 1;
198
199 #ifdef F_CLOSEM
200 (void)fcntl(3, F_CLOSEM, 0);
201 #else
202 for (cnt = getdtablesize(); cnt > 2; cnt--)
203 (void)close(cnt);
204 #endif
205
206 ttyn = ttyname(STDIN_FILENO);
207 if (ttyn == NULL || *ttyn == '\0') {
208 (void)snprintf(tname, sizeof(tname), "%s??", _PATH_TTY);
209 ttyn = tname;
210 }
211 if ((tty = strstr(ttyn, "/pts/")) != NULL)
212 ++tty;
213 else if ((tty = strrchr(ttyn, '/')) != NULL)
214 ++tty;
215 else
216 tty = ttyn;
217
218 if (issetugid()) {
219 nested = strdup(user_from_uid(getuid(), 0));
220 if (nested == NULL) {
221 syslog(LOG_ERR, "strdup: %m");
222 sleepexit(EXIT_FAILURE);
223 }
224 }
225
226 /* Get "login-retries" and "login-backoff" from default class */
227 if ((lc = login_getclass(NULL)) != NULL) {
228 login_retries = (int)login_getcapnum(lc, "login-retries",
229 DEFAULT_RETRIES, DEFAULT_RETRIES);
230 login_backoff = (int)login_getcapnum(lc, "login-backoff",
231 DEFAULT_BACKOFF, DEFAULT_BACKOFF);
232 login_close(lc);
233 lc = NULL;
234 }
235
236
237 for (cnt = 0;; ask = 1) {
238 if (ask) {
239 fflag = 0;
240 username = trimloginname(getloginname());
241 }
242 rootlogin = 0;
243 auth_passed = 0;
244
245 /*
246 * Note if trying multiple user names; log failures for
247 * previous user name, but don't bother logging one failure
248 * for nonexistent name (mistyped username).
249 */
250 if (failures && strcmp(tbuf, username)) {
251 if (failures > (pwd ? 0 : 1))
252 badlogin(tbuf);
253 failures = 0;
254 }
255
256 #define PAM_END(msg) do { \
257 syslog(LOG_ERR, "%s: %s", msg, pam_strerror(pamh, pam_err)); \
258 warnx("%s: %s", msg, pam_strerror(pamh, pam_err)); \
259 pam_end(pamh, pam_err); \
260 sleepexit(EXIT_FAILURE); \
261 } while (/*CONSTCOND*/0)
262
263 pam_err = pam_start("login", username, &pamc, &pamh);
264 if (pam_err != PAM_SUCCESS) {
265 if (pamh != NULL)
266 PAM_END("pam_start");
267 /* Things went really bad... */
268 syslog(LOG_ERR, "pam_start failed: %s",
269 pam_strerror(pamh, pam_err));
270 errx(EXIT_FAILURE, "pam_start failed");
271 }
272
273 #define PAM_SET_ITEM(item, var) do { \
274 pam_err = pam_set_item(pamh, (item), (var)); \
275 if (pam_err != PAM_SUCCESS) \
276 PAM_END("pam_set_item(" # item ")"); \
277 } while (/*CONSTCOND*/0)
278
279 /*
280 * Fill hostname tty, and nested user
281 */
282 PAM_SET_ITEM(PAM_RHOST, hostname);
283 PAM_SET_ITEM(PAM_TTY, tty);
284 if (nested)
285 PAM_SET_ITEM(PAM_NUSER, nested);
286 if (have_ss)
287 PAM_SET_ITEM(PAM_SOCKADDR, &ss);
288
289 /*
290 * Don't check for errors, because we don't want to give
291 * out any information.
292 */
293 pwd = NULL;
294 (void)getpwnam_r(username, &pwres, pwbuf, sizeof(pwbuf), &pwd);
295
296 /*
297 * Establish the class now, before we might goto
298 * within the next block. pwd can be NULL since it
299 * falls back to the "default" class if it is.
300 */
301 lc = login_getclass(pwd ? pwd->pw_class : NULL);
302
303 /*
304 * if we have a valid account name, and it doesn't have a
305 * password, or the -f option was specified and the caller
306 * is root or the caller isn't changing their uid, don't
307 * authenticate.
308 */
309 if (pwd) {
310 if (pwd->pw_uid == 0)
311 rootlogin = 1;
312
313 if (fflag && (uid == 0 || uid == pwd->pw_uid)) {
314 /* already authenticated */
315 auth_passed = 1;
316 goto skip_auth;
317 }
318 }
319
320 (void)setpriority(PRIO_PROCESS, 0, -4);
321
322 switch(pam_err = pam_authenticate(pamh, pam_silent)) {
323 case PAM_SUCCESS:
324 /*
325 * PAM can change the user, refresh
326 * username, pwd, and lc.
327 */
328 pam_err = pam_get_item(pamh, PAM_USER, &newuser);
329 if (pam_err != PAM_SUCCESS)
330 PAM_END("pam_get_item(PAM_USER)");
331
332 username = newuser;
333 /*
334 * Don't check for errors, because we don't want to give
335 * out any information.
336 */
337 pwd = NULL;
338 (void)getpwnam_r(username, &pwres, pwbuf, sizeof(pwbuf),
339 &pwd);
340 lc = login_getpwclass(pwd);
341 auth_passed = 1;
342
343 switch (pam_err = pam_acct_mgmt(pamh, pam_silent)) {
344 case PAM_SUCCESS:
345 break;
346
347 case PAM_NEW_AUTHTOK_REQD:
348 pam_err = pam_chauthtok(pamh,
349 pam_silent|PAM_CHANGE_EXPIRED_AUTHTOK);
350
351 if (pam_err != PAM_SUCCESS)
352 PAM_END("pam_chauthtok");
353 break;
354
355 case PAM_AUTH_ERR:
356 case PAM_USER_UNKNOWN:
357 case PAM_MAXTRIES:
358 auth_passed = 0;
359 break;
360
361 default:
362 PAM_END("pam_acct_mgmt");
363 break;
364 }
365 break;
366
367 case PAM_AUTH_ERR:
368 case PAM_USER_UNKNOWN:
369 case PAM_MAXTRIES:
370 auth_passed = 0;
371 break;
372
373 default:
374 PAM_END("pam_authenticate");
375 break;
376 }
377
378 (void)setpriority(PRIO_PROCESS, 0, 0);
379
380 skip_auth:
381 /*
382 * If the user exists and authentication passed,
383 * get out of the loop and login the user.
384 */
385 if (pwd && auth_passed)
386 break;
387
388 (void)printf("Login incorrect or refused on this terminal.\n");
389 failures++;
390 cnt++;
391 /*
392 * We allow login_retries tries, but after login_backoff
393 * we start backing off. These default to 10 and 3
394 * respectively.
395 */
396 if (cnt > login_backoff) {
397 if (cnt >= login_retries) {
398 badlogin(username);
399 pam_end(pamh, PAM_SUCCESS);
400 sleepexit(EXIT_FAILURE);
401 }
402 sleep((u_int)((cnt - login_backoff) * 5));
403 }
404 }
405
406 /* committed to login -- turn off timeout */
407 (void)alarm((u_int)0);
408
409 endpwent();
410
411 quietlog = login_getcapbool(lc, "hushlogin", 0);
412
413 /*
414 * Temporarily give up special privileges so we can change
415 * into NFS-mounted homes that are exported for non-root
416 * access and have mode 7x0
417 */
418 saved_uid = geteuid();
419 saved_gid = getegid();
420 nsaved_gids = getgroups(NGROUPS_MAX, saved_gids);
421
422 (void)setegid(pwd->pw_gid);
423 initgroups(username, pwd->pw_gid);
424 (void)seteuid(pwd->pw_uid);
425
426 if (chdir(pwd->pw_dir) != 0) {
427 if (login_getcapbool(lc, "requirehome", 0)) {
428 (void)printf("Home directory %s required\n",
429 pwd->pw_dir);
430 pam_end(pamh, PAM_SUCCESS);
431 exit(EXIT_FAILURE);
432 }
433
434 (void)printf("No home directory %s!\n", pwd->pw_dir);
435 if (chdir("/") == -1) {
436 pam_end(pamh, PAM_SUCCESS);
437 exit(EXIT_FAILURE);
438 }
439 pwd->pw_dir = __UNCONST("/");
440 (void)printf("Logging in with home = \"/\".\n");
441 }
442
443 if (!quietlog) {
444 quietlog = access(_PATH_HUSHLOGIN, F_OK) == 0;
445 pam_silent = quietlog ? PAM_SILENT : 0;
446 }
447
448 /* regain special privileges */
449 setegid(saved_gid);
450 setgroups(nsaved_gids, saved_gids);
451 seteuid(saved_uid);
452
453 (void)getgrnam_r(TTYGRPNAME, &grs, grbuf, sizeof(grbuf), &grp);
454 (void)chown(ttyn, pwd->pw_uid,
455 (grp != NULL) ? grp->gr_gid : pwd->pw_gid);
456
457 if (ttyaction(ttyn, "login", pwd->pw_name))
458 (void)printf("Warning: ttyaction failed.\n");
459
460 /* Nothing else left to fail -- really log in. */
461 update_db(quietlog, rootlogin, fflag);
462
463 if (nested == NULL && setusercontext(lc, pwd, pwd->pw_uid,
464 LOGIN_SETLOGIN) != 0) {
465 syslog(LOG_ERR, "setusercontext failed");
466 pam_end(pamh, PAM_SUCCESS);
467 exit(EXIT_FAILURE);
468 }
469
470 if (tty[sizeof("tty")-1] == 'd')
471 syslog(LOG_INFO, "DIALUP %s, %s", tty, pwd->pw_name);
472
473
474 /*
475 * Establish groups
476 */
477 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) != 0) {
478 syslog(LOG_ERR, "setusercontext failed");
479 pam_end(pamh, PAM_SUCCESS);
480 exit(EXIT_FAILURE);
481 }
482
483 pam_err = pam_setcred(pamh, pam_silent|PAM_ESTABLISH_CRED);
484 if (pam_err != PAM_SUCCESS)
485 PAM_END("pam_setcred");
486
487 pam_err = pam_open_session(pamh, pam_silent);
488 if (pam_err != PAM_SUCCESS)
489 PAM_END("pam_open_session");
490
491 /*
492 * Fork because we need to call pam_closesession as root.
493 * Make sure signals cannot kill the parent.
494 * This has been handled in the begining of main.
495 */
496
497 switch(pid = fork()) {
498 case -1:
499 pam_err = pam_close_session(pamh, 0);
500 if (pam_err != PAM_SUCCESS) {
501 syslog(LOG_ERR, "pam_close_session: %s",
502 pam_strerror(pamh, pam_err));
503 warnx("pam_close_session: %s",
504 pam_strerror(pamh, pam_err));
505 }
506 syslog(LOG_ERR, "fork failed: %m");
507 warn("fork failed");
508 pam_end(pamh, pam_err);
509 exit(EXIT_FAILURE);
510 break;
511
512 case 0: /* Child */
513 break;
514
515 default:
516 /*
517 * Parent: wait for the child to terminate
518 * and call pam_close_session.
519 */
520 if ((xpid = waitpid(pid, &status, 0)) != pid) {
521 pam_err = pam_close_session(pamh, 0);
522 if (pam_err != PAM_SUCCESS) {
523 syslog(LOG_ERR,
524 "pam_close_session: %s",
525 pam_strerror(pamh, pam_err));
526 warnx("pam_close_session: %s",
527 pam_strerror(pamh, pam_err));
528 }
529 pam_end(pamh, pam_err);
530 if (xpid != -1)
531 warnx("wrong PID: %d != %d", pid, xpid);
532 else
533 warn("wait for pid %d failed", pid);
534 exit(EXIT_FAILURE);
535 }
536
537 (void)signal(SIGABRT, oabrt);
538 (void)signal(SIGALRM, oalrm);
539 (void)signal(SIGINT, oint);
540 (void)signal(SIGQUIT, oquit);
541 if ((pam_err = pam_close_session(pamh, 0)) != PAM_SUCCESS) {
542 syslog(LOG_ERR, "pam_close_session: %s",
543 pam_strerror(pamh, pam_err));
544 warnx("pam_close_session: %s",
545 pam_strerror(pamh, pam_err));
546 }
547 pam_end(pamh, PAM_SUCCESS);
548 exit(EXIT_SUCCESS);
549 break;
550 }
551
552 /*
553 * The child: starting here, we don't have to care about
554 * handling PAM issues if we exit, the parent will do the
555 * job when we exit.
556 *
557 * Destroy environment unless user has requested its preservation.
558 * Try to preserve TERM anyway.
559 */
560 saved_term = getenv("TERM");
561 if (!pflag) {
562 environ = envinit;
563 if (saved_term)
564 setenv("TERM", saved_term, 0);
565 }
566
567 if (*pwd->pw_shell == '\0')
568 pwd->pw_shell = __UNCONST(_PATH_BSHELL);
569
570 shell = login_getcapstr(lc, "shell", pwd->pw_shell, pwd->pw_shell);
571 if (*shell == '\0')
572 shell = pwd->pw_shell;
573
574 if ((pwd->pw_shell = strdup(shell)) == NULL) {
575 syslog(LOG_ERR, "Cannot alloc mem");
576 exit(EXIT_FAILURE);
577 }
578
579 (void)setenv("HOME", pwd->pw_dir, 1);
580 (void)setenv("SHELL", pwd->pw_shell, 1);
581 if (term[0] == '\0') {
582 const char *tt = stypeof(tty);
583
584 if (tt == NULL)
585 tt = login_getcapstr(lc, "term", NULL, NULL);
586
587 /* unknown term -> "su" */
588 (void)strlcpy(term, tt != NULL ? tt : "su", sizeof(term));
589 }
590 (void)setenv("TERM", term, 0);
591 (void)setenv("LOGNAME", pwd->pw_name, 1);
592 (void)setenv("USER", pwd->pw_name, 1);
593
594 /*
595 * Add PAM environement
596 */
597 if ((pamenv = pam_getenvlist(pamh)) != NULL) {
598 char **envitem;
599
600 for (envitem = pamenv; *envitem; envitem++) {
601 putenv(*envitem);
602 free(*envitem);
603 }
604
605 free(pamenv);
606 }
607
608 /* This drops root privs */
609 if (setusercontext(lc, pwd, pwd->pw_uid,
610 (LOGIN_SETALL & ~LOGIN_SETLOGIN)) != 0) {
611 syslog(LOG_ERR, "setusercontext failed");
612 exit(EXIT_FAILURE);
613 }
614
615 if (!quietlog) {
616 const char *fname;
617
618 fname = login_getcapstr(lc, "copyright", NULL, NULL);
619 if (fname != NULL && access(fname, F_OK) == 0)
620 motd(fname);
621 else
622 (void)printf("%s", copyrightstr);
623
624 fname = login_getcapstr(lc, "welcome", NULL, NULL);
625 if (fname == NULL || access(fname, F_OK) != 0)
626 fname = _PATH_MOTDFILE;
627 motd(fname);
628
629 (void)snprintf(tbuf,
630 sizeof(tbuf), "%s/%s", _PATH_MAILDIR, pwd->pw_name);
631 if (stat(tbuf, &st) == 0 && st.st_size != 0)
632 (void)printf("You have %smail.\n",
633 (st.st_mtime > st.st_atime) ? "new " : "");
634 }
635
636 login_close(lc);
637
638
639 tbuf[0] = '-';
640 (void)strlcpy(tbuf + 1, (p = strrchr(pwd->pw_shell, '/')) ?
641 p + 1 : pwd->pw_shell, sizeof(tbuf) - 1);
642
643 (void)signal(SIGABRT, oabrt);
644 (void)signal(SIGALRM, oalrm);
645 (void)signal(SIGINT, oint);
646 (void)signal(SIGQUIT, oquit);
647 (void)signal(SIGTSTP, SIG_IGN);
648
649 execlp(pwd->pw_shell, tbuf, NULL);
650 err(EXIT_FAILURE, "%s", pwd->pw_shell);
651 }
652
653 static void
654 usage(void)
655 {
656 (void)fprintf(stderr,
657 "Usage: %s [-fp] [-a address] [-h hostname] [username]\n",
658 getprogname());
659 exit(EXIT_FAILURE);
660 }
661
662 #if 0
663 static int
664 rootterm(char *ttyn)
665 {
666 struct ttyent *t;
667
668 return ((t = getttynam(ttyn)) && t->ty_status & TTY_SECURE);
669 }
670 #endif
671