Drop main() prototype. Syncs with NetBSD-8
[minix.git] / usr.bin / login / login_pam.c
blobc1f9c76dff561c867251c72bdcb0a84dc1272780
1 /* $NetBSD: login_pam.c,v 1.24 2014/11/12 22:23:38 aymeric Exp $ */
3 /*-
4 * Copyright (c) 1980, 1987, 1988, 1991, 1993, 1994
5 * The Regents of the University of California. All rights reserved.
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.
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.
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 */
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.24 2014/11/12 22:23:38 aymeric Exp $");
43 #endif /* not lint */
46 * login [ name ]
47 * login -h hostname (for telnetd, etc.)
48 * login -f name (for pre-authenticated login: datakit, xterm, etc.)
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>
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>
77 #include <security/pam_appl.h>
78 #include <security/openpam.h>
80 #include "pathnames.h"
81 #include "common.h"
83 #if 0
84 static int rootterm(char *);
85 #endif
86 static void usage(void) __attribute__((__noreturn__));
88 static struct pam_conv pamc = { openpam_ttyconv, NULL };
90 #define TTYGRPNAME "tty" /* name of group to own ttys */
92 #define DEFAULT_BACKOFF 3
93 #define DEFAULT_RETRIES 10
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;
102 main(int argc, char *argv[])
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;
127 tbuf[0] = '\0';
128 nested = NULL;
130 oabrt = signal(SIGABRT, SIG_IGN);
131 oalrm = signal(SIGALRM, timedout);
132 oint = signal(SIGINT, SIG_IGN);
133 oquit = signal(SIGQUIT, SIG_IGN);
135 (void)alarm(timeout);
136 (void)setpriority(PRIO_PROCESS, 0, 0);
138 openlog("login", 0, LOG_AUTH);
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.
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';
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");
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");
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;
189 setproctitle(NULL);
190 argc -= optind;
191 argv += optind;
193 if (*argv) {
194 username = trimloginname(*argv);
195 ask = 0;
196 } else
197 ask = 1;
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
206 ttyn = ttyname(STDIN_FILENO);
207 if (ttyn == NULL || *ttyn == '\0') {
208 (void)snprintf(tname, sizeof(tname), "%s??", _PATH_TTY);
209 ttyn = tname;
211 if ((tty = strstr(ttyn, "/pts/")) != NULL)
212 ++tty;
213 else if ((tty = strrchr(ttyn, '/')) != NULL)
214 ++tty;
215 else
216 tty = ttyn;
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);
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;
237 for (cnt = 0;; ask = 1) {
238 if (ask) {
239 fflag = 0;
240 username = trimloginname(getloginname());
242 rootlogin = 0;
243 auth_passed = 0;
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).
250 if (failures && strcmp(tbuf, username)) {
251 if (failures > (pwd ? 0 : 1))
252 badlogin(tbuf);
253 failures = 0;
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)
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");
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)
280 * Fill hostname tty, and nested user
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);
290 * Don't check for errors, because we don't want to give
291 * out any information.
293 pwd = NULL;
294 (void)getpwnam_r(username, &pwres, pwbuf, sizeof(pwbuf), &pwd);
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.
301 lc = login_getclass(pwd ? pwd->pw_class : NULL);
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.
309 if (pwd) {
310 if (pwd->pw_uid == 0)
311 rootlogin = 1;
313 if (fflag && (uid == 0 || uid == pwd->pw_uid)) {
314 /* already authenticated */
315 auth_passed = 1;
316 goto skip_auth;
320 (void)setpriority(PRIO_PROCESS, 0, -4);
322 switch(pam_err = pam_authenticate(pamh, pam_silent)) {
323 case PAM_SUCCESS:
325 * PAM can change the user, refresh
326 * username, pwd, and lc.
328 pam_err = pam_get_item(pamh, PAM_USER, &newuser);
329 if (pam_err != PAM_SUCCESS)
330 PAM_END("pam_get_item(PAM_USER)");
332 username = newuser;
334 * Don't check for errors, because we don't want to give
335 * out any information.
337 pwd = NULL;
338 (void)getpwnam_r(username, &pwres, pwbuf, sizeof(pwbuf),
339 &pwd);
340 lc = login_getpwclass(pwd);
341 auth_passed = 1;
343 switch (pam_err = pam_acct_mgmt(pamh, pam_silent)) {
344 case PAM_SUCCESS:
345 break;
347 case PAM_NEW_AUTHTOK_REQD:
348 pam_err = pam_chauthtok(pamh,
349 pam_silent|PAM_CHANGE_EXPIRED_AUTHTOK);
351 if (pam_err != PAM_SUCCESS)
352 PAM_END("pam_chauthtok");
353 break;
355 case PAM_AUTH_ERR:
356 case PAM_USER_UNKNOWN:
357 case PAM_MAXTRIES:
358 auth_passed = 0;
359 break;
361 default:
362 PAM_END("pam_acct_mgmt");
363 break;
365 break;
367 case PAM_AUTH_ERR:
368 case PAM_USER_UNKNOWN:
369 case PAM_MAXTRIES:
370 auth_passed = 0;
371 break;
373 default:
374 PAM_END("pam_authenticate");
375 break;
378 (void)setpriority(PRIO_PROCESS, 0, 0);
380 skip_auth:
382 * If the user exists and authentication passed,
383 * get out of the loop and login the user.
385 if (pwd && auth_passed)
386 break;
388 (void)printf("Login incorrect or refused on this terminal.\n");
389 failures++;
390 cnt++;
392 * We allow login_retries tries, but after login_backoff
393 * we start backing off. These default to 10 and 3
394 * respectively.
396 if (cnt > login_backoff) {
397 if (cnt >= login_retries) {
398 badlogin(username);
399 pam_end(pamh, PAM_SUCCESS);
400 sleepexit(EXIT_FAILURE);
402 sleep((u_int)((cnt - login_backoff) * 5));
406 /* committed to login -- turn off timeout */
407 (void)alarm((u_int)0);
409 endpwent();
411 quietlog = login_getcapbool(lc, "hushlogin", 0);
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
418 saved_uid = geteuid();
419 saved_gid = getegid();
420 nsaved_gids = getgroups(NGROUPS_MAX, saved_gids);
422 (void)setegid(pwd->pw_gid);
423 initgroups(username, pwd->pw_gid);
424 (void)seteuid(pwd->pw_uid);
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);
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);
439 pwd->pw_dir = __UNCONST("/");
440 (void)printf("Logging in with home = \"/\".\n");
443 if (!quietlog) {
444 quietlog = access(_PATH_HUSHLOGIN, F_OK) == 0;
445 pam_silent = quietlog ? PAM_SILENT : 0;
448 /* regain special privileges */
449 setegid(saved_gid);
450 setgroups(nsaved_gids, saved_gids);
451 seteuid(saved_uid);
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);
457 if (ttyaction(ttyn, "login", pwd->pw_name))
458 (void)printf("Warning: ttyaction failed.\n");
460 /* Nothing else left to fail -- really log in. */
461 update_db(quietlog, rootlogin, fflag);
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);
471 * Establish groups
473 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) != 0) {
474 syslog(LOG_ERR, "setusercontext failed");
475 pam_end(pamh, PAM_SUCCESS);
476 exit(EXIT_FAILURE);
479 pam_err = pam_setcred(pamh, pam_silent|PAM_ESTABLISH_CRED);
480 if (pam_err != PAM_SUCCESS)
481 PAM_END("pam_setcred");
483 pam_err = pam_open_session(pamh, pam_silent);
484 if (pam_err != PAM_SUCCESS)
485 PAM_END("pam_open_session");
488 * Fork because we need to call pam_closesession as root.
489 * Make sure signals cannot kill the parent.
490 * This has been handled in the begining of main.
493 switch(pid = fork()) {
494 case -1:
495 pam_err = pam_close_session(pamh, 0);
496 if (pam_err != PAM_SUCCESS) {
497 syslog(LOG_ERR, "pam_close_session: %s",
498 pam_strerror(pamh, pam_err));
499 warnx("pam_close_session: %s",
500 pam_strerror(pamh, pam_err));
502 syslog(LOG_ERR, "fork failed: %m");
503 warn("fork failed");
504 pam_end(pamh, pam_err);
505 exit(EXIT_FAILURE);
506 break;
508 case 0: /* Child */
509 break;
511 default:
513 * Parent: wait for the child to terminate
514 * and call pam_close_session.
516 if ((xpid = waitpid(pid, &status, 0)) != pid) {
517 pam_err = pam_close_session(pamh, 0);
518 if (pam_err != PAM_SUCCESS) {
519 syslog(LOG_ERR,
520 "pam_close_session: %s",
521 pam_strerror(pamh, pam_err));
522 warnx("pam_close_session: %s",
523 pam_strerror(pamh, pam_err));
525 pam_end(pamh, pam_err);
526 if (xpid != -1)
527 warnx("wrong PID: %d != %d", pid, xpid);
528 else
529 warn("wait for pid %d failed", pid);
530 exit(EXIT_FAILURE);
533 (void)signal(SIGABRT, oabrt);
534 (void)signal(SIGALRM, oalrm);
535 (void)signal(SIGINT, oint);
536 (void)signal(SIGQUIT, oquit);
537 if ((pam_err = pam_close_session(pamh, 0)) != PAM_SUCCESS) {
538 syslog(LOG_ERR, "pam_close_session: %s",
539 pam_strerror(pamh, pam_err));
540 warnx("pam_close_session: %s",
541 pam_strerror(pamh, pam_err));
543 pam_end(pamh, PAM_SUCCESS);
544 exit(EXIT_SUCCESS);
545 break;
549 * The child: starting here, we don't have to care about
550 * handling PAM issues if we exit, the parent will do the
551 * job when we exit.
553 * Destroy environment unless user has requested its preservation.
554 * Try to preserve TERM anyway.
556 saved_term = getenv("TERM");
557 if (!pflag) {
558 environ = envinit;
559 if (saved_term)
560 setenv("TERM", saved_term, 0);
563 if (*pwd->pw_shell == '\0')
564 pwd->pw_shell = __UNCONST(_PATH_BSHELL);
566 shell = login_getcapstr(lc, "shell", pwd->pw_shell, pwd->pw_shell);
567 if (*shell == '\0')
568 shell = pwd->pw_shell;
570 if ((pwd->pw_shell = strdup(shell)) == NULL) {
571 syslog(LOG_ERR, "Cannot alloc mem");
572 exit(EXIT_FAILURE);
575 (void)setenv("HOME", pwd->pw_dir, 1);
576 (void)setenv("SHELL", pwd->pw_shell, 1);
577 if (term[0] == '\0') {
578 const char *tt = stypeof(tty);
580 if (tt == NULL)
581 tt = login_getcapstr(lc, "term", NULL, NULL);
583 /* unknown term -> "su" */
584 (void)strlcpy(term, tt != NULL ? tt : "su", sizeof(term));
586 (void)setenv("TERM", term, 0);
587 (void)setenv("LOGNAME", pwd->pw_name, 1);
588 (void)setenv("USER", pwd->pw_name, 1);
591 * Add PAM environement
593 if ((pamenv = pam_getenvlist(pamh)) != NULL) {
594 char **envitem;
596 for (envitem = pamenv; *envitem; envitem++) {
597 putenv(*envitem);
598 free(*envitem);
601 free(pamenv);
604 /* This drops root privs */
605 if (setusercontext(lc, pwd, pwd->pw_uid,
606 (LOGIN_SETALL & ~LOGIN_SETLOGIN)) != 0) {
607 syslog(LOG_ERR, "setusercontext failed");
608 exit(EXIT_FAILURE);
611 if (!quietlog) {
612 const char *fname;
614 fname = login_getcapstr(lc, "copyright", NULL, NULL);
615 if (fname != NULL && access(fname, F_OK) == 0)
616 motd(fname);
617 else
618 (void)printf("%s", copyrightstr);
620 fname = login_getcapstr(lc, "welcome", NULL, NULL);
621 if (fname == NULL || access(fname, F_OK) != 0)
622 fname = _PATH_MOTDFILE;
623 motd(fname);
625 (void)snprintf(tbuf,
626 sizeof(tbuf), "%s/%s", _PATH_MAILDIR, pwd->pw_name);
627 if (stat(tbuf, &st) == 0 && st.st_size != 0)
628 (void)printf("You have %smail.\n",
629 (st.st_mtime > st.st_atime) ? "new " : "");
632 login_close(lc);
635 tbuf[0] = '-';
636 (void)strlcpy(tbuf + 1, (p = strrchr(pwd->pw_shell, '/')) ?
637 p + 1 : pwd->pw_shell, sizeof(tbuf) - 1);
639 (void)signal(SIGABRT, oabrt);
640 (void)signal(SIGALRM, oalrm);
641 (void)signal(SIGINT, oint);
642 (void)signal(SIGQUIT, oquit);
643 (void)signal(SIGTSTP, SIG_IGN);
645 execlp(pwd->pw_shell, tbuf, NULL);
646 err(EXIT_FAILURE, "%s", pwd->pw_shell);
649 static void
650 usage(void)
652 (void)fprintf(stderr,
653 "Usage: %s [-fp] [-a address] [-h hostname] [username]\n",
654 getprogname());
655 exit(EXIT_FAILURE);
658 #if 0
659 static int
660 rootterm(char *ttyn)
662 struct ttyent *t;
664 return ((t = getttynam(ttyn)) && t->ty_status & TTY_SECURE);
666 #endif