]>
Commit | Line | Data |
---|---|---|
6dbe3af9 | 1 | /* |
0d28a157 KZ |
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 | * Michael Glad (glad@daimi.dk) | |
8 | * Computer Science Department, Aarhus University, Denmark | |
9 | * 1990-07-04 | |
10 | * | |
6dbe3af9 KZ |
11 | * Copyright (c) 1980, 1987, 1988 The Regents of the University of California. |
12 | * All rights reserved. | |
13 | * | |
14 | * Redistribution and use in source and binary forms are permitted | |
15 | * provided that the above copyright notice and this paragraph are | |
16 | * duplicated in all such forms and that any documentation, | |
17 | * advertising materials, and other materials related to such | |
18 | * distribution and use acknowledge that the software was developed | |
19 | * by the University of California, Berkeley. The name of the | |
20 | * University may not be used to endorse or promote products derived | |
21 | * from this software without specific prior written permission. | |
22 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR | |
23 | * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED | |
24 | * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. | |
25 | */ | |
6dbe3af9 | 26 | #include <sys/param.h> |
fd6b7a7f | 27 | #include <stdio.h> |
6dbe3af9 KZ |
28 | #include <ctype.h> |
29 | #include <unistd.h> | |
30 | #include <getopt.h> | |
31 | #include <memory.h> | |
fd6b7a7f | 32 | #include <time.h> |
6dbe3af9 KZ |
33 | #include <sys/stat.h> |
34 | #include <sys/time.h> | |
35 | #include <sys/resource.h> | |
36 | #include <sys/file.h> | |
37 | #include <termios.h> | |
38 | #include <string.h> | |
6dbe3af9 | 39 | #include <sys/ioctl.h> |
fd6b7a7f | 40 | #include <sys/wait.h> |
6dbe3af9 KZ |
41 | #include <signal.h> |
42 | #include <errno.h> | |
43 | #include <grp.h> | |
44 | #include <pwd.h> | |
fd6b7a7f | 45 | #include <utmp.h> |
6dbe3af9 KZ |
46 | #include <setjmp.h> |
47 | #include <stdlib.h> | |
6dbe3af9 KZ |
48 | #include <sys/syslog.h> |
49 | #include <sys/sysmacros.h> | |
5476bf9b | 50 | #include <linux/major.h> |
6dbe3af9 | 51 | #include <netdb.h> |
cb5acd69 | 52 | #include <lastlog.h> |
905045d4 KZ |
53 | #include <security/pam_appl.h> |
54 | #include <security/pam_misc.h> | |
f8bdba2f KZ |
55 | #ifdef HAVE_LIBAUDIT |
56 | # include <libaudit.h> | |
57 | #endif | |
0aeb57ac FG |
58 | #ifdef HAVE_CRYPT_H |
59 | #include <crypt.h> | |
60 | #endif | |
61 | ||
cb5acd69 KZ |
62 | #include "c.h" |
63 | #include "setproctitle.h" | |
66ee8158 | 64 | #include "pathnames.h" |
66ee8158 | 65 | #include "login.h" |
8abcf290 | 66 | #include "strutils.h" |
7eda085c | 67 | #include "nls.h" |
3e31a2df | 68 | #include "xalloc.h" |
6dbe3af9 | 69 | |
9244d3c2 | 70 | #define PAM_MAX_LOGIN_TRIES 3 |
905045d4 | 71 | #define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS) |
fd6b7a7f | 72 | |
fd6b7a7f KZ |
73 | #define SLEEP_EXIT_TIMEOUT 5 |
74 | ||
fd6b7a7f KZ |
75 | #ifdef USE_TTY_GROUP |
76 | # define TTY_MODE 0620 | |
77 | #else | |
78 | # define TTY_MODE 0600 | |
79 | #endif | |
80 | ||
6dbe3af9 | 81 | #define TTYGRPNAME "tty" /* name of group to own ttys */ |
726f69e2 KZ |
82 | |
83 | #ifndef MAXPATHLEN | |
84 | # define MAXPATHLEN 1024 | |
6dbe3af9 KZ |
85 | #endif |
86 | ||
87 | /* | |
88 | * This bounds the time given to login. Not a define so it can | |
89 | * be patched on machines where it's too small. | |
90 | */ | |
cb5acd69 | 91 | int timeout = 60; |
6dbe3af9 | 92 | |
48d7b13a KZ |
93 | struct passwd *pwd; |
94 | ||
e8f26419 | 95 | static struct passwd pwdcopy; |
cb5acd69 | 96 | char hostaddress[16]; /* used in checktty.c */ |
ea6c190a | 97 | sa_family_t hostfamily; /* used in checktty.c */ |
364cda48 KZ |
98 | char *hostname; /* idem */ |
99 | static char *username, *tty_name, *tty_number; | |
66ee8158 KZ |
100 | static char thishost[100]; |
101 | static int failures = 1; | |
1d4ad1de | 102 | static pid_t pid; |
6dbe3af9 | 103 | |
cb5acd69 KZ |
104 | static void timedout (int); |
105 | static void sigint (int); | |
106 | static void motd (void); | |
107 | static void dolastlog (int quiet); | |
108 | ||
eb63b9b8 KZ |
109 | /* Nice and simple code provided by Linus Torvalds 16-Feb-93 */ |
110 | /* Nonblocking stuff by Maciej W. Rozycki, macro@ds2.pg.gda.pl, 1999. | |
111 | He writes: "Login performs open() on a tty in a blocking mode. | |
112 | In some cases it may make login wait in open() for carrier infinitely, | |
113 | for example if the line is a simplistic case of a three-wire serial | |
114 | connection. I believe login should open the line in the non-blocking mode | |
115 | leaving the decision to make a connection to getty (where it actually | |
116 | belongs). */ | |
66ee8158 | 117 | static void |
364cda48 | 118 | opentty(const char * tty) { |
1d4ad1de KZ |
119 | int i, fd, flags; |
120 | ||
121 | fd = open(tty, O_RDWR | O_NONBLOCK); | |
122 | if (fd == -1) { | |
960cf573 | 123 | syslog(LOG_ERR, _("FATAL: can't reopen tty: %m")); |
66020e56 | 124 | sleepexit(EXIT_FAILURE); |
1d4ad1de | 125 | } |
eb63b9b8 | 126 | |
8bee984a KZ |
127 | if (!isatty(fd)) { |
128 | close(fd); | |
129 | syslog(LOG_ERR, _("FATAL: %s is not a terminal"), tty); | |
66020e56 | 130 | sleepexit(EXIT_FAILURE); |
8bee984a KZ |
131 | } |
132 | ||
1d4ad1de KZ |
133 | flags = fcntl(fd, F_GETFL); |
134 | flags &= ~O_NONBLOCK; | |
135 | fcntl(fd, F_SETFL, flags); | |
e09947b5 | 136 | |
1d4ad1de KZ |
137 | for (i = 0; i < fd; i++) |
138 | close(i); | |
139 | for (i = 0; i < 3; i++) | |
140 | if (fd != i) | |
141 | dup2(fd, i); | |
142 | if (fd >= 3) | |
143 | close(fd); | |
6dbe3af9 KZ |
144 | } |
145 | ||
24f4bbff KZ |
146 | /* In case login is suid it was possible to use a hardlink as stdin |
147 | and exploit races for a local root exploit. (Wojciech Purczynski). */ | |
148 | /* More precisely, the problem is ttyn := ttyname(0); ...; chown(ttyn); | |
149 | here ttyname() might return "/tmp/x", a hardlink to a pseudotty. */ | |
150 | /* All of this is a problem only when login is suid, which it isnt. */ | |
151 | static void | |
152 | check_ttyname(char *ttyn) { | |
153 | struct stat statbuf; | |
154 | ||
8bee984a KZ |
155 | if (ttyn == NULL |
156 | || *ttyn == '\0' | |
157 | || lstat(ttyn, &statbuf) | |
24f4bbff | 158 | || !S_ISCHR(statbuf.st_mode) |
62eabbf4 KZ |
159 | || (statbuf.st_nlink > 1 && strncmp(ttyn, "/dev/", 5)) |
160 | || (access(ttyn, R_OK | W_OK) != 0)) { | |
66020e56 | 161 | |
24f4bbff | 162 | syslog(LOG_ERR, _("FATAL: bad tty")); |
66020e56 | 163 | sleepexit(EXIT_FAILURE); |
24f4bbff KZ |
164 | } |
165 | } | |
166 | ||
48d7b13a | 167 | #ifdef LOGIN_CHOWN_VCS |
fd6b7a7f KZ |
168 | /* true if the filedescriptor fd is a console tty, very Linux specific */ |
169 | static int | |
364cda48 | 170 | consoletty(int fd) { |
fd6b7a7f KZ |
171 | struct stat stb; |
172 | ||
e09947b5 | 173 | if ((fstat(fd, &stb) >= 0) |
fd6b7a7f KZ |
174 | && (major(stb.st_rdev) == TTY_MAJOR) |
175 | && (minor(stb.st_rdev) < 64)) { | |
176 | return 1; | |
177 | } | |
fd6b7a7f KZ |
178 | return 0; |
179 | } | |
48d7b13a | 180 | #endif |
fd6b7a7f | 181 | |
364cda48 KZ |
182 | /* |
183 | * Log failed login attempts in _PATH_BTMP if that exists. | |
184 | * Must be called only with username the name of an actual user. | |
185 | * The most common login failure is to give password instead of username. | |
186 | */ | |
364cda48 KZ |
187 | static void |
188 | logbtmp(const char *line, const char *username, const char *hostname) { | |
189 | struct utmp ut; | |
75d4dbb0 | 190 | struct timeval tv; |
364cda48 KZ |
191 | |
192 | memset(&ut, 0, sizeof(ut)); | |
193 | ||
194 | strncpy(ut.ut_user, username ? username : "(unknown)", | |
195 | sizeof(ut.ut_user)); | |
196 | ||
197 | strncpy(ut.ut_id, line + 3, sizeof(ut.ut_id)); | |
198 | xstrncpy(ut.ut_line, line, sizeof(ut.ut_line)); | |
199 | ||
200 | #if defined(_HAVE_UT_TV) /* in <utmpbits.h> included by <utmp.h> */ | |
75d4dbb0 KZ |
201 | gettimeofday(&tv, NULL); |
202 | ut.ut_tv.tv_sec = tv.tv_sec; | |
203 | ut.ut_tv.tv_usec = tv.tv_usec; | |
364cda48 KZ |
204 | #else |
205 | { | |
206 | time_t t; | |
207 | time(&t); | |
208 | ut.ut_time = t; /* ut_time is not always a time_t */ | |
209 | } | |
210 | #endif | |
211 | ||
212 | ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */ | |
1d4ad1de | 213 | ut.ut_pid = pid; |
364cda48 KZ |
214 | if (hostname) { |
215 | xstrncpy(ut.ut_host, hostname, sizeof(ut.ut_host)); | |
216 | if (hostaddress[0]) | |
ea6c190a | 217 | memcpy(&ut.ut_addr_v6, hostaddress, sizeof(ut.ut_addr_v6)); |
364cda48 | 218 | } |
48d7b13a | 219 | #if HAVE_UPDWTMP /* bad luck for ancient systems */ |
364cda48 KZ |
220 | updwtmp(_PATH_BTMP, &ut); |
221 | #endif | |
222 | } | |
b2452358 KZ |
223 | |
224 | ||
225 | static int child_pid = 0; | |
226 | static volatile int got_sig = 0; | |
227 | ||
228 | /* | |
229 | * This handler allows to inform a shell about signals to login. If you have | |
230 | * (root) permissions you can kill all login childrent by one signal to login | |
231 | * process. | |
232 | * | |
233 | * Also, parent who is session leader is able (before setsid() in child) to | |
234 | * inform child when controlling tty goes away (e.g. modem hangup, SIGHUP). | |
235 | */ | |
236 | static void | |
237 | sig_handler(int signal) | |
238 | { | |
239 | if(child_pid) | |
240 | kill(-child_pid, signal); | |
241 | else | |
242 | got_sig = 1; | |
243 | if(signal == SIGTERM) | |
244 | kill(-child_pid, SIGHUP); /* because the shell often ignores SIGTERM */ | |
245 | } | |
246 | ||
f8bdba2f KZ |
247 | #ifdef HAVE_LIBAUDIT |
248 | static void | |
249 | logaudit(const char *tty, const char *username, const char *hostname, | |
250 | struct passwd *pwd, int status) | |
251 | { | |
f8bdba2f KZ |
252 | int audit_fd; |
253 | ||
254 | audit_fd = audit_open(); | |
255 | if (audit_fd == -1) | |
256 | return; | |
e797d832 | 257 | if (!pwd && username) |
f8bdba2f | 258 | pwd = getpwnam(username); |
f8bdba2f | 259 | |
8ccf0b25 SG |
260 | audit_log_acct_message(audit_fd, AUDIT_USER_LOGIN, |
261 | NULL, "login", username ? username : "(unknown)", | |
878a3697 | 262 | pwd ? pwd->pw_uid : (unsigned int) -1, hostname, NULL, tty, status); |
f8bdba2f KZ |
263 | |
264 | close(audit_fd); | |
265 | } | |
266 | #else /* ! HAVE_LIBAUDIT */ | |
267 | # define logaudit(tty, username, hostname, pwd, status) | |
268 | #endif /* HAVE_LIBAUDIT */ | |
269 | ||
53e9eda3 | 270 | /* encapsulate stupid "void **" pam_get_item() API */ |
905045d4 | 271 | static int loginpam_get_username(pam_handle_t *pamh, char **name) |
53e9eda3 KZ |
272 | { |
273 | const void *item = (void *) *name; | |
274 | int rc; | |
275 | rc = pam_get_item(pamh, PAM_USER, &item); | |
276 | *name = (char *) item; | |
277 | return rc; | |
278 | } | |
53e9eda3 | 279 | |
905045d4 KZ |
280 | static int loginpam_err(pam_handle_t *pamh, int retcode) |
281 | { | |
282 | const char *msg = pam_strerror(pamh, retcode); | |
283 | ||
284 | if (msg) { | |
285 | fprintf(stderr, "\n%s\n", msg); | |
286 | syslog(LOG_ERR, "%s", msg); | |
287 | } | |
288 | pam_end(pamh, retcode); | |
289 | exit(EXIT_FAILURE); | |
290 | ||
291 | } | |
292 | ||
3c0e680c KZ |
293 | /* |
294 | * We need to check effective UID/GID. For example $HOME could be on root | |
295 | * squashed NFS or on NFS with UID mapping and access(2) uses real UID/GID. | |
296 | * The open(2) seems as the surest solution. | |
297 | * -- kzak@redhat.com (10-Apr-2009) | |
298 | */ | |
299 | int | |
300 | effective_access(const char *path, int mode) | |
301 | { | |
302 | int fd = open(path, mode); | |
303 | if (fd != -1) | |
304 | close(fd); | |
305 | return fd == -1 ? -1 : 0; | |
306 | } | |
307 | ||
6dbe3af9 | 308 | int |
5c36a0eb | 309 | main(int argc, char **argv) |
6dbe3af9 | 310 | { |
22853e4a | 311 | extern int optind; |
fd6b7a7f KZ |
312 | extern char *optarg, **environ; |
313 | struct group *gr; | |
314 | register int ch; | |
315 | register char *p; | |
abd0a5c9 | 316 | int fflag, hflag, pflag, cnt; |
5c36a0eb KZ |
317 | int quietlog, passwd_req; |
318 | char *domain, *ttyn; | |
8bee984a | 319 | char tbuf[MAXPATHLEN + 2]; |
fd6b7a7f | 320 | char *termenv; |
24f4bbff KZ |
321 | char *childArgv[10]; |
322 | char *buff; | |
fd6b7a7f | 323 | int childArgc = 0; |
fd6b7a7f KZ |
324 | int retcode; |
325 | pam_handle_t *pamh = NULL; | |
326 | struct pam_conv conv = { misc_conv, NULL }; | |
b2452358 | 327 | struct sigaction sa, oldsa_hup, oldsa_term; |
48d7b13a | 328 | #ifdef LOGIN_CHOWN_VCS |
66ee8158 KZ |
329 | char vcsn[20], vcsan[20]; |
330 | #endif | |
6dbe3af9 | 331 | |
1d4ad1de KZ |
332 | pid = getpid(); |
333 | ||
fd6b7a7f | 334 | signal(SIGALRM, timedout); |
649efbb3 | 335 | siginterrupt(SIGALRM,1); /* we have to interrupt syscalls like ioclt() */ |
fd6b7a7f KZ |
336 | alarm((unsigned int)timeout); |
337 | signal(SIGQUIT, SIG_IGN); | |
338 | signal(SIGINT, SIG_IGN); | |
7eda085c KZ |
339 | |
340 | setlocale(LC_ALL, ""); | |
341 | bindtextdomain(PACKAGE, LOCALEDIR); | |
342 | textdomain(PACKAGE); | |
e09947b5 | 343 | |
fd6b7a7f | 344 | setpriority(PRIO_PROCESS, 0, 0); |
5c36a0eb | 345 | initproctitle(argc, argv); |
e09947b5 | 346 | |
fd6b7a7f KZ |
347 | /* |
348 | * -p is used by getty to tell login not to destroy the environment | |
e09947b5 | 349 | * -f is used to skip a second login authentication |
fd6b7a7f KZ |
350 | * -h is used by other servers to pass the name of the remote |
351 | * host to login so that it may be placed in utmp and wtmp | |
352 | */ | |
353 | gethostname(tbuf, sizeof(tbuf)); | |
c07ebfa1 | 354 | xstrncpy(thishost, tbuf, sizeof(thishost)); |
c0f19ccf | 355 | domain = strchr(tbuf, '.'); |
e09947b5 | 356 | |
364cda48 | 357 | username = tty_name = hostname = NULL; |
fd6b7a7f KZ |
358 | fflag = hflag = pflag = 0; |
359 | passwd_req = 1; | |
5c36a0eb | 360 | |
ffc43748 | 361 | while ((ch = getopt(argc, argv, "fh:p")) != -1) |
fd6b7a7f KZ |
362 | switch (ch) { |
363 | case 'f': | |
364 | fflag = 1; | |
365 | break; | |
e09947b5 | 366 | |
fd6b7a7f KZ |
367 | case 'h': |
368 | if (getuid()) { | |
369 | fprintf(stderr, | |
7eda085c | 370 | _("login: -h for super-user only.\n")); |
66020e56 | 371 | exit(EXIT_FAILURE); |
fd6b7a7f KZ |
372 | } |
373 | hflag = 1; | |
c0f19ccf | 374 | if (domain && (p = strchr(optarg, '.')) && |
fd6b7a7f KZ |
375 | strcasecmp(p, domain) == 0) |
376 | *p = 0; | |
364cda48 | 377 | |
7eda085c | 378 | hostname = strdup(optarg); /* strdup: Ambrose C. Li */ |
364cda48 | 379 | { |
ea6c190a KZ |
380 | struct addrinfo hints, *info = NULL; |
381 | ||
382 | memset(&hints, 0, sizeof(hints)); | |
383 | hints.ai_flags = AI_ADDRCONFIG; | |
384 | ||
385 | hostaddress[0] = 0; | |
386 | ||
387 | if (getaddrinfo(hostname, NULL, &hints, &info)==0 && info) { | |
388 | if (info->ai_family == AF_INET) { | |
389 | struct sockaddr_in *sa = | |
390 | (struct sockaddr_in *) info->ai_addr; | |
391 | memcpy(hostaddress, &(sa->sin_addr), | |
392 | sizeof(sa->sin_addr)); | |
393 | } | |
394 | else if (info->ai_family == AF_INET6) { | |
395 | struct sockaddr_in6 *sa = | |
396 | (struct sockaddr_in6 *) info->ai_addr; | |
397 | memcpy(hostaddress, &(sa->sin6_addr), | |
398 | sizeof(sa->sin6_addr)); | |
399 | } | |
400 | hostfamily = info->ai_family; | |
401 | freeaddrinfo(info); | |
402 | } | |
fd6b7a7f KZ |
403 | } |
404 | break; | |
e09947b5 | 405 | |
fd6b7a7f KZ |
406 | case 'p': |
407 | pflag = 1; | |
408 | break; | |
409 | ||
410 | case '?': | |
411 | default: | |
412 | fprintf(stderr, | |
7eda085c | 413 | _("usage: login [-fp] [username]\n")); |
66020e56 | 414 | exit(EXIT_FAILURE); |
fd6b7a7f KZ |
415 | } |
416 | argc -= optind; | |
417 | argv += optind; | |
abd0a5c9 | 418 | |
fd6b7a7f | 419 | if (*argv) { |
5c36a0eb KZ |
420 | char *p = *argv; |
421 | username = strdup(p); | |
abd0a5c9 | 422 | |
5c36a0eb KZ |
423 | /* wipe name - some people mistype their password here */ |
424 | /* (of course we are too late, but perhaps this helps a little ..) */ | |
425 | while(*p) | |
426 | *p++ = ' '; | |
abd0a5c9 | 427 | } |
22853e4a | 428 | |
fd6b7a7f KZ |
429 | for (cnt = getdtablesize(); cnt > 2; cnt--) |
430 | close(cnt); | |
e09947b5 | 431 | |
8bee984a KZ |
432 | /* note that libc checks that the file descriptor is a terminal, so we don't |
433 | * have to call isatty() here */ | |
fd6b7a7f | 434 | ttyn = ttyname(0); |
24f4bbff KZ |
435 | check_ttyname(ttyn); |
436 | ||
364cda48 KZ |
437 | if (strncmp(ttyn, "/dev/", 5) == 0) |
438 | tty_name = ttyn+5; | |
439 | else | |
440 | tty_name = ttyn; | |
441 | ||
442 | if (strncmp(ttyn, "/dev/tty", 8) == 0) | |
443 | tty_number = ttyn+8; | |
444 | else { | |
fd6b7a7f | 445 | char *p = ttyn; |
fd6b7a7f | 446 | while (*p && !isdigit(*p)) p++; |
364cda48 | 447 | tty_number = p; |
fd6b7a7f | 448 | } |
364cda48 | 449 | |
48d7b13a | 450 | #ifdef LOGIN_CHOWN_VCS |
364cda48 KZ |
451 | /* find names of Virtual Console devices, for later mode change */ |
452 | snprintf(vcsn, sizeof(vcsn), "/dev/vcs%s", tty_number); | |
453 | snprintf(vcsan, sizeof(vcsan), "/dev/vcsa%s", tty_number); | |
66ee8158 | 454 | #endif |
fd6b7a7f | 455 | |
1d4ad1de | 456 | /* set pgid to pid */ |
fd6b7a7f | 457 | setpgrp(); |
1d4ad1de | 458 | /* this means that setsid() will fail */ |
e09947b5 | 459 | |
fd6b7a7f KZ |
460 | { |
461 | struct termios tt, ttt; | |
e09947b5 | 462 | |
fd6b7a7f KZ |
463 | tcgetattr(0, &tt); |
464 | ttt = tt; | |
465 | ttt.c_cflag &= ~HUPCL; | |
66ee8158 | 466 | |
364cda48 | 467 | /* These can fail, e.g. with ttyn on a read-only filesystem */ |
11784a84 KZ |
468 | if (fchown(0, 0, 0)) { |
469 | ; /* glibc warn_unused_result */ | |
470 | } | |
471 | ||
453b3614 | 472 | fchmod(0, TTY_MODE); |
364cda48 KZ |
473 | |
474 | /* Kill processes left on this tty */ | |
475 | tcsetattr(0,TCSAFLUSH,&ttt); | |
476 | signal(SIGHUP, SIG_IGN); /* so vhangup() wont kill us */ | |
477 | vhangup(); | |
478 | signal(SIGHUP, SIG_DFL); | |
1d4ad1de KZ |
479 | |
480 | /* open stdin,stdout,stderr to the tty */ | |
fd6b7a7f | 481 | opentty(ttyn); |
e09947b5 | 482 | |
1d4ad1de | 483 | /* restore tty modes */ |
fd6b7a7f KZ |
484 | tcsetattr(0,TCSAFLUSH,&tt); |
485 | } | |
22853e4a | 486 | |
fd6b7a7f KZ |
487 | openlog("login", LOG_ODELAY, LOG_AUTHPRIV); |
488 | ||
22853e4a KZ |
489 | /* |
490 | * username is initialized to NULL | |
491 | * and if specified on the command line it is set. | |
492 | * Therefore, we are safe not setting it to anything | |
493 | */ | |
fd6b7a7f | 494 | |
067f5343 | 495 | retcode = pam_start(hflag?"remote":"login",username, &conv, &pamh); |
fd6b7a7f | 496 | if(retcode != PAM_SUCCESS) { |
66020e56 | 497 | warnx(_("PAM failure, aborting: %s"), pam_strerror(pamh, retcode)); |
e8f26419 KZ |
498 | syslog(LOG_ERR, _("Couldn't initialize PAM: %s"), |
499 | pam_strerror(pamh, retcode)); | |
66020e56 | 500 | exit(EXIT_FAILURE); |
fd6b7a7f | 501 | } |
905045d4 | 502 | |
fd6b7a7f | 503 | /* hostname & tty are either set to NULL or their correct values, |
905045d4 KZ |
504 | * depending on how much we know |
505 | */ | |
fd6b7a7f | 506 | retcode = pam_set_item(pamh, PAM_RHOST, hostname); |
905045d4 KZ |
507 | if (is_pam_failure(retcode)) |
508 | loginpam_err(pamh, retcode); | |
509 | ||
364cda48 | 510 | retcode = pam_set_item(pamh, PAM_TTY, tty_name); |
905045d4 KZ |
511 | if (is_pam_failure(retcode)) |
512 | loginpam_err(pamh, retcode); | |
7eda085c | 513 | |
22853e4a KZ |
514 | /* |
515 | * Andrew.Taylor@cal.montage.ca: Provide a user prompt to PAM | |
516 | * so that the "login: " prompt gets localized. Unfortunately, | |
517 | * PAM doesn't have an interface to specify the "Password: " string | |
518 | * (yet). | |
519 | */ | |
eb63b9b8 | 520 | retcode = pam_set_item(pamh, PAM_USER_PROMPT, _("login: ")); |
905045d4 KZ |
521 | if (is_pam_failure(retcode)) |
522 | loginpam_err(pamh, retcode); | |
eb63b9b8 | 523 | |
30568eec KZ |
524 | if (username) { |
525 | /* we need't the original username. We have to follow PAM. */ | |
526 | free(username); | |
527 | username = NULL; | |
528 | } | |
529 | ||
fd6b7a7f KZ |
530 | /* if fflag == 1, then the user has already been authenticated */ |
531 | if (fflag && (getuid() == 0)) | |
532 | passwd_req = 0; | |
533 | else | |
534 | passwd_req = 1; | |
6dbe3af9 | 535 | |
fd6b7a7f KZ |
536 | if(passwd_req == 1) { |
537 | int failcount=0; | |
538 | ||
364cda48 | 539 | /* if we didn't get a user on the command line, set it to NULL */ |
905045d4 | 540 | loginpam_get_username(pamh, &username); |
364cda48 | 541 | |
fd6b7a7f KZ |
542 | /* there may be better ways to deal with some of these |
543 | conditions, but at least this way I don't think we'll | |
544 | be giving away information... */ | |
545 | /* Perhaps someday we can trust that all PAM modules will | |
546 | pay attention to failure count and get rid of MAX_LOGIN_TRIES? */ | |
547 | ||
548 | retcode = pam_authenticate(pamh, 0); | |
549 | while((failcount++ < PAM_MAX_LOGIN_TRIES) && | |
550 | ((retcode == PAM_AUTH_ERR) || | |
551 | (retcode == PAM_USER_UNKNOWN) || | |
552 | (retcode == PAM_CRED_INSUFFICIENT) || | |
553 | (retcode == PAM_AUTHINFO_UNAVAIL))) { | |
905045d4 | 554 | loginpam_get_username(pamh, &username); |
364cda48 | 555 | |
7eda085c | 556 | syslog(LOG_NOTICE,_("FAILED LOGIN %d FROM %s FOR %s, %s"), |
364cda48 KZ |
557 | failcount, hostname, username, pam_strerror(pamh, retcode)); |
558 | logbtmp(tty_name, username, hostname); | |
f8bdba2f | 559 | logaudit(tty_name, username, hostname, NULL, 0); |
364cda48 | 560 | |
7eda085c | 561 | fprintf(stderr,_("Login incorrect\n\n")); |
fd6b7a7f KZ |
562 | pam_set_item(pamh,PAM_USER,NULL); |
563 | retcode = pam_authenticate(pamh, 0); | |
6dbe3af9 KZ |
564 | } |
565 | ||
905045d4 KZ |
566 | if (is_pam_failure(retcode)) { |
567 | loginpam_get_username(pamh, &username); | |
6dbe3af9 | 568 | |
364cda48 | 569 | if (retcode == PAM_MAXTRIES) |
7eda085c KZ |
570 | syslog(LOG_NOTICE,_("TOO MANY LOGIN TRIES (%d) FROM %s FOR " |
571 | "%s, %s"), failcount, hostname, username, | |
2b6fc908 | 572 | pam_strerror(pamh, retcode)); |
fd6b7a7f | 573 | else |
7eda085c | 574 | syslog(LOG_NOTICE,_("FAILED LOGIN SESSION FROM %s FOR %s, %s"), |
2b6fc908 | 575 | hostname, username, pam_strerror(pamh, retcode)); |
364cda48 | 576 | logbtmp(tty_name, username, hostname); |
f8bdba2f | 577 | logaudit(tty_name, username, hostname, NULL, 0); |
6dbe3af9 | 578 | |
7eda085c | 579 | fprintf(stderr,_("\nLogin incorrect\n")); |
fd6b7a7f | 580 | pam_end(pamh, retcode); |
66020e56 | 581 | exit(EXIT_SUCCESS); |
fd6b7a7f | 582 | } |
987195ce | 583 | } |
726f69e2 | 584 | |
987195ce KZ |
585 | /* |
586 | * Authentication may be skipped (for example, during krlogin, rlogin, etc...), | |
587 | * but it doesn't mean that we can skip other account checks. The account | |
588 | * could be disabled or password expired (althought kerberos ticket is valid). | |
589 | * -- kzak@redhat.com (22-Feb-2006) | |
590 | */ | |
591 | retcode = pam_acct_mgmt(pamh, 0); | |
6dbe3af9 | 592 | |
905045d4 | 593 | if (retcode == PAM_NEW_AUTHTOK_REQD) |
987195ce | 594 | retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); |
905045d4 KZ |
595 | if (is_pam_failure(retcode)) |
596 | loginpam_err(pamh, retcode); | |
987195ce | 597 | |
1d4ad1de KZ |
598 | /* |
599 | * Grab the user information out of the password file for future usage | |
600 | * First get the username that we are actually using, though. | |
601 | */ | |
905045d4 KZ |
602 | retcode = loginpam_get_username(pamh, &username); |
603 | if (is_pam_failure(retcode)) | |
604 | loginpam_err(pamh, retcode); | |
e8f26419 KZ |
605 | |
606 | if (!username || !*username) { | |
66020e56 | 607 | warnx(_("\nSession setup problem, abort.")); |
e8f26419 KZ |
608 | syslog(LOG_ERR, _("NULL user name in %s:%d. Abort."), |
609 | __FUNCTION__, __LINE__); | |
610 | pam_end(pamh, PAM_SYSTEM_ERR); | |
66020e56 | 611 | exit(EXIT_FAILURE); |
e8f26419 KZ |
612 | } |
613 | if (!(pwd = getpwnam(username))) { | |
66020e56 | 614 | warnx(_("\nSession setup problem, abort.")); |
e8f26419 | 615 | syslog(LOG_ERR, _("Invalid user name \"%s\" in %s:%d. Abort."), |
612721db | 616 | username, __FUNCTION__, __LINE__); |
e8f26419 | 617 | pam_end(pamh, PAM_SYSTEM_ERR); |
66020e56 | 618 | exit(EXIT_FAILURE); |
e8f26419 KZ |
619 | } |
620 | ||
1d4ad1de KZ |
621 | /* |
622 | * Create a copy of the pwd struct - otherwise it may get | |
623 | * clobbered by PAM | |
624 | */ | |
e8f26419 KZ |
625 | memcpy(&pwdcopy, pwd, sizeof(*pwd)); |
626 | pwd = &pwdcopy; | |
627 | pwd->pw_name = strdup(pwd->pw_name); | |
628 | pwd->pw_passwd = strdup(pwd->pw_passwd); | |
629 | pwd->pw_gecos = strdup(pwd->pw_gecos); | |
630 | pwd->pw_dir = strdup(pwd->pw_dir); | |
631 | pwd->pw_shell = strdup(pwd->pw_shell); | |
632 | if (!pwd->pw_name || !pwd->pw_passwd || !pwd->pw_gecos || | |
633 | !pwd->pw_dir || !pwd->pw_shell) { | |
66020e56 | 634 | warnx(_("out of memory")); |
e8f26419 KZ |
635 | syslog(LOG_ERR, "Out of memory"); |
636 | pam_end(pamh, PAM_SYSTEM_ERR); | |
66020e56 | 637 | exit(EXIT_FAILURE); |
c07ebfa1 | 638 | } |
e8f26419 | 639 | username = pwd->pw_name; |
c07ebfa1 KZ |
640 | |
641 | /* | |
642 | * Initialize the supplementary group list. | |
643 | * This should be done before pam_setcred because | |
644 | * the PAM modules might add groups during pam_setcred. | |
645 | */ | |
e8f26419 KZ |
646 | if (initgroups(username, pwd->pw_gid) < 0) { |
647 | syslog(LOG_ERR, "initgroups: %m"); | |
66020e56 | 648 | warnx(_("\nSession setup problem, abort.")); |
e8f26419 | 649 | pam_end(pamh, PAM_SYSTEM_ERR); |
66020e56 | 650 | exit(EXIT_FAILURE); |
e8f26419 | 651 | } |
6dbe3af9 | 652 | |
c07ebfa1 | 653 | retcode = pam_open_session(pamh, 0); |
905045d4 KZ |
654 | if (is_pam_failure(retcode)) |
655 | loginpam_err(pamh, retcode); | |
6dbe3af9 | 656 | |
c07ebfa1 | 657 | retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); |
905045d4 | 658 | if (is_pam_failure(retcode)) { |
f2c7ae1d | 659 | pam_close_session(pamh, 0); |
905045d4 KZ |
660 | loginpam_err(pamh, retcode); |
661 | } | |
6dbe3af9 | 662 | |
fd6b7a7f KZ |
663 | /* committed to login -- turn off timeout */ |
664 | alarm((unsigned int)0); | |
e09947b5 | 665 | |
fd6b7a7f | 666 | endpwent(); |
e09947b5 | 667 | |
fd6b7a7f KZ |
668 | /* This requires some explanation: As root we may not be able to |
669 | read the directory of the user if it is on an NFS mounted | |
670 | filesystem. We temporarily set our effective uid to the user-uid | |
e09947b5 KZ |
671 | making sure that we keep root privs. in the real uid. |
672 | ||
fd6b7a7f KZ |
673 | A portable solution would require a fork(), but we rely on Linux |
674 | having the BSD setreuid() */ | |
e09947b5 | 675 | |
fd6b7a7f KZ |
676 | { |
677 | char tmpstr[MAXPATHLEN]; | |
678 | uid_t ruid = getuid(); | |
679 | gid_t egid = getegid(); | |
2b6fc908 KZ |
680 | |
681 | /* avoid snprintf - old systems do not have it, or worse, | |
682 | have a libc in which snprintf is the same as sprintf */ | |
683 | if (strlen(pwd->pw_dir) + sizeof(_PATH_HUSHLOGIN) + 2 > MAXPATHLEN) | |
684 | quietlog = 0; | |
685 | else { | |
686 | sprintf(tmpstr, "%s/%s", pwd->pw_dir, _PATH_HUSHLOGIN); | |
687 | setregid(-1, pwd->pw_gid); | |
688 | setreuid(0, pwd->pw_uid); | |
3c0e680c | 689 | quietlog = (effective_access(tmpstr, O_RDONLY) == 0); |
2b6fc908 KZ |
690 | setuid(0); /* setreuid doesn't do it alone! */ |
691 | setreuid(ruid, 0); | |
692 | setregid(-1, egid); | |
693 | } | |
fd6b7a7f | 694 | } |
e09947b5 | 695 | |
fd6b7a7f KZ |
696 | /* for linux, write entries in utmp and wtmp */ |
697 | { | |
698 | struct utmp ut; | |
fd6b7a7f | 699 | struct utmp *utp; |
75d4dbb0 | 700 | struct timeval tv; |
e09947b5 | 701 | |
fd6b7a7f KZ |
702 | utmpname(_PATH_UTMP); |
703 | setutent(); | |
5c36a0eb | 704 | |
1d4ad1de | 705 | /* Find pid in utmp. |
5c36a0eb KZ |
706 | login sometimes overwrites the runlevel entry in /var/run/utmp, |
707 | confusing sysvinit. I added a test for the entry type, and the problem | |
708 | was gone. (In a runlevel entry, st_pid is not really a pid but some number | |
709 | calculated from the previous and current runlevel). | |
710 | Michael Riepe <michael@stud.uni-hannover.de> | |
711 | */ | |
712 | while ((utp = getutent())) | |
1d4ad1de | 713 | if (utp->ut_pid == pid |
5c36a0eb KZ |
714 | && utp->ut_type >= INIT_PROCESS |
715 | && utp->ut_type <= DEAD_PROCESS) | |
716 | break; | |
7eda085c KZ |
717 | |
718 | /* If we can't find a pre-existing entry by pid, try by line. | |
719 | BSD network daemons may rely on this. (anonymous) */ | |
720 | if (utp == NULL) { | |
721 | setutent(); | |
722 | ut.ut_type = LOGIN_PROCESS; | |
364cda48 | 723 | strncpy(ut.ut_line, tty_name, sizeof(ut.ut_line)); |
d03dd608 | 724 | utp = getutline(&ut); |
7eda085c | 725 | } |
e09947b5 | 726 | |
fd6b7a7f KZ |
727 | if (utp) { |
728 | memcpy(&ut, utp, sizeof(ut)); | |
729 | } else { | |
730 | /* some gettys/telnetds don't initialize utmp... */ | |
731 | memset(&ut, 0, sizeof(ut)); | |
6dbe3af9 | 732 | } |
e09947b5 | 733 | |
fd6b7a7f | 734 | if (ut.ut_id[0] == 0) |
364cda48 | 735 | strncpy(ut.ut_id, tty_number, sizeof(ut.ut_id)); |
e09947b5 | 736 | |
fd6b7a7f | 737 | strncpy(ut.ut_user, username, sizeof(ut.ut_user)); |
364cda48 | 738 | xstrncpy(ut.ut_line, tty_name, sizeof(ut.ut_line)); |
7eda085c | 739 | #ifdef _HAVE_UT_TV /* in <utmpbits.h> included by <utmp.h> */ |
75d4dbb0 KZ |
740 | gettimeofday(&tv, NULL); |
741 | ut.ut_tv.tv_sec = tv.tv_sec; | |
742 | ut.ut_tv.tv_usec = tv.tv_usec; | |
7eda085c KZ |
743 | #else |
744 | { | |
364cda48 KZ |
745 | time_t t; |
746 | time(&t); | |
747 | ut.ut_time = t; /* ut_time is not always a time_t */ | |
7eda085c KZ |
748 | /* glibc2 #defines it as ut_tv.tv_sec */ |
749 | } | |
750 | #endif | |
fd6b7a7f | 751 | ut.ut_type = USER_PROCESS; |
1d4ad1de | 752 | ut.ut_pid = pid; |
fd6b7a7f | 753 | if (hostname) { |
364cda48 KZ |
754 | xstrncpy(ut.ut_host, hostname, sizeof(ut.ut_host)); |
755 | if (hostaddress[0]) | |
ea6c190a | 756 | memcpy(&ut.ut_addr_v6, hostaddress, sizeof(ut.ut_addr_v6)); |
6dbe3af9 | 757 | } |
e09947b5 | 758 | |
fd6b7a7f KZ |
759 | pututline(&ut); |
760 | endutent(); | |
5c36a0eb | 761 | |
48d7b13a | 762 | #if HAVE_UPDWTMP |
5c36a0eb | 763 | updwtmp(_PATH_WTMP, &ut); |
5c36a0eb KZ |
764 | #else |
765 | /* Probably all this locking below is just nonsense, | |
766 | and the short version is OK as well. */ | |
e09947b5 | 767 | { |
22853e4a | 768 | int lf, wtmp; |
fd6b7a7f KZ |
769 | if ((lf = open(_PATH_WTMPLOCK, O_CREAT|O_WRONLY, 0660)) >= 0) { |
770 | flock(lf, LOCK_EX); | |
771 | if ((wtmp = open(_PATH_WTMP, O_APPEND|O_WRONLY)) >= 0) { | |
772 | write(wtmp, (char *)&ut, sizeof(ut)); | |
773 | close(wtmp); | |
774 | } | |
775 | flock(lf, LOCK_UN); | |
776 | close(lf); | |
777 | } | |
778 | } | |
c07ebfa1 | 779 | #endif |
5c36a0eb | 780 | } |
e09947b5 | 781 | |
f8bdba2f | 782 | logaudit(tty_name, username, hostname, pwd, 1); |
fd6b7a7f | 783 | dolastlog(quietlog); |
e09947b5 | 784 | |
11784a84 KZ |
785 | if (fchown(0, pwd->pw_uid, |
786 | (gr = getgrnam(TTYGRPNAME)) ? gr->gr_gid : pwd->pw_gid)) | |
787 | warn(_("change terminal owner failed")); | |
788 | ||
453b3614 | 789 | fchmod(0, TTY_MODE); |
fd6b7a7f | 790 | |
48d7b13a | 791 | #ifdef LOGIN_CHOWN_VCS |
e09947b5 | 792 | /* if tty is one of the VC's then change owner and mode of the |
fd6b7a7f KZ |
793 | special /dev/vcs devices as well */ |
794 | if (consoletty(0)) { | |
11784a84 KZ |
795 | |
796 | if (chown(vcsn, pwd->pw_uid, (gr ? gr->gr_gid : pwd->pw_gid))) | |
797 | warn(_("change terminal owner failed")); | |
798 | if (chown(vcsan, pwd->pw_uid, (gr ? gr->gr_gid : pwd->pw_gid))) | |
799 | warn(_("change terminal owner failed")); | |
800 | ||
fd6b7a7f KZ |
801 | chmod(vcsn, TTY_MODE); |
802 | chmod(vcsan, TTY_MODE); | |
803 | } | |
66ee8158 | 804 | #endif |
fd6b7a7f | 805 | |
81e9987b KZ |
806 | if (setgid(pwd->pw_gid) < 0 && pwd->pw_gid) { |
807 | syslog(LOG_ALERT, _("setgid() failed")); | |
808 | exit(EXIT_FAILURE); | |
809 | } | |
810 | ||
e09947b5 | 811 | |
fd6b7a7f KZ |
812 | if (*pwd->pw_shell == '\0') |
813 | pwd->pw_shell = _PATH_BSHELL; | |
e09947b5 | 814 | |
fd6b7a7f KZ |
815 | /* preserve TERM even without -p flag */ |
816 | { | |
817 | char *ep; | |
e09947b5 | 818 | |
fd6b7a7f KZ |
819 | if(!((ep = getenv("TERM")) && (termenv = strdup(ep)))) |
820 | termenv = "dumb"; | |
821 | } | |
e09947b5 | 822 | |
fd6b7a7f KZ |
823 | /* destroy environment unless user has requested preservation */ |
824 | if (!pflag) | |
825 | { | |
6dbe3af9 KZ |
826 | environ = (char**)malloc(sizeof(char*)); |
827 | memset(environ, 0, sizeof(char*)); | |
fd6b7a7f | 828 | } |
e09947b5 | 829 | |
fd6b7a7f KZ |
830 | setenv("HOME", pwd->pw_dir, 0); /* legal to override */ |
831 | if(pwd->pw_uid) | |
832 | setenv("PATH", _PATH_DEFPATH, 1); | |
833 | else | |
834 | setenv("PATH", _PATH_DEFPATH_ROOT, 1); | |
e09947b5 | 835 | |
fd6b7a7f KZ |
836 | setenv("SHELL", pwd->pw_shell, 1); |
837 | setenv("TERM", termenv, 1); | |
e09947b5 | 838 | |
fd6b7a7f | 839 | /* mailx will give a funny error msg if you forget this one */ |
5c36a0eb KZ |
840 | { |
841 | char tmp[MAXPATHLEN]; | |
2b6fc908 KZ |
842 | /* avoid snprintf */ |
843 | if (sizeof(_PATH_MAILDIR) + strlen(pwd->pw_name) + 1 < MAXPATHLEN) { | |
844 | sprintf(tmp, "%s/%s", _PATH_MAILDIR, pwd->pw_name); | |
845 | setenv("MAIL",tmp,0); | |
846 | } | |
847 | } | |
e09947b5 | 848 | |
fd6b7a7f KZ |
849 | /* LOGNAME is not documented in login(1) but |
850 | HP-UX 6.5 does it. We'll not allow modifying it. | |
851 | */ | |
852 | setenv("LOGNAME", pwd->pw_name, 1); | |
6dbe3af9 | 853 | |
fd6b7a7f KZ |
854 | { |
855 | int i; | |
364cda48 | 856 | char ** env = pam_getenvlist(pamh); |
6dbe3af9 | 857 | |
fd6b7a7f | 858 | if (env != NULL) { |
364cda48 KZ |
859 | for (i=0; env[i]; i++) { |
860 | putenv(env[i]); | |
861 | /* D(("env[%d] = %s", i,env[i])); */ | |
fd6b7a7f | 862 | } |
6dbe3af9 | 863 | } |
fd6b7a7f | 864 | } |
5c36a0eb | 865 | |
5c36a0eb | 866 | setproctitle("login", username); |
e09947b5 | 867 | |
364cda48 KZ |
868 | if (!strncmp(tty_name, "ttyS", 4)) |
869 | syslog(LOG_INFO, _("DIALUP AT %s BY %s"), tty_name, pwd->pw_name); | |
e09947b5 | 870 | |
fd6b7a7f KZ |
871 | /* allow tracking of good logins. |
872 | -steve philp (sphilp@mail.alliance.net) */ | |
e09947b5 | 873 | |
fd6b7a7f KZ |
874 | if (pwd->pw_uid == 0) { |
875 | if (hostname) | |
7eda085c | 876 | syslog(LOG_NOTICE, _("ROOT LOGIN ON %s FROM %s"), |
364cda48 | 877 | tty_name, hostname); |
fd6b7a7f | 878 | else |
364cda48 | 879 | syslog(LOG_NOTICE, _("ROOT LOGIN ON %s"), tty_name); |
fd6b7a7f | 880 | } else { |
e09947b5 KZ |
881 | if (hostname) |
882 | syslog(LOG_INFO, _("LOGIN ON %s BY %s FROM %s"), tty_name, | |
fd6b7a7f | 883 | pwd->pw_name, hostname); |
e09947b5 KZ |
884 | else |
885 | syslog(LOG_INFO, _("LOGIN ON %s BY %s"), tty_name, | |
fd6b7a7f KZ |
886 | pwd->pw_name); |
887 | } | |
e09947b5 | 888 | |
fd6b7a7f | 889 | if (!quietlog) { |
fd6b7a7f | 890 | motd(); |
d162fcb5 | 891 | |
48d7b13a | 892 | #ifdef LOGIN_STAT_MAIL |
d162fcb5 KZ |
893 | /* |
894 | * This turns out to be a bad idea: when the mail spool | |
895 | * is NFS mounted, and the NFS connection hangs, the | |
896 | * login hangs, even root cannot login. | |
897 | * Checking for mail should be done from the shell. | |
898 | */ | |
899 | { | |
900 | struct stat st; | |
901 | char *mail; | |
e09947b5 | 902 | |
d162fcb5 KZ |
903 | mail = getenv("MAIL"); |
904 | if (mail && stat(mail, &st) == 0 && st.st_size != 0) { | |
e8f26419 KZ |
905 | if (st.st_mtime > st.st_atime) |
906 | printf(_("You have new mail.\n")); | |
907 | else | |
908 | printf(_("You have mail.\n")); | |
d162fcb5 | 909 | } |
2b6fc908 | 910 | } |
d162fcb5 | 911 | #endif |
fd6b7a7f | 912 | } |
e09947b5 | 913 | |
fd6b7a7f KZ |
914 | signal(SIGALRM, SIG_DFL); |
915 | signal(SIGQUIT, SIG_DFL); | |
fd6b7a7f | 916 | signal(SIGTSTP, SIG_IGN); |
5c36a0eb | 917 | |
b2452358 KZ |
918 | memset(&sa, 0, sizeof(sa)); |
919 | sa.sa_handler = SIG_IGN; | |
920 | sigaction(SIGINT, &sa, NULL); | |
921 | ||
922 | sigaction(SIGHUP, &sa, &oldsa_hup); /* ignore when TIOCNOTTY */ | |
923 | ||
924 | /* | |
925 | * detach the controlling tty | |
926 | * -- we needn't the tty in parent who waits for child only. | |
927 | * The child calls setsid() that detach from the tty as well. | |
928 | */ | |
929 | ioctl(0, TIOCNOTTY, NULL); | |
930 | ||
931 | /* | |
932 | * We have care about SIGTERM, because leave PAM session without | |
933 | * pam_close_session() is pretty bad thing. | |
934 | */ | |
935 | sa.sa_handler = sig_handler; | |
936 | sigaction(SIGHUP, &sa, NULL); | |
937 | sigaction(SIGTERM, &sa, &oldsa_term); | |
938 | ||
939 | closelog(); | |
940 | ||
c07ebfa1 KZ |
941 | /* |
942 | * We must fork before setuid() because we need to call | |
5c36a0eb KZ |
943 | * pam_close_session() as root. |
944 | */ | |
e09947b5 | 945 | |
b2452358 KZ |
946 | child_pid = fork(); |
947 | if (child_pid < 0) { | |
5c36a0eb | 948 | /* error in fork() */ |
66020e56 | 949 | warn(_("failure forking")); |
905045d4 KZ |
950 | pam_setcred(pamh, PAM_DELETE_CRED); |
951 | pam_end(pamh, pam_close_session(pamh, 0)); | |
66020e56 | 952 | exit(EXIT_FAILURE); |
1d4ad1de KZ |
953 | } |
954 | ||
b2452358 | 955 | if (child_pid) { |
5c36a0eb | 956 | /* parent - wait for child to finish, then cleanup session */ |
b2452358 KZ |
957 | close(0); |
958 | close(1); | |
959 | close(2); | |
960 | sa.sa_handler = SIG_IGN; | |
961 | sigaction(SIGQUIT, &sa, NULL); | |
962 | sigaction(SIGINT, &sa, NULL); | |
963 | ||
964 | /* wait as long as any child is there */ | |
965 | while(wait(NULL) == -1 && errno == EINTR) | |
966 | ; | |
17274233 | 967 | openlog("login", LOG_ODELAY, LOG_AUTHPRIV); |
905045d4 KZ |
968 | pam_setcred(pamh, PAM_DELETE_CRED); |
969 | pam_end(pamh, pam_close_session(pamh, 0)); | |
66020e56 | 970 | exit(EXIT_SUCCESS); |
5c36a0eb | 971 | } |
1d4ad1de | 972 | |
5c36a0eb | 973 | /* child */ |
b2452358 KZ |
974 | |
975 | /* restore to old state */ | |
976 | sigaction(SIGHUP, &oldsa_hup, NULL); | |
977 | sigaction(SIGTERM, &oldsa_term, NULL); | |
978 | if(got_sig) | |
66020e56 | 979 | exit(EXIT_FAILURE); |
b2452358 | 980 | |
1d4ad1de KZ |
981 | /* |
982 | * Problem: if the user's shell is a shell like ash that doesnt do | |
983 | * setsid() or setpgrp(), then a ctrl-\, sending SIGQUIT to every | |
984 | * process in the pgrp, will kill us. | |
985 | */ | |
986 | ||
987 | /* start new session */ | |
c07ebfa1 | 988 | setsid(); |
1d4ad1de KZ |
989 | |
990 | /* make sure we have a controlling tty */ | |
364cda48 | 991 | opentty(ttyn); |
1d4ad1de KZ |
992 | openlog("login", LOG_ODELAY, LOG_AUTHPRIV); /* reopen */ |
993 | ||
994 | /* | |
995 | * TIOCSCTTY: steal tty from other process group. | |
996 | */ | |
997 | if (ioctl(0, TIOCSCTTY, 1)) | |
998 | syslog(LOG_ERR, _("TIOCSCTTY failed: %m")); | |
5c36a0eb | 999 | signal(SIGINT, SIG_DFL); |
e09947b5 | 1000 | |
fd6b7a7f KZ |
1001 | /* discard permissions last so can't get killed and drop core */ |
1002 | if(setuid(pwd->pw_uid) < 0 && pwd->pw_uid) { | |
7eda085c | 1003 | syslog(LOG_ALERT, _("setuid() failed")); |
66020e56 | 1004 | exit(EXIT_FAILURE); |
fd6b7a7f | 1005 | } |
e09947b5 | 1006 | |
fd6b7a7f KZ |
1007 | /* wait until here to change directory! */ |
1008 | if (chdir(pwd->pw_dir) < 0) { | |
66020e56 | 1009 | warn(_("%s: change directory failed"), pwd->pw_dir); |
5c36a0eb | 1010 | if (chdir("/")) |
66020e56 | 1011 | exit(EXIT_FAILURE); |
fd6b7a7f | 1012 | pwd->pw_dir = "/"; |
7eda085c | 1013 | printf(_("Logging in with home = \"/\".\n")); |
fd6b7a7f | 1014 | } |
e09947b5 | 1015 | |
fd6b7a7f KZ |
1016 | /* if the shell field has a space: treat it like a shell script */ |
1017 | if (strchr(pwd->pw_shell, ' ')) { | |
3e31a2df | 1018 | buff = xmalloc(strlen(pwd->pw_shell) + 6); |
6dbe3af9 | 1019 | |
fd6b7a7f KZ |
1020 | strcpy(buff, "exec "); |
1021 | strcat(buff, pwd->pw_shell); | |
1022 | childArgv[childArgc++] = "/bin/sh"; | |
1023 | childArgv[childArgc++] = "-sh"; | |
1024 | childArgv[childArgc++] = "-c"; | |
1025 | childArgv[childArgc++] = buff; | |
1026 | } else { | |
6dbe3af9 | 1027 | tbuf[0] = '-'; |
c0f19ccf | 1028 | xstrncpy(tbuf + 1, ((p = strrchr(pwd->pw_shell, '/')) ? |
fd6b7a7f KZ |
1029 | p + 1 : pwd->pw_shell), |
1030 | sizeof(tbuf)-1); | |
e09947b5 | 1031 | |
fd6b7a7f KZ |
1032 | childArgv[childArgc++] = pwd->pw_shell; |
1033 | childArgv[childArgc++] = tbuf; | |
1034 | } | |
1035 | ||
1036 | childArgv[childArgc++] = NULL; | |
1037 | ||
2b6fc908 KZ |
1038 | execvp(childArgv[0], childArgv + 1); |
1039 | ||
7eda085c | 1040 | if (!strcmp(childArgv[0], "/bin/sh")) |
66020e56 | 1041 | warn(_("couldn't exec shell script")); |
2b6fc908 | 1042 | else |
66020e56 | 1043 | warn(_("no shell")); |
fd6b7a7f | 1044 | |
66020e56 | 1045 | exit(EXIT_SUCCESS); |
6dbe3af9 KZ |
1046 | } |
1047 | ||
df1dddf9 KZ |
1048 | /* |
1049 | * Robert Ambrose writes: | |
1050 | * A couple of my users have a problem with login processes hanging around | |
1051 | * soaking up pts's. What they seem to hung up on is trying to write out the | |
1052 | * message 'Login timed out after %d seconds' when the connection has already | |
1053 | * been dropped. | |
1054 | * What I did was add a second timeout while trying to write the message so | |
1055 | * the process just exits if the second timeout expires. | |
1056 | */ | |
1057 | ||
1058 | static void | |
878a3697 | 1059 | timedout2(int sig __attribute__((__unused__))) { |
ad3a3bfc | 1060 | struct termios ti; |
e09947b5 | 1061 | |
df1dddf9 | 1062 | /* reset echo */ |
ad3a3bfc | 1063 | tcgetattr(0, &ti); |
df1dddf9 | 1064 | ti.c_lflag |= ECHO; |
ad3a3bfc | 1065 | tcsetattr(0, TCSANOW, &ti); |
66020e56 | 1066 | exit(EXIT_SUCCESS); /* %% */ |
df1dddf9 KZ |
1067 | } |
1068 | ||
1069 | static void | |
878a3697 | 1070 | timedout(int sig __attribute__((__unused__))) { |
df1dddf9 KZ |
1071 | signal(SIGALRM, timedout2); |
1072 | alarm(10); | |
f14f02f5 | 1073 | /* TRANSLATORS: The standard value for %d is 60. */ |
66020e56 | 1074 | warnx(_("timed out after %d seconds"), timeout); |
df1dddf9 KZ |
1075 | signal(SIGALRM, SIG_IGN); |
1076 | alarm(0); | |
1077 | timedout2(0); | |
6dbe3af9 KZ |
1078 | } |
1079 | ||
6dbe3af9 KZ |
1080 | jmp_buf motdinterrupt; |
1081 | ||
1082 | void | |
66ee8158 KZ |
1083 | motd(void) { |
1084 | int fd, nchars; | |
1085 | void (*oldint)(int); | |
fd6b7a7f | 1086 | char tbuf[8192]; |
e09947b5 | 1087 | |
fd6b7a7f KZ |
1088 | if ((fd = open(_PATH_MOTDFILE, O_RDONLY, 0)) < 0) |
1089 | return; | |
1090 | oldint = signal(SIGINT, sigint); | |
1091 | if (setjmp(motdinterrupt) == 0) | |
11784a84 KZ |
1092 | while ((nchars = read(fd, tbuf, sizeof(tbuf))) > 0) { |
1093 | if (write(fileno(stdout), tbuf, nchars)) { | |
1094 | ; /* glibc warn_unused_result */ | |
1095 | } | |
1096 | } | |
fd6b7a7f KZ |
1097 | signal(SIGINT, oldint); |
1098 | close(fd); | |
6dbe3af9 KZ |
1099 | } |
1100 | ||
fd6b7a7f | 1101 | void |
878a3697 | 1102 | sigint(int sig __attribute__((__unused__))) { |
fd6b7a7f | 1103 | longjmp(motdinterrupt, 1); |
6dbe3af9 KZ |
1104 | } |
1105 | ||
6dbe3af9 | 1106 | void |
66ee8158 | 1107 | dolastlog(int quiet) { |
fd6b7a7f KZ |
1108 | struct lastlog ll; |
1109 | int fd; | |
e09947b5 | 1110 | |
fd6b7a7f | 1111 | if ((fd = open(_PATH_LASTLOG, O_RDWR, 0)) >= 0) { |
5c36a0eb | 1112 | lseek(fd, (off_t)pwd->pw_uid * sizeof(ll), SEEK_SET); |
fd6b7a7f KZ |
1113 | if (!quiet) { |
1114 | if (read(fd, (char *)&ll, sizeof(ll)) == sizeof(ll) && | |
1115 | ll.ll_time != 0) { | |
d03dd608 KZ |
1116 | time_t ll_time = (time_t) ll.ll_time; |
1117 | ||
1118 | printf(_("Last login: %.*s "), | |
1119 | 24-5, ctime(&ll_time)); | |
e09947b5 | 1120 | |
d03dd608 KZ |
1121 | if (*ll.ll_host != '\0') |
1122 | printf(_("from %.*s\n"), | |
1123 | (int)sizeof(ll.ll_host), ll.ll_host); | |
1124 | else | |
1125 | printf(_("on %.*s\n"), | |
1126 | (int)sizeof(ll.ll_line), ll.ll_line); | |
fd6b7a7f | 1127 | } |
5c36a0eb | 1128 | lseek(fd, (off_t)pwd->pw_uid * sizeof(ll), SEEK_SET); |
6dbe3af9 | 1129 | } |
fd6b7a7f | 1130 | memset((char *)&ll, 0, sizeof(ll)); |
a063e16c KZ |
1131 | |
1132 | { | |
1133 | time_t t; | |
1134 | time(&t); | |
1135 | ll.ll_time = t; /* ll_time is always 32bit */ | |
1136 | } | |
1137 | ||
364cda48 | 1138 | xstrncpy(ll.ll_line, tty_name, sizeof(ll.ll_line)); |
c07ebfa1 KZ |
1139 | if (hostname) |
1140 | xstrncpy(ll.ll_host, hostname, sizeof(ll.ll_host)); | |
1141 | ||
11784a84 KZ |
1142 | if (write(fd, (char *)&ll, sizeof(ll)) < 0) |
1143 | warn(_("write lastlog failed")); | |
fd6b7a7f KZ |
1144 | close(fd); |
1145 | } | |
6dbe3af9 KZ |
1146 | } |
1147 | ||
1148 | void | |
364cda48 | 1149 | badlogin(const char *name) { |
7eda085c KZ |
1150 | if (failures == 1) { |
1151 | if (hostname) | |
1152 | syslog(LOG_NOTICE, _("LOGIN FAILURE FROM %s, %s"), | |
1153 | hostname, name); | |
1154 | else | |
1155 | syslog(LOG_NOTICE, _("LOGIN FAILURE ON %s, %s"), | |
364cda48 | 1156 | tty_name, name); |
7eda085c KZ |
1157 | } else { |
1158 | if (hostname) | |
1159 | syslog(LOG_NOTICE, _("%d LOGIN FAILURES FROM %s, %s"), | |
1160 | failures, hostname, name); | |
1161 | else | |
1162 | syslog(LOG_NOTICE, _("%d LOGIN FAILURES ON %s, %s"), | |
364cda48 | 1163 | failures, tty_name, name); |
7eda085c | 1164 | } |
6dbe3af9 KZ |
1165 | } |
1166 | ||
e8f26419 | 1167 | /* Should not be called from PAM code... */ |
6dbe3af9 | 1168 | void |
66ee8158 | 1169 | sleepexit(int eval) { |
fd6b7a7f KZ |
1170 | sleep(SLEEP_EXIT_TIMEOUT); |
1171 | exit(eval); | |
6dbe3af9 | 1172 | } |