]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/login.c
Merge branch 'help-description' of https://github.com/rudimeier/util-linux
[thirdparty/util-linux.git] / login-utils / login.c
1 /*
2 * login(1)
3 *
4 * This program is derived from 4.3 BSD software and is subject to the
5 * copyright notice below.
6 *
7 * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
8 * Rewritten to PAM-only version.
9 *
10 * Michael Glad (glad@daimi.dk)
11 * Computer Science Department, Aarhus University, Denmark
12 * 1990-07-04
13 *
14 * Copyright (c) 1980, 1987, 1988 The Regents of the University of California.
15 * All rights reserved.
16 *
17 * Redistribution and use in source and binary forms are permitted
18 * provided that the above copyright notice and this paragraph are
19 * duplicated in all such forms and that any documentation,
20 * advertising materials, and other materials related to such
21 * distribution and use acknowledge that the software was developed
22 * by the University of California, Berkeley. The name of the
23 * University may not be used to endorse or promote products derived
24 * from this software without specific prior written permission.
25 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
26 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
27 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
28 */
29 #include <sys/param.h>
30 #include <stdio.h>
31 #include <ctype.h>
32 #include <unistd.h>
33 #include <getopt.h>
34 #include <memory.h>
35 #include <time.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include <sys/resource.h>
39 #include <sys/file.h>
40 #include <termios.h>
41 #include <string.h>
42 #include <sys/ioctl.h>
43 #include <sys/wait.h>
44 #include <signal.h>
45 #include <errno.h>
46 #include <grp.h>
47 #include <pwd.h>
48 #include <utmpx.h>
49 #ifdef HAVE_LASTLOG_H
50 # include <lastlog.h>
51 #endif
52 #include <stdlib.h>
53 #include <sys/syslog.h>
54 #ifdef HAVE_LINUX_MAJOR_H
55 # include <linux/major.h>
56 #endif
57 #include <netdb.h>
58 #include <security/pam_appl.h>
59 #ifdef HAVE_SECURITY_PAM_MISC_H
60 # include <security/pam_misc.h>
61 #elif defined(HAVE_SECURITY_OPENPAM_H)
62 # include <security/openpam.h>
63 #endif
64 #include <sys/sendfile.h>
65
66 #ifdef HAVE_LIBAUDIT
67 # include <libaudit.h>
68 #endif
69
70 #include "c.h"
71 #include "setproctitle.h"
72 #include "pathnames.h"
73 #include "strutils.h"
74 #include "nls.h"
75 #include "env.h"
76 #include "xalloc.h"
77 #include "all-io.h"
78 #include "fileutils.h"
79 #include "ttyutils.h"
80
81 #include "logindefs.h"
82
83 #define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
84
85 #define LOGIN_MAX_TRIES 3
86 #define LOGIN_EXIT_TIMEOUT 5
87 #define LOGIN_TIMEOUT 60
88
89 #ifdef USE_TTY_GROUP
90 # define TTY_MODE 0620
91 #else
92 # define TTY_MODE 0600
93 #endif
94
95 #define TTYGRPNAME "tty" /* name of group to own ttys */
96 #define VCS_PATH_MAX 64
97
98 /*
99 * Login control struct
100 */
101 struct login_context {
102 const char *tty_path; /* ttyname() return value */
103 const char *tty_name; /* tty_path without /dev prefix */
104 const char *tty_number; /* end of the tty_path */
105 mode_t tty_mode; /* chmod() mode */
106
107 char *username; /* from command line or PAM */
108
109 struct passwd *pwd; /* user info */
110
111 pam_handle_t *pamh; /* PAM handler */
112 struct pam_conv conv; /* PAM conversation */
113
114 #ifdef LOGIN_CHOWN_VCS
115 char vcsn[VCS_PATH_MAX]; /* virtual console name */
116 char vcsan[VCS_PATH_MAX];
117 #endif
118
119 char *thishost; /* this machine */
120 char *thisdomain; /* this machine's domain */
121 char *hostname; /* remote machine */
122 char hostaddress[16]; /* remote address */
123
124 pid_t pid;
125 int quiet; /* 1 if hush file exists */
126
127 unsigned int remote:1, /* login -h */
128 nohost:1, /* login -H */
129 noauth:1, /* login -f */
130 keep_env:1; /* login -p */
131 };
132
133 /*
134 * This bounds the time given to login. Not a define, so it can
135 * be patched on machines where it's too small.
136 */
137 static unsigned int timeout = LOGIN_TIMEOUT;
138 static int child_pid = 0;
139 static volatile int got_sig = 0;
140
141 #ifdef LOGIN_CHOWN_VCS
142 /* true if the filedescriptor fd is a console tty, very Linux specific */
143 static int is_consoletty(int fd)
144 {
145 struct stat stb;
146
147 if ((fstat(fd, &stb) >= 0)
148 && (major(stb.st_rdev) == TTY_MAJOR)
149 && (minor(stb.st_rdev) < 64)) {
150 return 1;
151 }
152 return 0;
153 }
154 #endif
155
156
157 /*
158 * Robert Ambrose writes:
159 * A couple of my users have a problem with login processes hanging around
160 * soaking up pts's. What they seem to hung up on is trying to write out the
161 * message 'Login timed out after %d seconds' when the connection has already
162 * been dropped.
163 * What I did was add a second timeout while trying to write the message, so
164 * the process just exits if the second timeout expires.
165 */
166 static void __attribute__ ((__noreturn__))
167 timedout2(int sig __attribute__ ((__unused__)))
168 {
169 struct termios ti;
170
171 /* reset echo */
172 tcgetattr(0, &ti);
173 ti.c_lflag |= ECHO;
174 tcsetattr(0, TCSANOW, &ti);
175 exit(EXIT_SUCCESS); /* %% */
176 }
177
178 static void timedout(int sig __attribute__ ((__unused__)))
179 {
180 signal(SIGALRM, timedout2);
181 alarm(10);
182 /* TRANSLATORS: The standard value for %u is 60. */
183 warnx(_("timed out after %u seconds"), timeout);
184 signal(SIGALRM, SIG_IGN);
185 alarm(0);
186 timedout2(0);
187 }
188
189 /*
190 * This handler allows to inform a shell about signals to login. If you have
191 * (root) permissions, you can kill all login children by one signal to the
192 * login process.
193 *
194 * Also, a parent who is session leader is able (before setsid() in the child)
195 * to inform the child when the controlling tty goes away (e.g. modem hangup).
196 */
197 static void sig_handler(int signal)
198 {
199 if (child_pid)
200 kill(-child_pid, signal);
201 else
202 got_sig = 1;
203 if (signal == SIGTERM)
204 kill(-child_pid, SIGHUP); /* because the shell often ignores SIGTERM */
205 }
206
207 /*
208 * Let us delay all exit() calls when the user is not authenticated
209 * or the session not fully initialized (loginpam_session()).
210 */
211 static void __attribute__ ((__noreturn__)) sleepexit(int eval)
212 {
213 sleep((unsigned int)getlogindefs_num("FAIL_DELAY", LOGIN_EXIT_TIMEOUT));
214 exit(eval);
215 }
216
217 static const char *get_thishost(struct login_context *cxt, const char **domain)
218 {
219 if (!cxt->thishost) {
220 cxt->thishost = xgethostname();
221 if (!cxt->thishost) {
222 if (domain)
223 *domain = NULL;
224 return NULL;
225 }
226 cxt->thisdomain = strchr(cxt->thishost, '.');
227 if (cxt->thisdomain)
228 *cxt->thisdomain++ = '\0';
229 }
230
231 if (domain)
232 *domain = cxt->thisdomain;
233 return cxt->thishost;
234 }
235
236 /*
237 * Output the /etc/motd file.
238 *
239 * It determines the name of a login announcement file and outputs it to the
240 * user's terminal at login time. The MOTD_FILE configuration option is a
241 * colon-delimited list of filenames. An empty MOTD_FILE option disables
242 * message-of-the-day printing completely.
243 */
244 static void motd(void)
245 {
246 char *motdlist, *motdfile;
247 const char *mb;
248
249 mb = getlogindefs_str("MOTD_FILE", _PATH_MOTDFILE);
250 if (!mb || !*mb)
251 return;
252
253 motdlist = xstrdup(mb);
254
255 for (motdfile = strtok(motdlist, ":"); motdfile;
256 motdfile = strtok(NULL, ":")) {
257
258 struct stat st;
259 int fd;
260
261 fd = open(motdfile, O_RDONLY, 0);
262 if (fd < 0)
263 continue;
264 if (!fstat(fd, &st) && st.st_size)
265 sendfile(fileno(stdout), fd, NULL, st.st_size);
266 close(fd);
267 }
268
269 free(motdlist);
270 }
271
272 /*
273 * Nice and simple code provided by Linus Torvalds 16-Feb-93.
274 * Non-blocking stuff by Maciej W. Rozycki, macro@ds2.pg.gda.pl, 1999.
275 *
276 * He writes: "Login performs open() on a tty in a blocking mode.
277 * In some cases it may make login wait in open() for carrier infinitely,
278 * for example if the line is a simplistic case of a three-wire serial
279 * connection. I believe login should open the line in non-blocking mode,
280 * leaving the decision to make a connection to getty (where it actually
281 * belongs)."
282 */
283 static void open_tty(const char *tty)
284 {
285 int i, fd, flags;
286
287 fd = open(tty, O_RDWR | O_NONBLOCK);
288 if (fd == -1) {
289 syslog(LOG_ERR, _("FATAL: can't reopen tty: %m"));
290 sleepexit(EXIT_FAILURE);
291 }
292
293 if (!isatty(fd)) {
294 close(fd);
295 syslog(LOG_ERR, _("FATAL: %s is not a terminal"), tty);
296 sleepexit(EXIT_FAILURE);
297 }
298
299 flags = fcntl(fd, F_GETFL);
300 flags &= ~O_NONBLOCK;
301 fcntl(fd, F_SETFL, flags);
302
303 for (i = 0; i < fd; i++)
304 close(i);
305 for (i = 0; i < 3; i++)
306 if (fd != i)
307 dup2(fd, i);
308 if (fd >= 3)
309 close(fd);
310 }
311
312 #define chown_err(_what, _uid, _gid) \
313 syslog(LOG_ERR, _("chown (%s, %lu, %lu) failed: %m"), \
314 (_what), (unsigned long) (_uid), (unsigned long) (_gid))
315
316 #define chmod_err(_what, _mode) \
317 syslog(LOG_ERR, _("chmod (%s, %u) failed: %m"), (_what), (_mode))
318
319 static void chown_tty(struct login_context *cxt)
320 {
321 const char *grname;
322 uid_t uid = cxt->pwd->pw_uid;
323 gid_t gid = cxt->pwd->pw_gid;
324
325 grname = getlogindefs_str("TTYGROUP", TTYGRPNAME);
326 if (grname && *grname) {
327 struct group *gr = getgrnam(grname);
328 if (gr) /* group by name */
329 gid = gr->gr_gid;
330 else /* group by ID */
331 gid = (gid_t) getlogindefs_num("TTYGROUP", gid);
332 }
333 if (fchown(0, uid, gid)) /* tty */
334 chown_err(cxt->tty_name, uid, gid);
335 if (fchmod(0, cxt->tty_mode))
336 chmod_err(cxt->tty_name, cxt->tty_mode);
337
338 #ifdef LOGIN_CHOWN_VCS
339 if (is_consoletty(0)) {
340 if (chown(cxt->vcsn, uid, gid)) /* vcs */
341 chown_err(cxt->vcsn, uid, gid);
342 if (chmod(cxt->vcsn, cxt->tty_mode))
343 chmod_err(cxt->vcsn, cxt->tty_mode);
344
345 if (chown(cxt->vcsan, uid, gid)) /* vcsa */
346 chown_err(cxt->vcsan, uid, gid);
347 if (chmod(cxt->vcsan, cxt->tty_mode))
348 chmod_err(cxt->vcsan, cxt->tty_mode);
349 }
350 #endif
351 }
352
353 /*
354 * Reads the current terminal path and initializes cxt->tty_* variables.
355 */
356 static void init_tty(struct login_context *cxt)
357 {
358 struct stat st;
359 struct termios tt, ttt;
360
361 cxt->tty_mode = (mode_t) getlogindefs_num("TTYPERM", TTY_MODE);
362
363 get_terminal_name(&cxt->tty_path, &cxt->tty_name, &cxt->tty_number);
364
365 /*
366 * In case login is suid it was possible to use a hardlink as stdin
367 * and exploit races for a local root exploit. (Wojciech Purczynski).
368 *
369 * More precisely, the problem is ttyn := ttyname(0); ...; chown(ttyn);
370 * here ttyname() might return "/tmp/x", a hardlink to a pseudotty.
371 * All of this is a problem only when login is suid, which it isn't.
372 */
373 if (!cxt->tty_path || !*cxt->tty_path ||
374 lstat(cxt->tty_path, &st) != 0 || !S_ISCHR(st.st_mode) ||
375 (st.st_nlink > 1 && strncmp(cxt->tty_path, "/dev/", 5)) ||
376 access(cxt->tty_path, R_OK | W_OK) != 0) {
377
378 syslog(LOG_ERR, _("FATAL: bad tty"));
379 sleepexit(EXIT_FAILURE);
380 }
381
382 #ifdef LOGIN_CHOWN_VCS
383 if (cxt->tty_number) {
384 /* find names of Virtual Console devices, for later mode change */
385 snprintf(cxt->vcsn, sizeof(cxt->vcsn), "/dev/vcs%s", cxt->tty_number);
386 snprintf(cxt->vcsan, sizeof(cxt->vcsan), "/dev/vcsa%s", cxt->tty_number);
387 }
388 #endif
389
390 tcgetattr(0, &tt);
391 ttt = tt;
392 ttt.c_cflag &= ~HUPCL;
393
394 if ((fchown(0, 0, 0) || fchmod(0, cxt->tty_mode)) && errno != EROFS) {
395
396 syslog(LOG_ERR, _("FATAL: %s: change permissions failed: %m"),
397 cxt->tty_path);
398 sleepexit(EXIT_FAILURE);
399 }
400
401 /* Kill processes left on this tty */
402 tcsetattr(0, TCSANOW, &ttt);
403
404 /*
405 * Let's close file descriptors before vhangup
406 * https://lkml.org/lkml/2012/6/5/145
407 */
408 close(STDIN_FILENO);
409 close(STDOUT_FILENO);
410 close(STDERR_FILENO);
411
412 signal(SIGHUP, SIG_IGN); /* so vhangup() won't kill us */
413 vhangup();
414 signal(SIGHUP, SIG_DFL);
415
416 /* open stdin,stdout,stderr to the tty */
417 open_tty(cxt->tty_path);
418
419 /* restore tty modes */
420 tcsetattr(0, TCSAFLUSH, &tt);
421 }
422
423
424 /*
425 * Logs failed login attempts in _PATH_BTMP, if it exists.
426 * Must be called only with username the name of an actual user.
427 * The most common login failure is to give password instead of username.
428 */
429 static void log_btmp(struct login_context *cxt)
430 {
431 struct utmpx ut;
432 struct timeval tv;
433
434 memset(&ut, 0, sizeof(ut));
435
436 strncpy(ut.ut_user,
437 cxt->username ? cxt->username : "(unknown)",
438 sizeof(ut.ut_user));
439
440 if (cxt->tty_number)
441 strncpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
442 if (cxt->tty_name)
443 xstrncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
444
445 gettimeofday(&tv, NULL);
446 ut.ut_tv.tv_sec = tv.tv_sec;
447 ut.ut_tv.tv_usec = tv.tv_usec;
448
449 ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */
450 ut.ut_pid = cxt->pid;
451
452 if (cxt->hostname) {
453 xstrncpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
454 if (*cxt->hostaddress)
455 memcpy(&ut.ut_addr_v6, cxt->hostaddress,
456 sizeof(ut.ut_addr_v6));
457 }
458
459 updwtmpx(_PATH_BTMP, &ut);
460 }
461
462
463 #ifdef HAVE_LIBAUDIT
464 static void log_audit(struct login_context *cxt, int status)
465 {
466 int audit_fd;
467 struct passwd *pwd = cxt->pwd;
468
469 audit_fd = audit_open();
470 if (audit_fd == -1)
471 return;
472 if (!pwd && cxt->username)
473 pwd = getpwnam(cxt->username);
474
475 audit_log_acct_message(audit_fd,
476 AUDIT_USER_LOGIN,
477 NULL,
478 "login",
479 cxt->username ? cxt->username : "(unknown)",
480 pwd ? pwd->pw_uid : (unsigned int) -1,
481 cxt->hostname,
482 NULL,
483 cxt->tty_name,
484 status);
485
486 close(audit_fd);
487 }
488 #else /* !HAVE_LIBAUDIT */
489 # define log_audit(cxt, status)
490 #endif /* HAVE_LIBAUDIT */
491
492 static void log_lastlog(struct login_context *cxt)
493 {
494 struct sigaction sa, oldsa_xfsz;
495 struct lastlog ll;
496 time_t t;
497 int fd;
498
499 if (!cxt->pwd)
500 return;
501
502 /* lastlog is huge on systems with large UIDs, ignore SIGXFSZ */
503 memset(&sa, 0, sizeof(sa));
504 sa.sa_handler = SIG_IGN;
505 sigaction(SIGXFSZ, &sa, &oldsa_xfsz);
506
507 fd = open(_PATH_LASTLOG, O_RDWR, 0);
508 if (fd < 0)
509 goto done;
510
511 if (lseek(fd, (off_t) cxt->pwd->pw_uid * sizeof(ll), SEEK_SET) == -1)
512 goto done;
513
514 /*
515 * Print last log message.
516 */
517 if (!cxt->quiet) {
518 if (read(fd, (char *)&ll, sizeof(ll)) == sizeof(ll) &&
519 ll.ll_time != 0) {
520 time_t ll_time = (time_t) ll.ll_time;
521
522 printf(_("Last login: %.*s "), 24 - 5, ctime(&ll_time));
523 if (*ll.ll_host != '\0')
524 printf(_("from %.*s\n"),
525 (int)sizeof(ll.ll_host), ll.ll_host);
526 else
527 printf(_("on %.*s\n"),
528 (int)sizeof(ll.ll_line), ll.ll_line);
529 }
530 if (lseek(fd, (off_t) cxt->pwd->pw_uid * sizeof(ll), SEEK_SET) == -1)
531 goto done;
532 }
533
534 memset((char *)&ll, 0, sizeof(ll));
535
536 time(&t);
537 ll.ll_time = t; /* ll_time is always 32bit */
538
539 if (cxt->tty_name)
540 xstrncpy(ll.ll_line, cxt->tty_name, sizeof(ll.ll_line));
541 if (cxt->hostname)
542 xstrncpy(ll.ll_host, cxt->hostname, sizeof(ll.ll_host));
543
544 if (write_all(fd, (char *)&ll, sizeof(ll)))
545 warn(_("write lastlog failed"));
546 done:
547 if (fd >= 0)
548 close(fd);
549
550 sigaction(SIGXFSZ, &oldsa_xfsz, NULL); /* restore original setting */
551 }
552
553 /*
554 * Update wtmp and utmp logs.
555 */
556 static void log_utmp(struct login_context *cxt)
557 {
558 struct utmpx ut;
559 struct utmpx *utp;
560 struct timeval tv;
561
562 utmpxname(_PATH_UTMP);
563 setutxent();
564
565 /* Find pid in utmp.
566 *
567 * login sometimes overwrites the runlevel entry in /var/run/utmp,
568 * confusing sysvinit. I added a test for the entry type, and the
569 * problem was gone. (In a runlevel entry, st_pid is not really a pid
570 * but some number calculated from the previous and current runlevel.)
571 * -- Michael Riepe <michael@stud.uni-hannover.de>
572 */
573 while ((utp = getutxent()))
574 if (utp->ut_pid == cxt->pid
575 && utp->ut_type >= INIT_PROCESS
576 && utp->ut_type <= DEAD_PROCESS)
577 break;
578
579 /* If we can't find a pre-existing entry by pid, try by line.
580 * BSD network daemons may rely on this. */
581 if (utp == NULL && cxt->tty_name) {
582 setutxent();
583 ut.ut_type = LOGIN_PROCESS;
584 strncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
585 utp = getutxline(&ut);
586 }
587
588 /* If we can't find a pre-existing entry by pid and line, try it by id.
589 * Very stupid telnetd daemons don't set up utmp at all. (kzak) */
590 if (utp == NULL && cxt->tty_number) {
591 setutxent();
592 ut.ut_type = DEAD_PROCESS;
593 strncpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
594 utp = getutxid(&ut);
595 }
596
597 if (utp)
598 memcpy(&ut, utp, sizeof(ut));
599 else
600 /* some gettys/telnetds don't initialize utmp... */
601 memset(&ut, 0, sizeof(ut));
602
603 if (cxt->tty_number && ut.ut_id[0] == 0)
604 strncpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
605 if (cxt->username)
606 strncpy(ut.ut_user, cxt->username, sizeof(ut.ut_user));
607 if (cxt->tty_name)
608 xstrncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
609
610 gettimeofday(&tv, NULL);
611 ut.ut_tv.tv_sec = tv.tv_sec;
612 ut.ut_tv.tv_usec = tv.tv_usec;
613 ut.ut_type = USER_PROCESS;
614 ut.ut_pid = cxt->pid;
615 if (cxt->hostname) {
616 xstrncpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
617 if (*cxt->hostaddress)
618 memcpy(&ut.ut_addr_v6, cxt->hostaddress,
619 sizeof(ut.ut_addr_v6));
620 }
621
622 pututxline(&ut);
623 endutxent();
624
625 updwtmpx(_PATH_WTMP, &ut);
626 }
627
628 static void log_syslog(struct login_context *cxt)
629 {
630 struct passwd *pwd = cxt->pwd;
631
632 if (!cxt->tty_name)
633 return;
634
635 if (!strncmp(cxt->tty_name, "ttyS", 4))
636 syslog(LOG_INFO, _("DIALUP AT %s BY %s"),
637 cxt->tty_name, pwd->pw_name);
638
639 if (!pwd->pw_uid) {
640 if (cxt->hostname)
641 syslog(LOG_NOTICE, _("ROOT LOGIN ON %s FROM %s"),
642 cxt->tty_name, cxt->hostname);
643 else
644 syslog(LOG_NOTICE, _("ROOT LOGIN ON %s"), cxt->tty_name);
645 } else {
646 if (cxt->hostname)
647 syslog(LOG_INFO, _("LOGIN ON %s BY %s FROM %s"),
648 cxt->tty_name, pwd->pw_name, cxt->hostname);
649 else
650 syslog(LOG_INFO, _("LOGIN ON %s BY %s"), cxt->tty_name,
651 pwd->pw_name);
652 }
653 }
654
655 static struct passwd *get_passwd_entry(const char *username,
656 char **pwdbuf,
657 struct passwd *pwd)
658 {
659 struct passwd *res = NULL;
660 int x;
661
662 if (!pwdbuf || !username)
663 return NULL;
664
665 *pwdbuf = xrealloc(*pwdbuf, UL_GETPW_BUFSIZ);
666
667 x = getpwnam_r(username, pwd, *pwdbuf, UL_GETPW_BUFSIZ, &res);
668 if (!res) {
669 errno = x;
670 return NULL;
671 }
672 return res;
673 }
674
675 /* encapsulate stupid "void **" pam_get_item() API */
676 static int loginpam_get_username(pam_handle_t *pamh, char **name)
677 {
678 const void *item = (void *)*name;
679 int rc;
680 rc = pam_get_item(pamh, PAM_USER, &item);
681 *name = (char *)item;
682 return rc;
683 }
684
685 static void loginpam_err(pam_handle_t *pamh, int retcode)
686 {
687 const char *msg = pam_strerror(pamh, retcode);
688
689 if (msg) {
690 fprintf(stderr, "\n%s\n", msg);
691 syslog(LOG_ERR, "%s", msg);
692 }
693 pam_end(pamh, retcode);
694 sleepexit(EXIT_FAILURE);
695 }
696
697 /*
698 * Composes "<host> login: " string; or returns "login: " if -H is given.
699 */
700 static const char *loginpam_get_prompt(struct login_context *cxt)
701 {
702 const char *host;
703 char *prompt, *dflt_prompt = _("login: ");
704 size_t sz;
705
706 if (cxt->nohost || !(host = get_thishost(cxt, NULL)))
707 return dflt_prompt;
708
709 sz = strlen(host) + 1 + strlen(dflt_prompt) + 1;
710
711 prompt = xmalloc(sz);
712 snprintf(prompt, sz, "%s %s", host, dflt_prompt);
713
714 return prompt;
715 }
716
717 static pam_handle_t *init_loginpam(struct login_context *cxt)
718 {
719 pam_handle_t *pamh = NULL;
720 int rc;
721
722 /*
723 * username is initialized to NULL and if specified on the command line
724 * it is set. Therefore, we are safe not setting it to anything.
725 */
726 rc = pam_start(cxt->remote ? "remote" : "login",
727 cxt->username, &cxt->conv, &pamh);
728 if (rc != PAM_SUCCESS) {
729 warnx(_("PAM failure, aborting: %s"), pam_strerror(pamh, rc));
730 syslog(LOG_ERR, _("Couldn't initialize PAM: %s"),
731 pam_strerror(pamh, rc));
732 sleepexit(EXIT_FAILURE);
733 }
734
735 /* hostname & tty are either set to NULL or their correct values,
736 * depending on how much we know. */
737 rc = pam_set_item(pamh, PAM_RHOST, cxt->hostname);
738 if (is_pam_failure(rc))
739 loginpam_err(pamh, rc);
740
741 rc = pam_set_item(pamh, PAM_TTY, cxt->tty_name);
742 if (is_pam_failure(rc))
743 loginpam_err(pamh, rc);
744
745 /*
746 * Andrew.Taylor@cal.montage.ca: Provide a user prompt to PAM so that
747 * the "login: " prompt gets localized. Unfortunately, PAM doesn't have
748 * an interface to specify the "Password: " string (yet).
749 */
750 rc = pam_set_item(pamh, PAM_USER_PROMPT, loginpam_get_prompt(cxt));
751 if (is_pam_failure(rc))
752 loginpam_err(pamh, rc);
753
754 /* We don't need the original username. We have to follow PAM. */
755 free(cxt->username);
756 cxt->username = NULL;
757 cxt->pamh = pamh;
758
759 return pamh;
760 }
761
762 static void loginpam_auth(struct login_context *cxt)
763 {
764 int rc, show_unknown;
765 unsigned int retries, failcount = 0;
766 const char *hostname = cxt->hostname ? cxt->hostname :
767 cxt->tty_name ? cxt->tty_name : "<unknown>";
768 pam_handle_t *pamh = cxt->pamh;
769
770 /* if we didn't get a user on the command line, set it to NULL */
771 loginpam_get_username(pamh, &cxt->username);
772
773 show_unknown = getlogindefs_bool("LOG_UNKFAIL_ENAB", 0);
774 retries = getlogindefs_num("LOGIN_RETRIES", LOGIN_MAX_TRIES);
775
776 /*
777 * There may be better ways to deal with some of these conditions, but
778 * at least this way I don't think we'll be giving away information...
779 *
780 * Perhaps someday we can trust that all PAM modules will pay attention
781 * to failure count and get rid of LOGIN_MAX_TRIES?
782 */
783 rc = pam_authenticate(pamh, 0);
784
785 while ((++failcount < retries) &&
786 ((rc == PAM_AUTH_ERR) ||
787 (rc == PAM_USER_UNKNOWN) ||
788 (rc == PAM_CRED_INSUFFICIENT) ||
789 (rc == PAM_AUTHINFO_UNAVAIL))) {
790
791 if (rc == PAM_USER_UNKNOWN && !show_unknown)
792 /*
793 * Logging unknown usernames may be a security issue if
794 * a user enters her password instead of her login name.
795 */
796 cxt->username = NULL;
797 else
798 loginpam_get_username(pamh, &cxt->username);
799
800 syslog(LOG_NOTICE,
801 _("FAILED LOGIN %u FROM %s FOR %s, %s"),
802 failcount, hostname,
803 cxt->username ? cxt->username : "(unknown)",
804 pam_strerror(pamh, rc));
805
806 log_btmp(cxt);
807 log_audit(cxt, 0);
808
809 fprintf(stderr, _("Login incorrect\n\n"));
810
811 pam_set_item(pamh, PAM_USER, NULL);
812 rc = pam_authenticate(pamh, 0);
813 }
814
815 if (is_pam_failure(rc)) {
816
817 if (rc == PAM_USER_UNKNOWN && !show_unknown)
818 cxt->username = NULL;
819 else
820 loginpam_get_username(pamh, &cxt->username);
821
822 if (rc == PAM_MAXTRIES)
823 syslog(LOG_NOTICE,
824 _("TOO MANY LOGIN TRIES (%u) FROM %s FOR %s, %s"),
825 failcount, hostname,
826 cxt->username ? cxt->username : "(unknown)",
827 pam_strerror(pamh, rc));
828 else
829 syslog(LOG_NOTICE,
830 _("FAILED LOGIN SESSION FROM %s FOR %s, %s"),
831 hostname,
832 cxt->username ? cxt->username : "(unknown)",
833 pam_strerror(pamh, rc));
834
835 log_btmp(cxt);
836 log_audit(cxt, 0);
837
838 fprintf(stderr, _("\nLogin incorrect\n"));
839 pam_end(pamh, rc);
840 sleepexit(EXIT_SUCCESS);
841 }
842 }
843
844 static void loginpam_acct(struct login_context *cxt)
845 {
846 int rc;
847 pam_handle_t *pamh = cxt->pamh;
848
849 rc = pam_acct_mgmt(pamh, 0);
850
851 if (rc == PAM_NEW_AUTHTOK_REQD)
852 rc = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
853
854 if (is_pam_failure(rc))
855 loginpam_err(pamh, rc);
856
857 /*
858 * Grab the user information out of the password file for future use.
859 * First get the username that we are actually using, though.
860 */
861 rc = loginpam_get_username(pamh, &cxt->username);
862 if (is_pam_failure(rc))
863 loginpam_err(pamh, rc);
864
865 if (!cxt->username || !*cxt->username) {
866 warnx(_("\nSession setup problem, abort."));
867 syslog(LOG_ERR, _("NULL user name in %s:%d. Abort."),
868 __FUNCTION__, __LINE__);
869 pam_end(pamh, PAM_SYSTEM_ERR);
870 sleepexit(EXIT_FAILURE);
871 }
872 }
873
874 /*
875 * Note that the position of the pam_setcred() call is discussable:
876 *
877 * - the PAM docs recommend pam_setcred() before pam_open_session()
878 * - but the original RFC http://www.opengroup.org/rfc/mirror-rfc/rfc86.0.txt
879 * uses pam_setcred() after pam_open_session()
880 *
881 * The old login versions (before year 2011) followed the RFC. This is probably
882 * not optimal, because there could be a dependence between some session modules
883 * and the user's credentials.
884 *
885 * The best is probably to follow openssh and call pam_setcred() before and
886 * after pam_open_session(). -- kzak@redhat.com (18-Nov-2011)
887 *
888 */
889 static void loginpam_session(struct login_context *cxt)
890 {
891 int rc;
892 pam_handle_t *pamh = cxt->pamh;
893
894 rc = pam_setcred(pamh, PAM_ESTABLISH_CRED);
895 if (is_pam_failure(rc))
896 loginpam_err(pamh, rc);
897
898 rc = pam_open_session(pamh, 0);
899 if (is_pam_failure(rc)) {
900 pam_setcred(cxt->pamh, PAM_DELETE_CRED);
901 loginpam_err(pamh, rc);
902 }
903
904 rc = pam_setcred(pamh, PAM_REINITIALIZE_CRED);
905 if (is_pam_failure(rc)) {
906 pam_close_session(pamh, 0);
907 loginpam_err(pamh, rc);
908 }
909 }
910
911 /*
912 * Detach the controlling terminal, fork, restore syslog stuff, and create
913 * a new session.
914 */
915 static void fork_session(struct login_context *cxt)
916 {
917 struct sigaction sa, oldsa_hup, oldsa_term;
918
919 signal(SIGALRM, SIG_DFL);
920 signal(SIGQUIT, SIG_DFL);
921 signal(SIGTSTP, SIG_IGN);
922
923 memset(&sa, 0, sizeof(sa));
924 sa.sa_handler = SIG_IGN;
925 sigaction(SIGINT, &sa, NULL);
926
927 sigaction(SIGHUP, &sa, &oldsa_hup); /* ignore when TIOCNOTTY */
928
929 /*
930 * Detach the controlling tty.
931 * We don't need the tty in a parent who only waits for a child.
932 * The child calls setsid() that detaches from the tty as well.
933 */
934 ioctl(0, TIOCNOTTY, NULL);
935
936 /*
937 * We have to beware of SIGTERM, because leaving a PAM session
938 * without pam_close_session() is a pretty bad thing.
939 */
940 sa.sa_handler = sig_handler;
941 sigaction(SIGHUP, &sa, NULL);
942 sigaction(SIGTERM, &sa, &oldsa_term);
943
944 closelog();
945
946 /*
947 * We must fork before setuid(), because we need to call
948 * pam_close_session() as root.
949 */
950 child_pid = fork();
951 if (child_pid < 0) {
952 warn(_("fork failed"));
953
954 pam_setcred(cxt->pamh, PAM_DELETE_CRED);
955 pam_end(cxt->pamh, pam_close_session(cxt->pamh, 0));
956 sleepexit(EXIT_FAILURE);
957 }
958
959 if (child_pid) {
960 /*
961 * parent - wait for child to finish, then clean up session
962 */
963 close(0);
964 close(1);
965 close(2);
966 sa.sa_handler = SIG_IGN;
967 sigaction(SIGQUIT, &sa, NULL);
968 sigaction(SIGINT, &sa, NULL);
969
970 /* wait as long as any child is there */
971 while (wait(NULL) == -1 && errno == EINTR) ;
972 openlog("login", LOG_ODELAY, LOG_AUTHPRIV);
973
974 pam_setcred(cxt->pamh, PAM_DELETE_CRED);
975 pam_end(cxt->pamh, pam_close_session(cxt->pamh, 0));
976 exit(EXIT_SUCCESS);
977 }
978
979 /*
980 * child
981 */
982 sigaction(SIGHUP, &oldsa_hup, NULL); /* restore old state */
983 sigaction(SIGTERM, &oldsa_term, NULL);
984 if (got_sig)
985 exit(EXIT_FAILURE);
986
987 /*
988 * Problem: if the user's shell is a shell like ash that doesn't do
989 * setsid() or setpgrp(), then a ctrl-\, sending SIGQUIT to every
990 * process in the pgrp, will kill us.
991 */
992
993 /* start new session */
994 setsid();
995
996 /* make sure we have a controlling tty */
997 open_tty(cxt->tty_path);
998 openlog("login", LOG_ODELAY, LOG_AUTHPRIV); /* reopen */
999
1000 /*
1001 * TIOCSCTTY: steal tty from other process group.
1002 */
1003 if (ioctl(0, TIOCSCTTY, 1))
1004 syslog(LOG_ERR, _("TIOCSCTTY failed: %m"));
1005 signal(SIGINT, SIG_DFL);
1006 }
1007
1008 /*
1009 * Initialize $TERM, $HOME, ...
1010 */
1011 static void init_environ(struct login_context *cxt)
1012 {
1013 struct passwd *pwd = cxt->pwd;
1014 char *termenv, **env;
1015 char tmp[PATH_MAX];
1016 int len, i;
1017
1018 termenv = getenv("TERM");
1019 if (termenv)
1020 termenv = xstrdup(termenv);
1021
1022 /* destroy environment unless user has requested preservation (-p) */
1023 if (!cxt->keep_env) {
1024 environ = xmalloc(sizeof(char *));
1025 memset(environ, 0, sizeof(char *));
1026 }
1027
1028 xsetenv("HOME", pwd->pw_dir, 0); /* legal to override */
1029 xsetenv("USER", pwd->pw_name, 1);
1030 xsetenv("SHELL", pwd->pw_shell, 1);
1031 xsetenv("TERM", termenv ? termenv : "dumb", 1);
1032 free(termenv);
1033
1034 if (pwd->pw_uid) {
1035 if (logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH) != 0)
1036 err(EXIT_FAILURE, _("failed to set the %s environment variable"), "PATH");
1037
1038 } else if (logindefs_setenv("PATH", "ENV_ROOTPATH", NULL) != 0 &&
1039 logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT) != 0) {
1040 err(EXIT_FAILURE, _("failed to set the %s environment variable"), "PATH");
1041 }
1042
1043 /* mailx will give a funny error msg if you forget this one */
1044 len = snprintf(tmp, sizeof(tmp), "%s/%s", _PATH_MAILDIR, pwd->pw_name);
1045 if (len > 0 && (size_t) len < sizeof(tmp))
1046 xsetenv("MAIL", tmp, 0);
1047
1048 /* LOGNAME is not documented in login(1) but HP-UX 6.5 does it. We'll
1049 * not allow modifying it.
1050 */
1051 xsetenv("LOGNAME", pwd->pw_name, 1);
1052
1053 env = pam_getenvlist(cxt->pamh);
1054 for (i = 0; env && env[i]; i++)
1055 putenv(env[i]);
1056 }
1057
1058 /*
1059 * This is called for the -h option, initializes cxt->{hostname,hostaddress}.
1060 */
1061 static void init_remote_info(struct login_context *cxt, char *remotehost)
1062 {
1063 const char *domain;
1064 char *p;
1065 struct addrinfo hints, *info = NULL;
1066
1067 cxt->remote = 1;
1068
1069 get_thishost(cxt, &domain);
1070
1071 if (domain && (p = strchr(remotehost, '.')) &&
1072 strcasecmp(p + 1, domain) == 0)
1073 *p = '\0';
1074
1075 cxt->hostname = xstrdup(remotehost);
1076
1077 memset(&hints, 0, sizeof(hints));
1078 hints.ai_flags = AI_ADDRCONFIG;
1079 cxt->hostaddress[0] = 0;
1080
1081 if (getaddrinfo(cxt->hostname, NULL, &hints, &info) == 0 && info) {
1082 if (info->ai_family == AF_INET) {
1083 struct sockaddr_in *sa =
1084 (struct sockaddr_in *) info->ai_addr;
1085
1086 memcpy(cxt->hostaddress, &(sa->sin_addr), sizeof(sa->sin_addr));
1087
1088 } else if (info->ai_family == AF_INET6) {
1089 struct sockaddr_in6 *sa =
1090 (struct sockaddr_in6 *) info->ai_addr;
1091
1092 memcpy(cxt->hostaddress, &(sa->sin6_addr), sizeof(sa->sin6_addr));
1093 }
1094 freeaddrinfo(info);
1095 }
1096 }
1097
1098 static void __attribute__((__noreturn__)) usage(void)
1099 {
1100 fputs(USAGE_HEADER, stdout);
1101 printf(_(" %s [-p] [-h <host>] [-H] [[-f] <username>]\n"), program_invocation_short_name);
1102 fputs(USAGE_SEPARATOR, stdout);
1103 fputs(_("Begin a session on the system.\n"), stdout);
1104
1105 fputs(USAGE_OPTIONS, stdout);
1106 puts(_(" -p do not destroy the environment"));
1107 puts(_(" -f skip a second login authentication"));
1108 puts(_(" -h <host> hostname to be used for utmp logging"));
1109 puts(_(" -H suppress hostname in the login prompt"));
1110 printf(" --help %s\n", USAGE_OPTSTR_HELP);
1111 printf(" -V, --version %s\n", USAGE_OPTSTR_VERSION);
1112 printf(USAGE_MAN_TAIL("login(1)"));
1113 exit(EXIT_SUCCESS);
1114 }
1115
1116 int main(int argc, char **argv)
1117 {
1118 int c;
1119 int cnt;
1120 char *childArgv[10];
1121 char *buff;
1122 int childArgc = 0;
1123 int retcode;
1124 struct sigaction act;
1125
1126 char *pwdbuf = NULL;
1127 struct passwd *pwd = NULL, _pwd;
1128
1129 struct login_context cxt = {
1130 .tty_mode = TTY_MODE, /* tty chmod() */
1131 .pid = getpid(), /* PID */
1132 #ifdef HAVE_SECURITY_PAM_MISC_H
1133 .conv = { misc_conv, NULL } /* Linux-PAM conversation function */
1134 #elif defined(HAVE_SECURITY_OPENPAM_H)
1135 .conv = { openpam_ttyconv, NULL } /* OpenPAM conversation function */
1136 #endif
1137
1138 };
1139
1140 /* the only two longopts to satisfy UL standards */
1141 enum { HELP_OPTION = CHAR_MAX + 1 };
1142 static const struct option longopts[] = {
1143 {"help", no_argument, NULL, HELP_OPTION},
1144 {"version", no_argument, NULL, 'V'},
1145 {NULL, 0, NULL, 0}
1146 };
1147
1148 timeout = (unsigned int)getlogindefs_num("LOGIN_TIMEOUT", LOGIN_TIMEOUT);
1149
1150 signal(SIGALRM, timedout);
1151 (void) sigaction(SIGALRM, NULL, &act);
1152 act.sa_flags &= ~SA_RESTART;
1153 sigaction(SIGALRM, &act, NULL);
1154 alarm(timeout);
1155 signal(SIGQUIT, SIG_IGN);
1156 signal(SIGINT, SIG_IGN);
1157
1158 setlocale(LC_ALL, "");
1159 bindtextdomain(PACKAGE, LOCALEDIR);
1160 textdomain(PACKAGE);
1161
1162 setpriority(PRIO_PROCESS, 0, 0);
1163 initproctitle(argc, argv);
1164
1165 /*
1166 * -p is used by getty to tell login not to destroy the environment
1167 * -f is used to skip a second login authentication
1168 * -h is used by other servers to pass the name of the remote
1169 * host to login so that it may be placed in utmp and wtmp
1170 */
1171 while ((c = getopt_long(argc, argv, "fHh:pV", longopts, NULL)) != -1)
1172 switch (c) {
1173 case 'f':
1174 cxt.noauth = 1;
1175 break;
1176
1177 case 'H':
1178 cxt.nohost = 1;
1179 break;
1180
1181 case 'h':
1182 if (getuid()) {
1183 fprintf(stderr,
1184 _("login: -h is for superuser only\n"));
1185 exit(EXIT_FAILURE);
1186 }
1187 init_remote_info(&cxt, optarg);
1188 break;
1189
1190 case 'p':
1191 cxt.keep_env = 1;
1192 break;
1193
1194 case 'V':
1195 printf(UTIL_LINUX_VERSION);
1196 return EXIT_SUCCESS;
1197 case HELP_OPTION:
1198 usage();
1199 default:
1200 errtryhelp(EXIT_FAILURE);
1201 }
1202 argc -= optind;
1203 argv += optind;
1204
1205 if (*argv) {
1206 char *p = *argv;
1207 cxt.username = xstrdup(p);
1208
1209 /* Wipe the name - some people mistype their password here. */
1210 /* (Of course we are too late, but perhaps this helps a little...) */
1211 while (*p)
1212 *p++ = ' ';
1213 }
1214
1215 for (cnt = get_fd_tabsize() - 1; cnt > 2; cnt--)
1216 close(cnt);
1217
1218 setpgrp(); /* set pgid to pid this means that setsid() will fail */
1219 init_tty(&cxt);
1220
1221 openlog("login", LOG_ODELAY, LOG_AUTHPRIV);
1222
1223 init_loginpam(&cxt);
1224
1225 /* login -f, then the user has already been authenticated */
1226 cxt.noauth = cxt.noauth && getuid() == 0 ? 1 : 0;
1227
1228 if (!cxt.noauth)
1229 loginpam_auth(&cxt);
1230
1231 /*
1232 * Authentication may be skipped (for example, during krlogin, rlogin,
1233 * etc...), but it doesn't mean that we can skip other account checks.
1234 * The account could be disabled or the password has expired (although
1235 * the kerberos ticket is valid). -- kzak@redhat.com (22-Feb-2006)
1236 */
1237 loginpam_acct(&cxt);
1238
1239 if (!(cxt.pwd = get_passwd_entry(cxt.username, &pwdbuf, &_pwd))) {
1240 warnx(_("\nSession setup problem, abort."));
1241 syslog(LOG_ERR, _("Invalid user name \"%s\" in %s:%d. Abort."),
1242 cxt.username, __FUNCTION__, __LINE__);
1243 pam_end(cxt.pamh, PAM_SYSTEM_ERR);
1244 sleepexit(EXIT_FAILURE);
1245 }
1246
1247 pwd = cxt.pwd;
1248 cxt.username = pwd->pw_name;
1249
1250 /*
1251 * Initialize the supplementary group list. This should be done before
1252 * pam_setcred, because PAM modules might add groups during that call.
1253 *
1254 * For root we don't call initgroups, instead we call setgroups with
1255 * group 0. This avoids the need to step through the whole group file,
1256 * which can cause problems if NIS, NIS+, LDAP or something similar
1257 * is used and the machine has network problems.
1258 */
1259 retcode = pwd->pw_uid ? initgroups(cxt.username, pwd->pw_gid) : /* user */
1260 setgroups(0, NULL); /* root */
1261 if (retcode < 0) {
1262 syslog(LOG_ERR, _("groups initialization failed: %m"));
1263 warnx(_("\nSession setup problem, abort."));
1264 pam_end(cxt.pamh, PAM_SYSTEM_ERR);
1265 sleepexit(EXIT_FAILURE);
1266 }
1267
1268 /*
1269 * Open PAM session (after successful authentication and account check).
1270 */
1271 loginpam_session(&cxt);
1272
1273 /* committed to login -- turn off timeout */
1274 alarm((unsigned int)0);
1275
1276 endpwent();
1277
1278 cxt.quiet = get_hushlogin_status(pwd, 1);
1279
1280 log_utmp(&cxt);
1281 log_audit(&cxt, 1);
1282 log_lastlog(&cxt);
1283
1284 chown_tty(&cxt);
1285
1286 if (setgid(pwd->pw_gid) < 0 && pwd->pw_gid) {
1287 syslog(LOG_ALERT, _("setgid() failed"));
1288 exit(EXIT_FAILURE);
1289 }
1290
1291 if (pwd->pw_shell == NULL || *pwd->pw_shell == '\0')
1292 pwd->pw_shell = _PATH_BSHELL;
1293
1294 init_environ(&cxt); /* init $HOME, $TERM ... */
1295
1296 setproctitle("login", cxt.username);
1297
1298 log_syslog(&cxt);
1299
1300 if (!cxt.quiet) {
1301 motd();
1302
1303 #ifdef LOGIN_STAT_MAIL
1304 /*
1305 * This turns out to be a bad idea: when the mail spool
1306 * is NFS mounted, and the NFS connection hangs, the
1307 * login hangs, even root cannot login.
1308 * Checking for mail should be done from the shell.
1309 */
1310 {
1311 struct stat st;
1312 char *mail;
1313
1314 mail = getenv("MAIL");
1315 if (mail && stat(mail, &st) == 0 && st.st_size != 0) {
1316 if (st.st_mtime > st.st_atime)
1317 printf(_("You have new mail.\n"));
1318 else
1319 printf(_("You have mail.\n"));
1320 }
1321 }
1322 #endif
1323 }
1324
1325 /*
1326 * Detach the controlling terminal, fork, and create a new session
1327 * and reinitialize syslog stuff.
1328 */
1329 fork_session(&cxt);
1330
1331 /* discard permissions last so we can't get killed and drop core */
1332 if (setuid(pwd->pw_uid) < 0 && pwd->pw_uid) {
1333 syslog(LOG_ALERT, _("setuid() failed"));
1334 exit(EXIT_FAILURE);
1335 }
1336
1337 /* wait until here to change directory! */
1338 if (chdir(pwd->pw_dir) < 0) {
1339 warn(_("%s: change directory failed"), pwd->pw_dir);
1340
1341 if (!getlogindefs_bool("DEFAULT_HOME", 1))
1342 exit(0);
1343 if (chdir("/"))
1344 exit(EXIT_FAILURE);
1345 pwd->pw_dir = "/";
1346 printf(_("Logging in with home = \"/\".\n"));
1347 }
1348
1349 /* if the shell field has a space: treat it like a shell script */
1350 if (strchr(pwd->pw_shell, ' ')) {
1351 buff = xmalloc(strlen(pwd->pw_shell) + 6);
1352
1353 strcpy(buff, "exec ");
1354 strcat(buff, pwd->pw_shell);
1355 childArgv[childArgc++] = "/bin/sh";
1356 childArgv[childArgc++] = "-sh";
1357 childArgv[childArgc++] = "-c";
1358 childArgv[childArgc++] = buff;
1359 } else {
1360 char tbuf[PATH_MAX + 2], *p;
1361
1362 tbuf[0] = '-';
1363 xstrncpy(tbuf + 1, ((p = strrchr(pwd->pw_shell, '/')) ?
1364 p + 1 : pwd->pw_shell), sizeof(tbuf) - 1);
1365
1366 childArgv[childArgc++] = pwd->pw_shell;
1367 childArgv[childArgc++] = xstrdup(tbuf);
1368 }
1369
1370 childArgv[childArgc++] = NULL;
1371
1372 execvp(childArgv[0], childArgv + 1);
1373
1374 if (!strcmp(childArgv[0], "/bin/sh"))
1375 warn(_("couldn't exec shell script"));
1376 else
1377 warn(_("no shell"));
1378
1379 exit(EXIT_SUCCESS);
1380 }