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