1 /* $NetBSD: login.c,v 1.97 2009/12/29 19:26:13 christos Exp $ */
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
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
32 #include <sys/cdefs.h>
34 __COPYRIGHT("@(#) Copyright (c) 1980, 1987, 1988, 1991, 1993, 1994\
35 The Regents of the University of California. All rights reserved.");
40 static char sccsid
[] = "@(#)login.c 8.4 (Berkeley) 4/2/94";
42 __RCSID("$NetBSD: login.c,v 1.97 2009/12/29 19:26:13 christos Exp $");
47 * login -h hostname (for telnetd, etc.)
48 * login -f name (for pre-authenticated login: datakit, xterm, etc.)
51 #include <sys/param.h>
54 #include <sys/resource.h>
57 #include <sys/socket.h>
85 #include <krb5/krb5.h>
89 #include <login_cap.h>
93 #include "pathnames.h"
97 int login_krb5_forwardable_tgt
= 0;
98 static int login_krb5_get_tickets
= 1;
99 static int login_krb5_retain_ccache
= 0;
102 static void checknologin(char *);
104 int k5login(struct passwd
*, char *, char *, char *);
105 void k5destroy(void);
106 int k5_read_creds(char*);
107 int k5_write_creds(void);
109 #if defined(KERBEROS5)
110 static void dofork(void);
112 static void usage(void);
114 #define TTYGRPNAME "tty" /* name of group to own ttys */
116 #define DEFAULT_BACKOFF 3
117 #define DEFAULT_RETRIES 10
119 #if defined(KERBEROS5)
121 static int notickets
= 1;
122 static char *instance
;
123 extern krb5_context kcontext
;
124 extern int have_forward
;
125 extern char *krb5tkfile_env
;
126 extern int krb5_configured
;
129 #if defined(KERBEROS5)
130 #define KERBEROS_CONFIGURED krb5_configured
133 extern char **environ
;
136 main(int argc
, char *argv
[])
140 int ask
, ch
, cnt
, fflag
, hflag
, pflag
, sflag
, quietlog
, rootlogin
, rval
;
142 uid_t uid
, saved_uid
;
143 gid_t saved_gid
, saved_gids
[NGROUPS_MAX
];
145 char *domain
, *p
, *ttyn
, *pwprompt
;
146 char tbuf
[MAXPATHLEN
+ 2], tname
[sizeof(_PATH_TTY
) + 10];
147 char localhost
[MAXHOSTNAMELEN
+ 1];
148 int need_chpass
, require_chpass
;
149 int login_retries
= DEFAULT_RETRIES
,
150 login_backoff
= DEFAULT_BACKOFF
;
151 time_t pw_warntime
= _PASSWORD_WARNDAYS
* SECSPERDAY
;
153 krb5_error_code kerror
;
155 #if defined(KERBEROS5)
160 login_cap_t
*lc
= NULL
;
167 need_chpass
= require_chpass
= 0;
169 (void)signal(SIGALRM
, timedout
);
170 (void)alarm(timeout
);
171 (void)signal(SIGQUIT
, SIG_IGN
);
172 (void)signal(SIGINT
, SIG_IGN
);
173 (void)setpriority(PRIO_PROCESS
, 0, 0);
175 openlog("login", 0, LOG_AUTH
);
178 * -p is used by getty to tell login not to destroy the environment
179 * -f is used to skip a second login authentication
180 * -h is used by other servers to pass the name of the remote host to
181 * login so that it may be placed in utmp/utmpx and wtmp/wtmpx
182 * -a in addition to -h, a server may supply -a to pass the actual
184 * -s is used to force use of S/Key or equivalent.
187 if (gethostname(localhost
, sizeof(localhost
)) < 0)
188 syslog(LOG_ERR
, "couldn't get local hostname: %m");
190 domain
= strchr(localhost
, '.');
191 localhost
[sizeof(localhost
) - 1] = '\0';
193 Fflag
= fflag
= hflag
= pflag
= sflag
= 0;
199 while ((ch
= getopt(argc
, argv
, "a:Ffh:ps")) != -1)
203 errx(EXIT_FAILURE
, "-a option: %s", strerror(EPERM
));
206 (void)sockaddr_snprintf(optarg
,
207 sizeof(struct sockaddr_storage
), "%a", (void *)&ss
);
218 errx(EXIT_FAILURE
, "-h option: %s", strerror(EPERM
));
221 if (domain
&& (p
= strchr(optarg
, '.')) != NULL
&&
222 strcasecmp(p
, domain
) == 0)
252 (void)fcntl(3, F_CLOSEM
, 0);
254 for (cnt
= getdtablesize(); cnt
> 2; cnt
--)
258 ttyn
= ttyname(STDIN_FILENO
);
259 if (ttyn
== NULL
|| *ttyn
== '\0') {
260 (void)snprintf(tname
, sizeof(tname
), "%s??", _PATH_TTY
);
263 if ((tty
= strstr(ttyn
, "/pts/")) != NULL
)
265 else if ((tty
= strrchr(ttyn
, '/')) != NULL
)
271 nested
= strdup(user_from_uid(getuid(), 0));
272 if (nested
== NULL
) {
273 syslog(LOG_ERR
, "strdup: %m");
274 sleepexit(EXIT_FAILURE
);
279 /* Get "login-retries" and "login-backoff" from default class */
280 if ((lc
= login_getclass(NULL
)) != NULL
) {
281 login_retries
= (int)login_getcapnum(lc
, "login-retries",
282 DEFAULT_RETRIES
, DEFAULT_RETRIES
);
283 login_backoff
= (int)login_getcapnum(lc
, "login-backoff",
284 DEFAULT_BACKOFF
, DEFAULT_BACKOFF
);
291 kerror
= krb5_init_context(&kcontext
);
294 * If Kerberos is not configured, that is, we are
295 * not using Kerberos, do not log the error message.
296 * However, if Kerberos is configured, and the
297 * context init fails for some other reason, we need
298 * to issue a no tickets warning to the user when the
301 if (kerror
!= ENXIO
) { /* XXX NetBSD-local Heimdal hack */
303 "%s when initializing Kerberos context",
304 error_message(kerror
));
307 login_krb5_get_tickets
= 0;
309 #endif /* KERBEROS5 */
311 for (cnt
= 0;; ask
= 1) {
312 #if defined(KERBEROS5)
313 if (login_krb5_get_tickets
)
322 if ((instance
= strchr(username
, '/')) != NULL
)
327 if (strlen(username
) > MAXLOGNAME
)
328 username
[MAXLOGNAME
] = '\0';
331 * Note if trying multiple user names; log failures for
332 * previous user name, but don't bother logging one failure
333 * for nonexistent name (mistyped username).
335 if (failures
&& strcmp(tbuf
, username
)) {
336 if (failures
> (pwd
? 0 : 1))
340 (void)strlcpy(tbuf
, username
, sizeof(tbuf
));
342 pwd
= getpwnam(username
);
346 * Establish the class now, before we might goto
347 * within the next block. pwd can be NULL since it
348 * falls back to the "default" class if it is.
350 lc
= login_getclass(pwd
? pwd
->pw_class
: NULL
);
353 * if we have a valid account name, and it doesn't have a
354 * password, or the -f option was specified and the caller
355 * is root or the caller isn't changing their uid, don't
359 if (pwd
->pw_uid
== 0)
362 if (fflag
&& (uid
== 0 || uid
== pwd
->pw_uid
)) {
363 /* already authenticated */
365 if (login_krb5_get_tickets
&& Fflag
)
366 k5_read_creds(username
);
369 } else if (pwd
->pw_passwd
[0] == '\0') {
370 /* pretend password okay */
378 (void)setpriority(PRIO_PROCESS
, 0, -4);
381 if (skey_haskey(username
) == 0) {
382 static char skprompt
[80];
383 const char *skinfo
= skey_keyinfo(username
);
385 (void)snprintf(skprompt
, sizeof(skprompt
),
387 skinfo
? skinfo
: "error getting challenge");
391 pwprompt
= "Password:";
393 p
= getpass(pwprompt
);
400 if (login_krb5_get_tickets
&&
401 k5login(pwd
, instance
, localhost
, p
) == 0) {
406 #if defined(KERBEROS5)
411 if (skey_haskey(username
) == 0 &&
412 skey_passcheck(username
, p
) != -1) {
417 if (!sflag
&& *pwd
->pw_passwd
!= '\0' &&
418 !strcmp(crypt(p
, pwd
->pw_passwd
), pwd
->pw_passwd
)) {
426 memset(p
, 0, strlen(p
));
428 (void)setpriority(PRIO_PROCESS
, 0, 0);
432 * If trying to log in as root without Kerberos,
433 * but with insecure terminal, refuse the login attempt.
435 if (pwd
&& !rval
&& rootlogin
&& !rootterm(tty
)) {
436 (void)printf("Login incorrect or refused on this "
440 "LOGIN %s REFUSED FROM %s ON TTY %s",
441 pwd
->pw_name
, hostname
, tty
);
444 "LOGIN %s REFUSED ON TTY %s",
452 (void)printf("Login incorrect or refused on this "
457 * We allow login_retries tries, but after login_backoff
458 * we start backing off. These default to 10 and 3
461 if (cnt
> login_backoff
) {
462 if (cnt
>= login_retries
) {
464 sleepexit(EXIT_FAILURE
);
466 sleep((u_int
)((cnt
- login_backoff
) * 5));
470 /* committed to login -- turn off timeout */
471 (void)alarm((u_int
)0);
475 /* if user not super-user, check for disabled logins */
477 if (!login_getcapbool(lc
, "ignorenologin", rootlogin
))
478 checknologin(login_getcapstr(lc
, "nologin", NULL
, NULL
));
485 quietlog
= login_getcapbool(lc
, "hushlogin", 0);
489 /* Temporarily give up special privileges so we can change */
490 /* into NFS-mounted homes that are exported for non-root */
491 /* access and have mode 7x0 */
492 saved_uid
= geteuid();
493 saved_gid
= getegid();
494 nsaved_gids
= getgroups(NGROUPS_MAX
, saved_gids
);
496 (void)setegid(pwd
->pw_gid
);
497 initgroups(username
, pwd
->pw_gid
);
498 (void)seteuid(pwd
->pw_uid
);
500 if (chdir(pwd
->pw_dir
) < 0) {
502 if (login_getcapbool(lc
, "requirehome", 0)) {
503 (void)printf("Home directory %s required\n",
505 sleepexit(EXIT_FAILURE
);
508 (void)printf("No home directory %s!\n", pwd
->pw_dir
);
509 if (chdir("/") == -1)
512 (void)printf("Logging in with home = \"/\".\n");
516 quietlog
= access(_PATH_HUSHLOGIN
, F_OK
) == 0;
518 /* regain special privileges */
519 (void)seteuid(saved_uid
);
520 setgroups(nsaved_gids
, saved_gids
);
521 (void)setegid(saved_gid
);
524 pw_warntime
= login_getcaptime(lc
, "password-warn",
525 _PASSWORD_WARNDAYS
* SECSPERDAY
,
526 _PASSWORD_WARNDAYS
* SECSPERDAY
);
529 (void)gettimeofday(&now
, (struct timezone
*)NULL
);
530 if (pwd
->pw_expire
) {
531 if (now
.tv_sec
>= pwd
->pw_expire
) {
532 (void)printf("Sorry -- your account has expired.\n");
533 sleepexit(EXIT_FAILURE
);
534 } else if (pwd
->pw_expire
- now
.tv_sec
< pw_warntime
&&
536 (void)printf("Warning: your account expires on %s",
537 ctime(&pwd
->pw_expire
));
539 if (pwd
->pw_change
) {
540 if (pwd
->pw_change
== _PASSWORD_CHGNOW
)
542 else if (now
.tv_sec
>= pwd
->pw_change
) {
543 (void)printf("Sorry -- your password has expired.\n");
544 sleepexit(EXIT_FAILURE
);
545 } else if (pwd
->pw_change
- now
.tv_sec
< pw_warntime
&&
547 (void)printf("Warning: your password expires on %s",
548 ctime(&pwd
->pw_change
));
551 /* Nothing else left to fail -- really log in. */
552 update_db(quietlog
, rootlogin
, fflag
);
554 (void)chown(ttyn
, pwd
->pw_uid
,
555 (gr
= getgrnam(TTYGRPNAME
)) ? gr
->gr_gid
: pwd
->pw_gid
);
557 if (ttyaction(ttyn
, "login", pwd
->pw_name
))
558 (void)printf("Warning: ttyaction failed.\n");
560 #if defined(KERBEROS5)
561 /* Fork so that we can call kdestroy */
562 if (! login_krb5_retain_ccache
&& has_ccache
)
566 /* Destroy environment unless user has requested its preservation. */
571 if (nested
== NULL
&& setusercontext(lc
, pwd
, pwd
->pw_uid
,
572 LOGIN_SETLOGIN
) != 0) {
573 syslog(LOG_ERR
, "setusercontext failed");
576 if (setusercontext(lc
, pwd
, pwd
->pw_uid
,
577 (LOGIN_SETALL
& ~(LOGIN_SETPATH
|LOGIN_SETLOGIN
))) != 0) {
578 syslog(LOG_ERR
, "setusercontext failed");
582 (void)setgid(pwd
->pw_gid
);
584 initgroups(username
, pwd
->pw_gid
);
587 if (nested
== NULL
&& setlogin(pwd
->pw_name
) < 0)
588 syslog(LOG_ERR
, "setlogin() failure: %m");
591 /* Discard permissions last so can't get killed and drop core. */
595 (void)setuid(pwd
->pw_uid
);
598 if (*pwd
->pw_shell
== '\0')
599 pwd
->pw_shell
= _PATH_BSHELL
;
601 if ((shell
= login_getcapstr(lc
, "shell", NULL
, NULL
)) != NULL
) {
602 if ((shell
= strdup(shell
)) == NULL
) {
603 syslog(LOG_ERR
, "Cannot alloc mem");
604 sleepexit(EXIT_FAILURE
);
606 pwd
->pw_shell
= shell
;
610 (void)setenv("HOME", pwd
->pw_dir
, 1);
611 (void)setenv("SHELL", pwd
->pw_shell
, 1);
612 if (term
[0] == '\0') {
613 char *tt
= (char *)stypeof(tty
);
616 tt
= login_getcapstr(lc
, "term", NULL
, NULL
);
618 /* unknown term -> "su" */
619 (void)strlcpy(term
, tt
!= NULL
? tt
: "su", sizeof(term
));
621 (void)setenv("TERM", term
, 0);
622 (void)setenv("LOGNAME", pwd
->pw_name
, 1);
623 (void)setenv("USER", pwd
->pw_name
, 1);
626 setusercontext(lc
, pwd
, pwd
->pw_uid
, LOGIN_SETPATH
);
628 (void)setenv("PATH", _PATH_DEFPATH
, 0);
633 (void)setenv("KRB5CCNAME", krb5tkfile_env
, 1);
636 if (tty
[sizeof("tty")-1] == 'd')
637 syslog(LOG_INFO
, "DIALUP %s, %s", tty
, pwd
->pw_name
);
639 /* If fflag is on, assume caller/authenticator has logged root login. */
640 if (rootlogin
&& fflag
== 0) {
642 syslog(LOG_NOTICE
, "ROOT LOGIN (%s) ON %s FROM %s",
643 username
, tty
, hostname
);
645 syslog(LOG_NOTICE
, "ROOT LOGIN (%s) ON %s",
649 #if defined(KERBEROS5)
650 if (KERBEROS_CONFIGURED
&& !quietlog
&& notickets
== 1)
651 (void)printf("Warning: no Kerberos tickets issued.\n");
657 fname
= login_getcapstr(lc
, "copyright", NULL
, NULL
);
658 if (fname
!= NULL
&& access(fname
, F_OK
) == 0)
662 (void)printf("%s", copyrightstr
);
665 fname
= login_getcapstr(lc
, "welcome", NULL
, NULL
);
666 if (fname
== NULL
|| access(fname
, F_OK
) != 0)
668 fname
= _PATH_MOTDFILE
;
672 sizeof(tbuf
), "%s/%s", _PATH_MAILDIR
, pwd
->pw_name
);
673 if (stat(tbuf
, &st
) == 0 && st
.st_size
!= 0)
674 (void)printf("You have %smail.\n",
675 (st
.st_mtime
> st
.st_atime
) ? "new " : "");
682 (void)signal(SIGALRM
, SIG_DFL
);
683 (void)signal(SIGQUIT
, SIG_DFL
);
684 (void)signal(SIGINT
, SIG_DFL
);
685 (void)signal(SIGTSTP
, SIG_IGN
);
688 (void)strlcpy(tbuf
+ 1, (p
= strrchr(pwd
->pw_shell
, '/')) ?
689 p
+ 1 : pwd
->pw_shell
, sizeof(tbuf
) - 1);
691 /* Wait to change password until we're unprivileged */
695 "Warning: your password has expired. Please change it as soon as possible.\n");
700 "Your password has expired. Please choose a new one.\n");
704 sleepexit(EXIT_FAILURE
);
706 execl(_PATH_BINPASSWD
, "passwd", NULL
);
709 if (wait(&status
) == -1 ||
711 sleepexit(EXIT_FAILURE
);
717 if (login_krb5_get_tickets
)
720 execlp(pwd
->pw_shell
, tbuf
, NULL
);
721 err(EXIT_FAILURE
, "%s", pwd
->pw_shell
);
724 #if defined(KERBEROS5)
726 * This routine handles cleanup stuff, and the like.
727 * It exists only in the child process.
734 switch (child
= fork()) {
736 return; /* Child process */
738 err(EXIT_FAILURE
, "Can't fork");
745 * Setup stuff? This would be things we could do in parallel
748 if (chdir("/") == -1) /* Let's not keep the fs busy... */
749 err(EXIT_FAILURE
, "Can't chdir to `/'");
751 /* If we're the parent, watch the child until it dies */
752 while ((wchild
= wait(NULL
)) != child
)
754 err(EXIT_FAILURE
, "Can't wait");
757 /* Run kdestroy to destroy tickets */
758 if (login_krb5_get_tickets
)
767 checknologin(char *fname
)
772 if ((fd
= open(fname
? fname
: _PATH_NOLOGIN
, O_RDONLY
, 0)) >= 0) {
773 while ((nchars
= read(fd
, tbuf
, sizeof(tbuf
))) > 0)
774 (void)write(fileno(stdout
), tbuf
, nchars
);
775 sleepexit(EXIT_SUCCESS
);
782 (void)fprintf(stderr
,
783 "Usage: %s [-Ffps] [-a address] [-h hostname] [username]\n",