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