1 /* $NetBSD: login.c,v 1.96 2008/07/21 14:19:23 lukem 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.96 2008/07/21 14:19:23 lukem 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)
250 (void)fcntl(3, F_CLOSEM
, 0);
252 for (cnt
= getdtablesize(); cnt
> 2; cnt
--)
256 ttyn
= ttyname(STDIN_FILENO
);
257 if (ttyn
== NULL
|| *ttyn
== '\0') {
258 (void)snprintf(tname
, sizeof(tname
), "%s??", _PATH_TTY
);
261 if ((tty
= strstr(ttyn
, "/pts/")) != NULL
)
263 else if ((tty
= strrchr(ttyn
, '/')) != NULL
)
269 nested
= strdup(user_from_uid(getuid(), 0));
270 if (nested
== NULL
) {
271 syslog(LOG_ERR
, "strdup: %m");
272 sleepexit(EXIT_FAILURE
);
277 /* Get "login-retries" and "login-backoff" from default class */
278 if ((lc
= login_getclass(NULL
)) != NULL
) {
279 login_retries
= (int)login_getcapnum(lc
, "login-retries",
280 DEFAULT_RETRIES
, DEFAULT_RETRIES
);
281 login_backoff
= (int)login_getcapnum(lc
, "login-backoff",
282 DEFAULT_BACKOFF
, DEFAULT_BACKOFF
);
289 kerror
= krb5_init_context(&kcontext
);
292 * If Kerberos is not configured, that is, we are
293 * not using Kerberos, do not log the error message.
294 * However, if Kerberos is configured, and the
295 * context init fails for some other reason, we need
296 * to issue a no tickets warning to the user when the
299 if (kerror
!= ENXIO
) { /* XXX NetBSD-local Heimdal hack */
301 "%s when initializing Kerberos context",
302 error_message(kerror
));
305 login_krb5_get_tickets
= 0;
307 #endif /* KERBEROS5 */
309 for (cnt
= 0;; ask
= 1) {
310 #if defined(KERBEROS5)
311 if (login_krb5_get_tickets
)
320 if ((instance
= strchr(username
, '/')) != NULL
)
325 if (strlen(username
) > MAXLOGNAME
)
326 username
[MAXLOGNAME
] = '\0';
329 * Note if trying multiple user names; log failures for
330 * previous user name, but don't bother logging one failure
331 * for nonexistent name (mistyped username).
333 if (failures
&& strcmp(tbuf
, username
)) {
334 if (failures
> (pwd
? 0 : 1))
338 (void)strlcpy(tbuf
, username
, sizeof(tbuf
));
340 pwd
= getpwnam(username
);
344 * Establish the class now, before we might goto
345 * within the next block. pwd can be NULL since it
346 * falls back to the "default" class if it is.
348 lc
= login_getclass(pwd
? pwd
->pw_class
: NULL
);
351 * if we have a valid account name, and it doesn't have a
352 * password, or the -f option was specified and the caller
353 * is root or the caller isn't changing their uid, don't
357 if (pwd
->pw_uid
== 0)
360 if (fflag
&& (uid
== 0 || uid
== pwd
->pw_uid
)) {
361 /* already authenticated */
363 if (login_krb5_get_tickets
&& Fflag
)
364 k5_read_creds(username
);
367 } else if (pwd
->pw_passwd
[0] == '\0') {
368 /* pretend password okay */
376 (void)setpriority(PRIO_PROCESS
, 0, -4);
379 if (skey_haskey(username
) == 0) {
380 static char skprompt
[80];
381 const char *skinfo
= skey_keyinfo(username
);
383 (void)snprintf(skprompt
, sizeof(skprompt
),
385 skinfo
? skinfo
: "error getting challenge");
389 pwprompt
= "Password:";
391 p
= getpass(pwprompt
);
398 if (login_krb5_get_tickets
&&
399 k5login(pwd
, instance
, localhost
, p
) == 0) {
404 #if defined(KERBEROS5)
409 if (skey_haskey(username
) == 0 &&
410 skey_passcheck(username
, p
) != -1) {
415 if (!sflag
&& *pwd
->pw_passwd
!= '\0' &&
416 !strcmp(crypt(p
, pwd
->pw_passwd
), pwd
->pw_passwd
)) {
424 memset(p
, 0, strlen(p
));
426 (void)setpriority(PRIO_PROCESS
, 0, 0);
430 * If trying to log in as root without Kerberos,
431 * but with insecure terminal, refuse the login attempt.
433 if (pwd
&& !rval
&& rootlogin
&& !rootterm(tty
)) {
434 (void)printf("Login incorrect or refused on this "
438 "LOGIN %s REFUSED FROM %s ON TTY %s",
439 pwd
->pw_name
, hostname
, tty
);
442 "LOGIN %s REFUSED ON TTY %s",
450 (void)printf("Login incorrect or refused on this "
455 * We allow login_retries tries, but after login_backoff
456 * we start backing off. These default to 10 and 3
459 if (cnt
> login_backoff
) {
460 if (cnt
>= login_retries
) {
462 sleepexit(EXIT_FAILURE
);
464 sleep((u_int
)((cnt
- login_backoff
) * 5));
468 /* committed to login -- turn off timeout */
469 (void)alarm((u_int
)0);
473 /* if user not super-user, check for disabled logins */
475 if (!login_getcapbool(lc
, "ignorenologin", rootlogin
))
476 checknologin(login_getcapstr(lc
, "nologin", NULL
, NULL
));
483 quietlog
= login_getcapbool(lc
, "hushlogin", 0);
487 /* Temporarily give up special privileges so we can change */
488 /* into NFS-mounted homes that are exported for non-root */
489 /* access and have mode 7x0 */
490 saved_uid
= geteuid();
491 saved_gid
= getegid();
492 nsaved_gids
= getgroups(NGROUPS_MAX
, saved_gids
);
494 (void)setegid(pwd
->pw_gid
);
495 initgroups(username
, pwd
->pw_gid
);
496 (void)seteuid(pwd
->pw_uid
);
498 if (chdir(pwd
->pw_dir
) < 0) {
500 if (login_getcapbool(lc
, "requirehome", 0)) {
501 (void)printf("Home directory %s required\n",
503 sleepexit(EXIT_FAILURE
);
506 (void)printf("No home directory %s!\n", pwd
->pw_dir
);
507 if (chdir("/") == -1)
510 (void)printf("Logging in with home = \"/\".\n");
514 quietlog
= access(_PATH_HUSHLOGIN
, F_OK
) == 0;
516 /* regain special privileges */
517 (void)seteuid(saved_uid
);
518 setgroups(nsaved_gids
, saved_gids
);
519 (void)setegid(saved_gid
);
522 pw_warntime
= login_getcaptime(lc
, "password-warn",
523 _PASSWORD_WARNDAYS
* SECSPERDAY
,
524 _PASSWORD_WARNDAYS
* SECSPERDAY
);
527 (void)gettimeofday(&now
, (struct timezone
*)NULL
);
528 if (pwd
->pw_expire
) {
529 if (now
.tv_sec
>= pwd
->pw_expire
) {
530 (void)printf("Sorry -- your account has expired.\n");
531 sleepexit(EXIT_FAILURE
);
532 } else if (pwd
->pw_expire
- now
.tv_sec
< pw_warntime
&&
534 (void)printf("Warning: your account expires on %s",
535 ctime(&pwd
->pw_expire
));
537 if (pwd
->pw_change
) {
538 if (pwd
->pw_change
== _PASSWORD_CHGNOW
)
540 else if (now
.tv_sec
>= pwd
->pw_change
) {
541 (void)printf("Sorry -- your password has expired.\n");
542 sleepexit(EXIT_FAILURE
);
543 } else if (pwd
->pw_change
- now
.tv_sec
< pw_warntime
&&
545 (void)printf("Warning: your password expires on %s",
546 ctime(&pwd
->pw_change
));
549 /* Nothing else left to fail -- really log in. */
550 update_db(quietlog
, rootlogin
, fflag
);
552 (void)chown(ttyn
, pwd
->pw_uid
,
553 (gr
= getgrnam(TTYGRPNAME
)) ? gr
->gr_gid
: pwd
->pw_gid
);
555 if (ttyaction(ttyn
, "login", pwd
->pw_name
))
556 (void)printf("Warning: ttyaction failed.\n");
558 #if defined(KERBEROS5)
559 /* Fork so that we can call kdestroy */
560 if (! login_krb5_retain_ccache
&& has_ccache
)
564 /* Destroy environment unless user has requested its preservation. */
569 if (nested
== NULL
&& setusercontext(lc
, pwd
, pwd
->pw_uid
,
570 LOGIN_SETLOGIN
) != 0) {
571 syslog(LOG_ERR
, "setusercontext failed");
574 if (setusercontext(lc
, pwd
, pwd
->pw_uid
,
575 (LOGIN_SETALL
& ~(LOGIN_SETPATH
|LOGIN_SETLOGIN
))) != 0) {
576 syslog(LOG_ERR
, "setusercontext failed");
580 (void)setgid(pwd
->pw_gid
);
582 initgroups(username
, pwd
->pw_gid
);
584 if (nested
== NULL
&& setlogin(pwd
->pw_name
) < 0)
585 syslog(LOG_ERR
, "setlogin() failure: %m");
587 /* Discard permissions last so can't get killed and drop core. */
591 (void)setuid(pwd
->pw_uid
);
594 if (*pwd
->pw_shell
== '\0')
595 pwd
->pw_shell
= _PATH_BSHELL
;
597 if ((shell
= login_getcapstr(lc
, "shell", NULL
, NULL
)) != NULL
) {
598 if ((shell
= strdup(shell
)) == NULL
) {
599 syslog(LOG_ERR
, "Cannot alloc mem");
600 sleepexit(EXIT_FAILURE
);
602 pwd
->pw_shell
= shell
;
606 (void)setenv("HOME", pwd
->pw_dir
, 1);
607 (void)setenv("SHELL", pwd
->pw_shell
, 1);
608 if (term
[0] == '\0') {
609 char *tt
= (char *)stypeof(tty
);
612 tt
= login_getcapstr(lc
, "term", NULL
, NULL
);
614 /* unknown term -> "su" */
615 (void)strlcpy(term
, tt
!= NULL
? tt
: "su", sizeof(term
));
617 (void)setenv("TERM", term
, 0);
618 (void)setenv("LOGNAME", pwd
->pw_name
, 1);
619 (void)setenv("USER", pwd
->pw_name
, 1);
622 setusercontext(lc
, pwd
, pwd
->pw_uid
, LOGIN_SETPATH
);
624 (void)setenv("PATH", _PATH_DEFPATH
, 0);
629 (void)setenv("KRB5CCNAME", krb5tkfile_env
, 1);
632 if (tty
[sizeof("tty")-1] == 'd')
633 syslog(LOG_INFO
, "DIALUP %s, %s", tty
, pwd
->pw_name
);
635 /* If fflag is on, assume caller/authenticator has logged root login. */
636 if (rootlogin
&& fflag
== 0) {
638 syslog(LOG_NOTICE
, "ROOT LOGIN (%s) ON %s FROM %s",
639 username
, tty
, hostname
);
641 syslog(LOG_NOTICE
, "ROOT LOGIN (%s) ON %s",
645 #if defined(KERBEROS5)
646 if (KERBEROS_CONFIGURED
&& !quietlog
&& notickets
== 1)
647 (void)printf("Warning: no Kerberos tickets issued.\n");
653 fname
= login_getcapstr(lc
, "copyright", NULL
, NULL
);
654 if (fname
!= NULL
&& access(fname
, F_OK
) == 0)
658 (void)printf("%s", copyrightstr
);
661 fname
= login_getcapstr(lc
, "welcome", NULL
, NULL
);
662 if (fname
== NULL
|| access(fname
, F_OK
) != 0)
664 fname
= _PATH_MOTDFILE
;
668 sizeof(tbuf
), "%s/%s", _PATH_MAILDIR
, pwd
->pw_name
);
669 if (stat(tbuf
, &st
) == 0 && st
.st_size
!= 0)
670 (void)printf("You have %smail.\n",
671 (st
.st_mtime
> st
.st_atime
) ? "new " : "");
678 (void)signal(SIGALRM
, SIG_DFL
);
679 (void)signal(SIGQUIT
, SIG_DFL
);
680 (void)signal(SIGINT
, SIG_DFL
);
681 (void)signal(SIGTSTP
, SIG_IGN
);
684 (void)strlcpy(tbuf
+ 1, (p
= strrchr(pwd
->pw_shell
, '/')) ?
685 p
+ 1 : pwd
->pw_shell
, sizeof(tbuf
) - 1);
687 /* Wait to change password until we're unprivileged */
691 "Warning: your password has expired. Please change it as soon as possible.\n");
696 "Your password has expired. Please choose a new one.\n");
700 sleepexit(EXIT_FAILURE
);
702 execl(_PATH_BINPASSWD
, "passwd", NULL
);
705 if (wait(&status
) == -1 ||
707 sleepexit(EXIT_FAILURE
);
713 if (login_krb5_get_tickets
)
716 execlp(pwd
->pw_shell
, tbuf
, NULL
);
717 err(EXIT_FAILURE
, "%s", pwd
->pw_shell
);
720 #if defined(KERBEROS5)
722 * This routine handles cleanup stuff, and the like.
723 * It exists only in the child process.
730 switch (child
= fork()) {
732 return; /* Child process */
734 err(EXIT_FAILURE
, "Can't fork");
741 * Setup stuff? This would be things we could do in parallel
744 if (chdir("/") == -1) /* Let's not keep the fs busy... */
745 err(EXIT_FAILURE
, "Can't chdir to `/'");
747 /* If we're the parent, watch the child until it dies */
748 while ((wchild
= wait(NULL
)) != child
)
750 err(EXIT_FAILURE
, "Can't wait");
753 /* Run kdestroy to destroy tickets */
754 if (login_krb5_get_tickets
)
763 checknologin(char *fname
)
768 if ((fd
= open(fname
? fname
: _PATH_NOLOGIN
, O_RDONLY
, 0)) >= 0) {
769 while ((nchars
= read(fd
, tbuf
, sizeof(tbuf
))) > 0)
770 (void)write(fileno(stdout
), tbuf
, nchars
);
771 sleepexit(EXIT_SUCCESS
);
778 (void)fprintf(stderr
,
779 "Usage: %s [-Ffps] [-a address] [-h hostname] [username]\n",