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