pretty printing
[heimdal.git] / appl / login / login.c
blob6b16f0b7157adcd0dca39d4248176aef27563e5b
1 /*
2 * Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
34 #include "login_locl.h"
35 #ifdef HAVE_CAPABILITY_H
36 #include <capability.h>
37 #endif
38 #ifdef HAVE_SYS_CAPABILITY_H
39 #include <sys/capability.h>
40 #endif
41 #ifdef HAVE_CRYPT_H
42 #include <crypt.h>
43 #endif
45 RCSID("$Id$");
47 static int login_timeout = 60;
49 static int
50 start_login_process(void)
52 char *prog, *argv0;
53 prog = login_conf_get_string("login_program");
54 if(prog == NULL)
55 return 0;
56 argv0 = strrchr(prog, '/');
58 if(argv0)
59 argv0++;
60 else
61 argv0 = prog;
63 return simple_execle(prog, argv0, NULL, env);
66 static int
67 start_logout_process(void)
69 char *prog, *argv0;
70 pid_t pid;
72 prog = login_conf_get_string("logout_program");
73 if(prog == NULL)
74 return 0;
75 argv0 = strrchr(prog, '/');
77 if(argv0)
78 argv0++;
79 else
80 argv0 = prog;
82 pid = fork();
83 if(pid == 0) {
84 /* avoid getting signals sent to the shell */
85 setpgid(0, getpid());
86 return 0;
88 if(pid == -1)
89 err(1, "fork");
90 /* wait for the real login process to exit */
91 #ifdef HAVE_SETPROCTITLE
92 setproctitle("waitpid %d", pid);
93 #endif
94 while(1) {
95 int status;
96 int ret;
97 ret = waitpid(pid, &status, 0);
98 if(ret > 0) {
99 if(WIFEXITED(status) || WIFSIGNALED(status)) {
100 execle(prog, argv0, NULL, env);
101 err(1, "exec %s", prog);
103 } else if(ret < 0)
104 err(1, "waitpid");
108 static void
109 exec_shell(const char *shell, int fallback)
111 char *sh;
112 const char *p;
114 extend_env(NULL);
115 if(start_login_process() < 0)
116 warn("login process");
117 start_logout_process();
119 p = strrchr(shell, '/');
120 if(p)
121 p++;
122 else
123 p = shell;
124 if (asprintf(&sh, "-%s", p) == -1)
125 errx(1, "Out of memory");
126 execle(shell, sh, NULL, env);
127 if(fallback){
128 warnx("Can't exec %s, trying %s",
129 shell, _PATH_BSHELL);
130 execle(_PATH_BSHELL, "-sh", NULL, env);
131 err(1, "%s", _PATH_BSHELL);
133 err(1, "%s", shell);
136 static enum { NONE = 0, AUTH_KRB5 = 2, AUTH_OTP = 3 } auth;
138 #ifdef OTP
139 static OtpContext otp_ctx;
141 static int
142 otp_verify(struct passwd *pwd, const char *password)
144 return (otp_verify_user (&otp_ctx, password));
146 #endif /* OTP */
149 static int pag_set = 0;
151 #ifdef KRB5
152 static krb5_context context;
153 static krb5_ccache id, id2;
155 static int
156 krb5_verify(struct passwd *pwd, const char *password)
158 krb5_error_code ret;
159 krb5_principal princ;
161 ret = krb5_parse_name(context, pwd->pw_name, &princ);
162 if(ret)
163 return 1;
164 ret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &id);
165 if(ret) {
166 krb5_free_principal(context, princ);
167 return 1;
169 ret = krb5_verify_user_lrealm(context,
170 princ,
172 password,
174 NULL);
175 krb5_free_principal(context, princ);
176 return ret;
179 static int
180 krb5_start_session (const struct passwd *pwd)
182 krb5_error_code ret;
183 char residual[64];
185 /* copy credentials to file cache */
186 snprintf(residual, sizeof(residual), "FILE:/tmp/krb5cc_%u",
187 (unsigned)pwd->pw_uid);
188 krb5_cc_resolve(context, residual, &id2);
189 ret = krb5_cc_copy_cache(context, id, id2);
190 if (ret == 0)
191 add_env("KRB5CCNAME", residual);
192 else {
193 krb5_cc_destroy (context, id2);
194 return ret;
196 krb5_cc_close(context, id2);
197 krb5_cc_destroy(context, id);
198 return 0;
201 static void
202 krb5_finish (void)
204 krb5_free_context(context);
207 static void
208 krb5_get_afs_tokens (const struct passwd *pwd)
210 char cell[64];
211 char *pw_dir;
212 krb5_error_code ret;
214 if (!k_hasafs ())
215 return;
217 ret = krb5_cc_default(context, &id2);
219 if (ret == 0) {
220 pw_dir = pwd->pw_dir;
222 if (!pag_set) {
223 k_setpag();
224 pag_set = 1;
227 if(k_afs_cell_of_file(pw_dir, cell, sizeof(cell)) == 0)
228 krb5_afslog_uid_home (context, id2,
229 cell, NULL, pwd->pw_uid, pwd->pw_dir);
230 krb5_afslog_uid_home (context, id2, NULL, NULL,
231 pwd->pw_uid, pwd->pw_dir);
232 krb5_cc_close (context, id2);
236 #endif /* KRB5 */
238 static int f_flag;
239 static int p_flag;
240 #if 0
241 static int r_flag;
242 #endif
243 static int version_flag;
244 static int help_flag;
245 static char *remote_host;
246 static char *auth_level = NULL;
248 struct getargs args[] = {
249 { NULL, 'a', arg_string, &auth_level, "authentication mode" },
250 #if 0
251 { NULL, 'd' },
252 #endif
253 { NULL, 'f', arg_flag, &f_flag, "pre-authenticated" },
254 { NULL, 'h', arg_string, &remote_host, "remote host", "hostname" },
255 { NULL, 'p', arg_flag, &p_flag, "don't purge environment" },
256 #if 0
257 { NULL, 'r', arg_flag, &r_flag, "rlogin protocol" },
258 #endif
259 { "version", 0, arg_flag, &version_flag },
260 { "help", 0, arg_flag,&help_flag, }
263 int nargs = sizeof(args) / sizeof(args[0]);
265 static void
266 update_utmp(const char *username, const char *hostname,
267 char *tty, char *ttyn)
270 * Update the utmp files, both BSD and SYSV style.
272 if (utmpx_login(tty, username, hostname) != 0 && !f_flag) {
273 printf("No utmpx entry. You must exec \"login\" from the "
274 "lowest level shell.\n");
275 exit(1);
277 utmp_login(ttyn, username, hostname);
280 static void
281 checknologin(void)
283 FILE *f;
284 char buf[1024];
286 f = fopen(_PATH_NOLOGIN, "r");
287 if(f == NULL)
288 return;
289 while(fgets(buf, sizeof(buf), f))
290 fputs(buf, stdout);
291 fclose(f);
292 exit(0);
295 /* print contents of a file */
296 static void
297 show_file(const char *file)
299 FILE *f;
300 char buf[BUFSIZ];
301 if((f = fopen(file, "r")) == NULL)
302 return;
303 while (fgets(buf, sizeof(buf), f))
304 fputs(buf, stdout);
305 fclose(f);
309 * Actually log in the user. `pwd' contains all the relevant
310 * information about the user. `ttyn' is the complete name of the tty
311 * and `tty' the short name.
314 static void
315 do_login(const struct passwd *pwd, char *tty, char *ttyn)
317 #ifdef HAVE_GETSPNAM
318 struct spwd *sp;
319 #endif
320 int rootlogin = (pwd->pw_uid == 0);
321 gid_t tty_gid;
322 struct group *gr;
323 const char *home_dir;
324 int i;
326 if(!rootlogin)
327 checknologin();
329 #ifdef HAVE_GETSPNAM
330 sp = getspnam(pwd->pw_name);
331 #endif
333 update_utmp(pwd->pw_name, remote_host ? remote_host : "",
334 tty, ttyn);
336 gr = getgrnam ("tty");
337 if (gr != NULL)
338 tty_gid = gr->gr_gid;
339 else
340 tty_gid = pwd->pw_gid;
342 if (chown (ttyn, pwd->pw_uid, tty_gid) < 0) {
343 warn("chown %s", ttyn);
344 if (rootlogin == 0)
345 exit (1);
348 if (chmod (ttyn, S_IRUSR | S_IWUSR | S_IWGRP) < 0) {
349 warn("chmod %s", ttyn);
350 if (rootlogin == 0)
351 exit (1);
354 #ifdef HAVE_SETLOGIN
355 if(setlogin(pwd->pw_name)){
356 warn("setlogin(%s)", pwd->pw_name);
357 if(rootlogin == 0)
358 exit(1);
360 #endif
361 if(rootlogin == 0) {
362 const char *file = login_conf_get_string("limits");
363 if(file == NULL)
364 file = _PATH_LIMITS_CONF;
366 read_limits_conf(file, pwd);
369 #ifdef HAVE_SETPCRED
370 if (setpcred (pwd->pw_name, NULL) == -1)
371 warn("setpcred(%s)", pwd->pw_name);
372 #endif /* HAVE_SETPCRED */
373 #ifdef HAVE_INITGROUPS
374 if(initgroups(pwd->pw_name, pwd->pw_gid)){
375 warn("initgroups(%s, %u)", pwd->pw_name, (unsigned)pwd->pw_gid);
376 if(rootlogin == 0)
377 exit(1);
379 #endif
380 if(do_osfc2_magic(pwd->pw_uid))
381 exit(1);
382 if(setgid(pwd->pw_gid)){
383 warn("setgid(%u)", (unsigned)pwd->pw_gid);
384 if(rootlogin == 0)
385 exit(1);
387 if(setuid(pwd->pw_uid) || (pwd->pw_uid != 0 && setuid(0) == 0)) {
388 warn("setuid(%u)", (unsigned)pwd->pw_uid);
389 if(rootlogin == 0)
390 exit(1);
393 /* make sure signals are set to default actions, apparently some
394 OS:es like to ignore SIGINT, which is not very convenient */
396 for (i = 1; i < NSIG; ++i)
397 signal(i, SIG_DFL);
399 /* all kinds of different magic */
401 #ifdef HAVE_GETSPNAM
402 check_shadow(pwd, sp);
403 #endif
405 #if defined(HAVE_GETUDBNAM) && defined(HAVE_SETLIM)
407 struct udb *udb;
408 long t;
409 const long maxcpu = 46116860184; /* some random constant */
410 udb = getudbnam(pwd->pw_name);
411 if(udb == UDB_NULL)
412 errx(1, "Failed to get UDB entry.");
413 t = udb->ue_pcpulim[UDBRC_INTER];
414 if(t == 0 || t > maxcpu)
415 t = CPUUNLIM;
416 else
417 t *= 100 * CLOCKS_PER_SEC;
419 if(limit(C_PROC, 0, L_CPU, t) < 0)
420 warn("limit C_PROC");
422 t = udb->ue_jcpulim[UDBRC_INTER];
423 if(t == 0 || t > maxcpu)
424 t = CPUUNLIM;
425 else
426 t *= 100 * CLOCKS_PER_SEC;
428 if(limit(C_JOBPROCS, 0, L_CPU, t) < 0)
429 warn("limit C_JOBPROCS");
431 nice(udb->ue_nice[UDBRC_INTER]);
433 #endif
434 #if defined(HAVE_SGI_GETCAPABILITYBYNAME) && defined(HAVE_CAP_SET_PROC)
435 /* XXX SGI capability hack IRIX 6.x (x >= 0?) has something
436 called capabilities, that allow you to give away
437 permissions (such as chown) to specific processes. From 6.5
438 this is default on, and the default capability set seems to
439 not always be the empty set. The problem is that the
440 runtime linker refuses to do just about anything if the
441 process has *any* capabilities set, so we have to remove
442 them here (unless otherwise instructed by /etc/capability).
443 In IRIX < 6.5, these functions was called sgi_cap_setproc,
444 etc, but we ignore this fact (it works anyway). */
446 struct user_cap *ucap = sgi_getcapabilitybyname(pwd->pw_name);
447 cap_t cap;
448 if(ucap == NULL)
449 cap = cap_from_text("all=");
450 else
451 cap = cap_from_text(ucap->ca_default);
452 if(cap == NULL)
453 err(1, "cap_from_text");
454 if(cap_set_proc(cap) < 0)
455 err(1, "cap_set_proc");
456 cap_free(cap);
457 free(ucap);
459 #endif
460 home_dir = pwd->pw_dir;
461 if (chdir(home_dir) < 0) {
462 fprintf(stderr, "No home directory \"%s\"!\n", pwd->pw_dir);
463 if (chdir("/"))
464 exit(0);
465 home_dir = "/";
466 fprintf(stderr, "Logging in with home = \"/\".\n");
468 #ifdef KRB5
469 if (auth == AUTH_KRB5) {
470 krb5_start_session (pwd);
473 krb5_get_afs_tokens (pwd);
475 krb5_finish ();
476 #endif /* KRB5 */
478 add_env("PATH", _PATH_DEFPATH);
481 const char *str = login_conf_get_string("environment");
482 char buf[MAXPATHLEN];
484 if(str == NULL) {
485 login_read_env(_PATH_ETC_ENVIRONMENT);
486 } else {
487 while(strsep_copy(&str, ",", buf, sizeof(buf)) != -1) {
488 if(buf[0] == '\0')
489 continue;
490 login_read_env(buf);
495 const char *str = login_conf_get_string("motd");
496 char buf[MAXPATHLEN];
498 if(str != NULL) {
499 while(strsep_copy(&str, ",", buf, sizeof(buf)) != -1) {
500 if(buf[0] == '\0')
501 continue;
502 show_file(buf);
504 } else {
505 str = login_conf_get_string("welcome");
506 if(str != NULL)
507 show_file(str);
510 add_env("HOME", home_dir);
511 add_env("USER", pwd->pw_name);
512 add_env("LOGNAME", pwd->pw_name);
513 add_env("SHELL", pwd->pw_shell);
514 exec_shell(pwd->pw_shell, rootlogin);
517 static int
518 check_password(struct passwd *pwd, const char *password)
520 if(pwd->pw_passwd == NULL)
521 return 1;
522 if(pwd->pw_passwd[0] == '\0'){
523 #ifdef ALLOW_NULL_PASSWORD
524 return password[0] != '\0';
525 #else
526 return 1;
527 #endif
529 if(strcmp(pwd->pw_passwd, crypt(password, pwd->pw_passwd)) == 0)
530 return 0;
531 #ifdef KRB5
532 if(krb5_verify(pwd, password) == 0) {
533 auth = AUTH_KRB5;
534 return 0;
536 #endif
537 #ifdef OTP
538 if (otp_verify (pwd, password) == 0) {
539 auth = AUTH_OTP;
540 return 0;
542 #endif
543 return 1;
546 static void
547 usage(int status)
549 arg_printusage(args, nargs, NULL, "[username]");
550 exit(status);
553 static RETSIGTYPE
554 sig_handler(int sig)
556 if (sig == SIGALRM)
557 fprintf(stderr, "Login timed out after %d seconds\n",
558 login_timeout);
559 else
560 fprintf(stderr, "Login received signal, exiting\n");
561 exit(0);
565 main(int argc, char **argv)
567 int max_tries = 5;
568 int try;
570 char username[32];
571 int optidx = 0;
573 int ask = 1;
574 struct sigaction sa;
576 setprogname(argv[0]);
578 #ifdef KRB5
580 krb5_error_code ret;
582 ret = krb5_init_context(&context);
583 if (ret)
584 errx (1, "krb5_init_context failed: %d", ret);
586 #endif
588 openlog("login", LOG_ODELAY | LOG_PID, LOG_AUTH);
590 if (getarg (args, sizeof(args) / sizeof(args[0]), argc, argv,
591 &optidx))
592 usage (1);
593 argc -= optidx;
594 argv += optidx;
596 if(help_flag)
597 usage(0);
598 if (version_flag) {
599 print_version (NULL);
600 return 0;
603 if (geteuid() != 0)
604 errx(1, "only root may use login, use su");
606 /* Default tty settings. */
607 stty_default();
609 if(p_flag)
610 copy_env();
611 else {
612 /* this set of variables is always preserved by BSD login */
613 if(getenv("TERM"))
614 add_env("TERM", getenv("TERM"));
615 if(getenv("TZ"))
616 add_env("TZ", getenv("TZ"));
619 if(*argv){
620 if(strchr(*argv, '=') == NULL && strcmp(*argv, "-") != 0){
621 strlcpy (username, *argv, sizeof(username));
622 ask = 0;
626 #if defined(DCE) && defined(AIX)
627 esetenv("AUTHSTATE", "DCE", 1);
628 #endif
630 /* XXX should we care about environment on the command line? */
632 memset(&sa, 0, sizeof(sa));
633 sa.sa_handler = sig_handler;
634 sigemptyset(&sa.sa_mask);
635 sa.sa_flags = 0;
636 sigaction(SIGALRM, &sa, NULL);
637 alarm(login_timeout);
639 for(try = 0; try < max_tries; try++){
640 struct passwd *pwd;
641 char password[128];
642 int ret;
643 char ttname[32];
644 char *tty, *ttyn;
645 char prompt[128];
646 #ifdef OTP
647 char otp_str[256];
648 #endif
650 if(ask){
651 f_flag = 0;
652 #if 0
653 r_flag = 0;
654 #endif
655 ret = read_string("login: ", username, sizeof(username), 1);
656 if(ret == -3)
657 exit(0);
658 if(ret == -2)
659 sig_handler(0); /* exit */
661 pwd = k_getpwnam(username);
662 #ifdef ALLOW_NULL_PASSWORD
663 if (pwd != NULL && (pwd->pw_passwd[0] == '\0')) {
664 strcpy(password,"");
666 else
667 #endif
670 #ifdef OTP
671 if(auth_level && strcmp(auth_level, "otp") == 0 &&
672 otp_challenge(&otp_ctx, username,
673 otp_str, sizeof(otp_str)) == 0)
674 snprintf (prompt, sizeof(prompt), "%s's %s Password: ",
675 username, otp_str);
676 else
677 #endif
678 strncpy(prompt, "Password: ", sizeof(prompt));
680 if (f_flag == 0) {
681 ret = read_string(prompt, password, sizeof(password), 0);
682 if (ret == -3) {
683 ask = 1;
684 continue;
686 if (ret == -2)
687 sig_handler(0);
691 if(pwd == NULL){
692 fprintf(stderr, "Login incorrect.\n");
693 ask = 1;
694 continue;
697 if(f_flag == 0 && check_password(pwd, password)){
698 fprintf(stderr, "Login incorrect.\n");
699 ask = 1;
700 continue;
702 ttyn = ttyname(STDIN_FILENO);
703 if(ttyn == NULL){
704 snprintf(ttname, sizeof(ttname), "%s??", _PATH_TTY);
705 ttyn = ttname;
707 if (strncmp (ttyn, _PATH_DEV, strlen(_PATH_DEV)) == 0)
708 tty = ttyn + strlen(_PATH_DEV);
709 else
710 tty = ttyn;
712 if (login_access (pwd, remote_host ? remote_host : tty) == 0) {
713 fprintf(stderr, "Permission denied\n");
714 if (remote_host)
715 syslog(LOG_NOTICE, "%s LOGIN REFUSED FROM %s",
716 pwd->pw_name, remote_host);
717 else
718 syslog(LOG_NOTICE, "%s LOGIN REFUSED ON %s",
719 pwd->pw_name, tty);
720 exit (1);
721 } else {
722 if (remote_host)
723 syslog(LOG_NOTICE, "%s LOGIN ACCEPTED FROM %s ppid=%d",
724 pwd->pw_name, remote_host, (int) getppid());
725 else
726 syslog(LOG_NOTICE, "%s LOGIN ACCEPTED ON %s ppid=%d",
727 pwd->pw_name, tty, (int) getppid());
729 alarm(0);
730 do_login(pwd, tty, ttyn);
732 exit(1);