| 1 |
/*- |
| 2 |
* Copyright (c) 1980, 1987, 1988, 1991, 1993, 1994 |
| 3 |
* The Regents of the University of California. All rights reserved. |
| 4 |
* Copyright (c) 2002 Networks Associates Technologies, Inc. |
| 5 |
* All rights reserved. |
| 6 |
* |
| 7 |
* Portions of this software were developed for the FreeBSD Project by |
| 8 |
* ThinkSec AS and NAI Labs, the Security Research Division of Network |
| 9 |
* Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 |
| 10 |
* ("CBOSS"), as part of the DARPA CHATS research program. |
| 11 |
* |
| 12 |
* Redistribution and use in source and binary forms, with or without |
| 13 |
* modification, are permitted provided that the following conditions |
| 14 |
* are met: |
| 15 |
* 1. Redistributions of source code must retain the above copyright |
| 16 |
* notice, this list of conditions and the following disclaimer. |
| 17 |
* 2. Redistributions in binary form must reproduce the above copyright |
| 18 |
* notice, this list of conditions and the following disclaimer in the |
| 19 |
* documentation and/or other materials provided with the distribution. |
| 20 |
* 3. All advertising materials mentioning features or use of this software |
| 21 |
* must display the following acknowledgement: |
| 22 |
* This product includes software developed by the University of |
| 23 |
* California, Berkeley and its contributors. |
| 24 |
* 4. Neither the name of the University nor the names of its contributors |
| 25 |
* may be used to endorse or promote products derived from this software |
| 26 |
* without specific prior written permission. |
| 27 |
* |
| 28 |
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| 29 |
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 30 |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 31 |
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| 32 |
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 33 |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| 34 |
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| 35 |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| 36 |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| 37 |
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| 38 |
* SUCH DAMAGE. |
| 39 |
*/ |
| 40 |
|
| 41 |
#if 0 |
| 42 |
#ifndef lint |
| 43 |
static char sccsid[] = "@(#)login.c 8.4 (Berkeley) 4/2/94"; |
| 44 |
#endif |
| 45 |
#endif |
| 46 |
|
| 47 |
#include <sys/cdefs.h> |
| 48 |
__FBSDID("$FreeBSD$"); |
| 49 |
|
| 50 |
/* |
| 51 |
* login [ name ] |
| 52 |
* login -h hostname (for telnetd, etc.) |
| 53 |
* login -f name (for pre-authenticated login: datakit, xterm, etc.) |
| 54 |
*/ |
| 55 |
|
| 56 |
#include <sys/param.h> |
| 57 |
#include <sys/file.h> |
| 58 |
#include <sys/stat.h> |
| 59 |
#include <sys/time.h> |
| 60 |
#include <sys/resource.h> |
| 61 |
#include <sys/wait.h> |
| 62 |
|
| 63 |
#include <err.h> |
| 64 |
#include <errno.h> |
| 65 |
#include <grp.h> |
| 66 |
#include <login_cap.h> |
| 67 |
#include <pwd.h> |
| 68 |
#include <setjmp.h> |
| 69 |
#include <signal.h> |
| 70 |
#include <stdio.h> |
| 71 |
#include <stdlib.h> |
| 72 |
#include <string.h> |
| 73 |
#include <syslog.h> |
| 74 |
#include <ttyent.h> |
| 75 |
#include <unistd.h> |
| 76 |
|
| 77 |
#include <security/pam_appl.h> |
| 78 |
#include <security/openpam.h> |
| 79 |
|
| 80 |
#include "login.h" |
| 81 |
#include "pathnames.h" |
| 82 |
|
| 83 |
static int auth_pam(void); |
| 84 |
static void bail(int, int); |
| 85 |
static void bail_internal(int, int, int); |
| 86 |
static int export(const char *); |
| 87 |
static void export_pam_environment(void); |
| 88 |
static int motd(const char *); |
| 89 |
static void badlogin(char *); |
| 90 |
static char *getloginname(void); |
| 91 |
static void pam_syslog(const char *); |
| 92 |
static void pam_cleanup(void); |
| 93 |
static void refused(const char *, const char *, int); |
| 94 |
static const char *stypeof(char *); |
| 95 |
static void sigint(int); |
| 96 |
static void timedout(int); |
| 97 |
static void bail_sig(int); |
| 98 |
static void usage(void); |
| 99 |
|
| 100 |
#define TTYGRPNAME "tty" /* group to own ttys */ |
| 101 |
#define DEFAULT_BACKOFF 3 |
| 102 |
#define DEFAULT_RETRIES 10 |
| 103 |
#define DEFAULT_PROMPT "login: " |
| 104 |
#define DEFAULT_PASSWD_PROMPT "Password:" |
| 105 |
#define TERM_UNKNOWN "su" |
| 106 |
#define DEFAULT_WARN (2L * 7L * 86400L) /* Two weeks */ |
| 107 |
#define NO_SLEEP_EXIT 0 |
| 108 |
#define SLEEP_EXIT 5 |
| 109 |
|
| 110 |
/* |
| 111 |
* This bounds the time given to login. Not a define so it can |
| 112 |
* be patched on machines where it's too small. |
| 113 |
*/ |
| 114 |
static u_int timeout = 300; |
| 115 |
|
| 116 |
/* Buffer for signal handling of timeout */ |
| 117 |
static jmp_buf timeout_buf; |
| 118 |
|
| 119 |
struct passwd *pwd; |
| 120 |
static int failures; |
| 121 |
|
| 122 |
static char *envinit[1]; /* empty environment list */ |
| 123 |
|
| 124 |
/* |
| 125 |
* Command line flags and arguments |
| 126 |
*/ |
| 127 |
static int fflag; /* -f: do not perform authentication */ |
| 128 |
static int hflag; /* -h: login from remote host */ |
| 129 |
static char *hostname; /* hostname from command line */ |
| 130 |
static int pflag; /* -p: preserve environment */ |
| 131 |
|
| 132 |
/* |
| 133 |
* User name |
| 134 |
*/ |
| 135 |
static char *username; /* user name */ |
| 136 |
static char *olduser; /* previous user name */ |
| 137 |
|
| 138 |
/* |
| 139 |
* Prompts |
| 140 |
*/ |
| 141 |
static char default_prompt[] = DEFAULT_PROMPT; |
| 142 |
static const char *prompt; |
| 143 |
static char default_passwd_prompt[] = DEFAULT_PASSWD_PROMPT; |
| 144 |
static const char *passwd_prompt; |
| 145 |
|
| 146 |
static char *tty; |
| 147 |
|
| 148 |
/* |
| 149 |
* PAM data |
| 150 |
*/ |
| 151 |
static pam_handle_t *pamh = NULL; |
| 152 |
static struct pam_conv pamc = { openpam_ttyconv, NULL }; |
| 153 |
static int pam_err; |
| 154 |
static int pam_silent = PAM_SILENT; |
| 155 |
static int pam_cred_established; |
| 156 |
static int pam_session_established; |
| 157 |
|
| 158 |
int |
| 159 |
main(int argc, char *argv[]) |
| 160 |
{ |
| 161 |
struct group *gr; |
| 162 |
struct stat st; |
| 163 |
int retries, backoff; |
| 164 |
int ask, ch, cnt, quietlog, rootlogin, rval; |
| 165 |
uid_t uid, euid; |
| 166 |
gid_t egid; |
| 167 |
char *term; |
| 168 |
char *p, *ttyn; |
| 169 |
char tname[sizeof(_PATH_TTY) + 10]; |
| 170 |
char *arg0; |
| 171 |
const char *tp; |
| 172 |
const char *shell = NULL; |
| 173 |
login_cap_t *lc = NULL; |
| 174 |
login_cap_t *lc_user = NULL; |
| 175 |
pid_t pid; |
| 176 |
sigset_t mask, omask; |
| 177 |
struct sigaction sa; |
| 178 |
#ifdef USE_BSM_AUDIT |
| 179 |
char auditsuccess = 1; |
| 180 |
#endif |
| 181 |
|
| 182 |
sa.sa_flags = SA_RESTART; |
| 183 |
(void)sigfillset(&sa.sa_mask); |
| 184 |
sa.sa_handler = SIG_IGN; |
| 185 |
(void)sigaction(SIGQUIT, &sa, NULL); |
| 186 |
(void)sigaction(SIGINT, &sa, NULL); |
| 187 |
(void)sigaction(SIGHUP, &sa, NULL); |
| 188 |
if (setjmp(timeout_buf)) { |
| 189 |
if (failures) |
| 190 |
badlogin(username); |
| 191 |
(void)fprintf(stderr, "Login timed out after %d seconds\n", |
| 192 |
timeout); |
| 193 |
bail(NO_SLEEP_EXIT, 0); |
| 194 |
} |
| 195 |
sa.sa_handler = timedout; |
| 196 |
(void)sigaction(SIGALRM, &sa, NULL); |
| 197 |
(void)alarm(timeout); |
| 198 |
(void)setpriority(PRIO_PROCESS, 0, 0); |
| 199 |
|
| 200 |
openlog("login", 0, LOG_AUTH); |
| 201 |
|
| 202 |
uid = getuid(); |
| 203 |
euid = geteuid(); |
| 204 |
egid = getegid(); |
| 205 |
|
| 206 |
while ((ch = getopt(argc, argv, "fh:p")) != -1) |
| 207 |
switch (ch) { |
| 208 |
case 'f': |
| 209 |
fflag = 1; |
| 210 |
break; |
| 211 |
case 'h': |
| 212 |
if (uid != 0) |
| 213 |
errx(1, "-h option: %s", strerror(EPERM)); |
| 214 |
if (strlen(optarg) >= MAXHOSTNAMELEN) |
| 215 |
errx(1, "-h option: %s: exceeds maximum " |
| 216 |
"hostname size", optarg); |
| 217 |
hflag = 1; |
| 218 |
hostname = optarg; |
| 219 |
break; |
| 220 |
case 'p': |
| 221 |
pflag = 1; |
| 222 |
break; |
| 223 |
case '?': |
| 224 |
default: |
| 225 |
if (uid == 0) |
| 226 |
syslog(LOG_ERR, "invalid flag %c", ch); |
| 227 |
usage(); |
| 228 |
} |
| 229 |
argc -= optind; |
| 230 |
argv += optind; |
| 231 |
|
| 232 |
if (argc > 0) { |
| 233 |
username = strdup(*argv); |
| 234 |
if (username == NULL) |
| 235 |
err(1, "strdup()"); |
| 236 |
ask = 0; |
| 237 |
} else { |
| 238 |
ask = 1; |
| 239 |
} |
| 240 |
|
| 241 |
setproctitle("-%s", getprogname()); |
| 242 |
|
| 243 |
closefrom(3); |
| 244 |
|
| 245 |
/* |
| 246 |
* Get current TTY |
| 247 |
*/ |
| 248 |
ttyn = ttyname(STDIN_FILENO); |
| 249 |
if (ttyn == NULL || *ttyn == '\0') { |
| 250 |
(void)snprintf(tname, sizeof(tname), "%s??", _PATH_TTY); |
| 251 |
ttyn = tname; |
| 252 |
} |
| 253 |
if (strncmp(ttyn, _PATH_DEV, sizeof _PATH_DEV - 1) == 0) |
| 254 |
tty = ttyn + sizeof _PATH_DEV - 1; |
| 255 |
else |
| 256 |
tty = ttyn; |
| 257 |
|
| 258 |
/* |
| 259 |
* Get "login-retries" & "login-backoff" from default class |
| 260 |
*/ |
| 261 |
lc = login_getclass(NULL); |
| 262 |
prompt = login_getcapstr(lc, "login_prompt", |
| 263 |
default_prompt, default_prompt); |
| 264 |
passwd_prompt = login_getcapstr(lc, "passwd_prompt", |
| 265 |
default_passwd_prompt, default_passwd_prompt); |
| 266 |
retries = login_getcapnum(lc, "login-retries", |
| 267 |
DEFAULT_RETRIES, DEFAULT_RETRIES); |
| 268 |
backoff = login_getcapnum(lc, "login-backoff", |
| 269 |
DEFAULT_BACKOFF, DEFAULT_BACKOFF); |
| 270 |
login_close(lc); |
| 271 |
lc = NULL; |
| 272 |
|
| 273 |
/* |
| 274 |
* Try to authenticate the user until we succeed or time out. |
| 275 |
*/ |
| 276 |
for (cnt = 0;; ask = 1) { |
| 277 |
if (ask) { |
| 278 |
fflag = 0; |
| 279 |
if (olduser != NULL) |
| 280 |
free(olduser); |
| 281 |
olduser = username; |
| 282 |
username = getloginname(); |
| 283 |
} |
| 284 |
rootlogin = 0; |
| 285 |
|
| 286 |
/* |
| 287 |
* Note if trying multiple user names; log failures for |
| 288 |
* previous user name, but don't bother logging one failure |
| 289 |
* for nonexistent name (mistyped username). |
| 290 |
*/ |
| 291 |
if (failures && strcmp(olduser, username) != 0) { |
| 292 |
if (failures > (pwd ? 0 : 1)) |
| 293 |
badlogin(olduser); |
| 294 |
} |
| 295 |
|
| 296 |
/* |
| 297 |
* Load the PAM policy and set some variables |
| 298 |
*/ |
| 299 |
pam_err = pam_start("login", username, &pamc, &pamh); |
| 300 |
if (pam_err != PAM_SUCCESS) { |
| 301 |
pam_syslog("pam_start()"); |
| 302 |
#ifdef USE_BSM_AUDIT |
| 303 |
au_login_fail("PAM Error", 1); |
| 304 |
#endif |
| 305 |
bail(NO_SLEEP_EXIT, 1); |
| 306 |
} |
| 307 |
pam_err = pam_set_item(pamh, PAM_TTY, tty); |
| 308 |
if (pam_err != PAM_SUCCESS) { |
| 309 |
pam_syslog("pam_set_item(PAM_TTY)"); |
| 310 |
#ifdef USE_BSM_AUDIT |
| 311 |
au_login_fail("PAM Error", 1); |
| 312 |
#endif |
| 313 |
bail(NO_SLEEP_EXIT, 1); |
| 314 |
} |
| 315 |
pam_err = pam_set_item(pamh, PAM_RHOST, hostname); |
| 316 |
if (pam_err != PAM_SUCCESS) { |
| 317 |
pam_syslog("pam_set_item(PAM_RHOST)"); |
| 318 |
#ifdef USE_BSM_AUDIT |
| 319 |
au_login_fail("PAM Error", 1); |
| 320 |
#endif |
| 321 |
bail(NO_SLEEP_EXIT, 1); |
| 322 |
} |
| 323 |
|
| 324 |
pwd = getpwnam(username); |
| 325 |
if (pwd != NULL && pwd->pw_uid == 0) |
| 326 |
rootlogin = 1; |
| 327 |
|
| 328 |
/* |
| 329 |
* If the -f option was specified and the caller is |
| 330 |
* root or the caller isn't changing their uid, don't |
| 331 |
* authenticate. |
| 332 |
*/ |
| 333 |
if (pwd != NULL && fflag && |
| 334 |
(uid == (uid_t)0 || uid == (uid_t)pwd->pw_uid)) { |
| 335 |
/* already authenticated */ |
| 336 |
rval = 0; |
| 337 |
#ifdef USE_BSM_AUDIT |
| 338 |
auditsuccess = 0; /* opened a terminal window only */ |
| 339 |
#endif |
| 340 |
} else { |
| 341 |
fflag = 0; |
| 342 |
(void)setpriority(PRIO_PROCESS, 0, -4); |
| 343 |
rval = auth_pam(); |
| 344 |
(void)setpriority(PRIO_PROCESS, 0, 0); |
| 345 |
} |
| 346 |
|
| 347 |
if (pwd && rval == 0) |
| 348 |
break; |
| 349 |
|
| 350 |
pam_cleanup(); |
| 351 |
|
| 352 |
/* |
| 353 |
* We are not exiting here, but this corresponds to a failed |
| 354 |
* login event, so set exitstatus to 1. |
| 355 |
*/ |
| 356 |
#ifdef USE_BSM_AUDIT |
| 357 |
au_login_fail("Login incorrect", 1); |
| 358 |
#endif |
| 359 |
|
| 360 |
(void)printf("Login incorrect\n"); |
| 361 |
failures++; |
| 362 |
|
| 363 |
pwd = NULL; |
| 364 |
|
| 365 |
/* |
| 366 |
* Allow up to 'retry' (10) attempts, but start |
| 367 |
* backing off after 'backoff' (3) attempts. |
| 368 |
*/ |
| 369 |
if (++cnt > backoff) { |
| 370 |
if (cnt >= retries) { |
| 371 |
badlogin(username); |
| 372 |
bail(SLEEP_EXIT, 1); |
| 373 |
} |
| 374 |
sleep((u_int)((cnt - backoff) * 5)); |
| 375 |
} |
| 376 |
} |
| 377 |
|
| 378 |
/* committed to login -- turn off timeout */ |
| 379 |
(void)alarm((u_int)0); |
| 380 |
|
| 381 |
(void)sigemptyset(&mask); |
| 382 |
(void)sigaddset(&mask, SIGHUP); |
| 383 |
(void)sigaddset(&mask, SIGTERM); |
| 384 |
(void)sigprocmask(SIG_BLOCK, &mask, &omask); |
| 385 |
sa.sa_handler = bail_sig; |
| 386 |
(void)sigaction(SIGHUP, &sa, NULL); |
| 387 |
(void)sigaction(SIGTERM, &sa, NULL); |
| 388 |
|
| 389 |
endpwent(); |
| 390 |
|
| 391 |
#ifdef USE_BSM_AUDIT |
| 392 |
/* Audit successful login. */ |
| 393 |
if (auditsuccess) |
| 394 |
au_login_success(); |
| 395 |
#endif |
| 396 |
|
| 397 |
/* |
| 398 |
* This needs to happen before login_getpwclass to support |
| 399 |
* home directories on GSS-API authenticated NFS where the |
| 400 |
* kerberos credentials need to be saved so that the kernel |
| 401 |
* can authenticate to the NFS server. |
| 402 |
*/ |
| 403 |
pam_err = pam_setcred(pamh, pam_silent|PAM_ESTABLISH_CRED); |
| 404 |
if (pam_err != PAM_SUCCESS) { |
| 405 |
pam_syslog("pam_setcred()"); |
| 406 |
bail(NO_SLEEP_EXIT, 1); |
| 407 |
} |
| 408 |
pam_cred_established = 1; |
| 409 |
|
| 410 |
/* |
| 411 |
* Establish the login class. |
| 412 |
*/ |
| 413 |
lc = login_getpwclass(pwd); |
| 414 |
lc_user = login_getuserclass(pwd); |
| 415 |
|
| 416 |
if (!(quietlog = login_getcapbool(lc_user, "hushlogin", 0))) |
| 417 |
quietlog = login_getcapbool(lc, "hushlogin", 0); |
| 418 |
|
| 419 |
/* |
| 420 |
* Switching needed for NFS with root access disabled. |
| 421 |
* |
| 422 |
* XXX: This change fails to modify the additional groups for the |
| 423 |
* process, and as such, may restrict rights normally granted |
| 424 |
* through those groups. |
| 425 |
*/ |
| 426 |
(void)setegid(pwd->pw_gid); |
| 427 |
(void)seteuid(rootlogin ? 0 : pwd->pw_uid); |
| 428 |
if (!*pwd->pw_dir || chdir(pwd->pw_dir) < 0) { |
| 429 |
if (login_getcapbool(lc, "requirehome", 0)) |
| 430 |
refused("Home directory not available", "HOMEDIR", 1); |
| 431 |
if (chdir("/") < 0) |
| 432 |
refused("Cannot find root directory", "ROOTDIR", 1); |
| 433 |
if (!quietlog || *pwd->pw_dir) |
| 434 |
printf("No home directory.\nLogging in with home = \"/\".\n"); |
| 435 |
pwd->pw_dir = strdup("/"); |
| 436 |
if (pwd->pw_dir == NULL) { |
| 437 |
syslog(LOG_NOTICE, "strdup(): %m"); |
| 438 |
bail(SLEEP_EXIT, 1); |
| 439 |
} |
| 440 |
} |
| 441 |
(void)seteuid(euid); |
| 442 |
(void)setegid(egid); |
| 443 |
if (!quietlog) { |
| 444 |
quietlog = access(_PATH_HUSHLOGIN, F_OK) == 0; |
| 445 |
if (!quietlog) |
| 446 |
pam_silent = 0; |
| 447 |
} |
| 448 |
|
| 449 |
shell = login_getcapstr(lc, "shell", pwd->pw_shell, pwd->pw_shell); |
| 450 |
if (*pwd->pw_shell == '\0') |
| 451 |
pwd->pw_shell = strdup(_PATH_BSHELL); |
| 452 |
if (pwd->pw_shell == NULL) { |
| 453 |
syslog(LOG_NOTICE, "strdup(): %m"); |
| 454 |
bail(SLEEP_EXIT, 1); |
| 455 |
} |
| 456 |
if (*shell == '\0') /* Not overridden */ |
| 457 |
shell = pwd->pw_shell; |
| 458 |
if ((shell = strdup(shell)) == NULL) { |
| 459 |
syslog(LOG_NOTICE, "strdup(): %m"); |
| 460 |
bail(SLEEP_EXIT, 1); |
| 461 |
} |
| 462 |
|
| 463 |
/* |
| 464 |
* Set device protections, depending on what terminal the |
| 465 |
* user is logged in. This feature is used on Suns to give |
| 466 |
* console users better privacy. |
| 467 |
*/ |
| 468 |
login_fbtab(tty, pwd->pw_uid, pwd->pw_gid); |
| 469 |
|
| 470 |
/* |
| 471 |
* Clear flags of the tty. None should be set, and when the |
| 472 |
* user sets them otherwise, this can cause the chown to fail. |
| 473 |
* Since it isn't clear that flags are useful on character |
| 474 |
* devices, we just clear them. |
| 475 |
* |
| 476 |
* We don't log in the case of EOPNOTSUPP because dev might be |
| 477 |
* on NFS, which doesn't support chflags. |
| 478 |
* |
| 479 |
* We don't log in the EROFS because that means that /dev is on |
| 480 |
* a read only file system and we assume that the permissions there |
| 481 |
* are sane. |
| 482 |
*/ |
| 483 |
if (ttyn != tname && chflags(ttyn, 0)) |
| 484 |
if (errno != EOPNOTSUPP && errno != EROFS) |
| 485 |
syslog(LOG_ERR, "chflags(%s): %m", ttyn); |
| 486 |
if (ttyn != tname && chown(ttyn, pwd->pw_uid, |
| 487 |
(gr = getgrnam(TTYGRPNAME)) ? gr->gr_gid : pwd->pw_gid)) |
| 488 |
if (errno != EROFS) |
| 489 |
syslog(LOG_ERR, "chown(%s): %m", ttyn); |
| 490 |
|
| 491 |
#ifdef LOGALL |
| 492 |
/* |
| 493 |
* Syslog each successful login, so we don't have to watch |
| 494 |
* hundreds of wtmp or lastlogin files. |
| 495 |
*/ |
| 496 |
if (hflag) |
| 497 |
syslog(LOG_INFO, "login from %s on %s as %s", |
| 498 |
hostname, tty, pwd->pw_name); |
| 499 |
else |
| 500 |
syslog(LOG_INFO, "login on %s as %s", |
| 501 |
tty, pwd->pw_name); |
| 502 |
#endif |
| 503 |
|
| 504 |
/* |
| 505 |
* If fflag is on, assume caller/authenticator has logged root |
| 506 |
* login. |
| 507 |
*/ |
| 508 |
if (rootlogin && fflag == 0) { |
| 509 |
if (hflag) |
| 510 |
syslog(LOG_NOTICE, "ROOT LOGIN (%s) ON %s FROM %s", |
| 511 |
username, tty, hostname); |
| 512 |
else |
| 513 |
syslog(LOG_NOTICE, "ROOT LOGIN (%s) ON %s", |
| 514 |
username, tty); |
| 515 |
} |
| 516 |
|
| 517 |
/* |
| 518 |
* Destroy environment unless user has requested its |
| 519 |
* preservation - but preserve TERM in all cases |
| 520 |
*/ |
| 521 |
term = getenv("TERM"); |
| 522 |
if (!pflag) |
| 523 |
environ = envinit; |
| 524 |
if (term != NULL) |
| 525 |
setenv("TERM", term, 0); |
| 526 |
|
| 527 |
/* |
| 528 |
* PAM modules might add supplementary groups during pam_setcred(). |
| 529 |
*/ |
| 530 |
if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) != 0) { |
| 531 |
syslog(LOG_ERR, "setusercontext() failed - exiting"); |
| 532 |
bail(NO_SLEEP_EXIT, 1); |
| 533 |
} |
| 534 |
|
| 535 |
pam_err = pam_setcred(pamh, pam_silent|PAM_REINITIALIZE_CRED); |
| 536 |
if (pam_err != PAM_SUCCESS) { |
| 537 |
pam_syslog("pam_setcred()"); |
| 538 |
bail(NO_SLEEP_EXIT, 1); |
| 539 |
} |
| 540 |
|
| 541 |
pam_err = pam_open_session(pamh, pam_silent); |
| 542 |
if (pam_err != PAM_SUCCESS) { |
| 543 |
pam_syslog("pam_open_session()"); |
| 544 |
bail(NO_SLEEP_EXIT, 1); |
| 545 |
} |
| 546 |
pam_session_established = 1; |
| 547 |
|
| 548 |
/* |
| 549 |
* We must fork() before setuid() because we need to call |
| 550 |
* pam_close_session() as root. |
| 551 |
*/ |
| 552 |
pid = fork(); |
| 553 |
if (pid < 0) { |
| 554 |
err(1, "fork"); |
| 555 |
} else if (pid != 0) { |
| 556 |
/* |
| 557 |
* Parent: wait for child to finish, then clean up |
| 558 |
* session. |
| 559 |
* |
| 560 |
* If we get SIGHUP or SIGTERM, clean up the session |
| 561 |
* and exit right away. This will make the terminal |
| 562 |
* inaccessible and send SIGHUP to the foreground |
| 563 |
* process group. |
| 564 |
*/ |
| 565 |
int status; |
| 566 |
setproctitle("-%s [pam]", getprogname()); |
| 567 |
(void)sigprocmask(SIG_SETMASK, &omask, NULL); |
| 568 |
waitpid(pid, &status, 0); |
| 569 |
(void)sigprocmask(SIG_BLOCK, &mask, NULL); |
| 570 |
bail(NO_SLEEP_EXIT, 0); |
| 571 |
} |
| 572 |
|
| 573 |
/* |
| 574 |
* NOTICE: We are now in the child process! |
| 575 |
*/ |
| 576 |
|
| 577 |
/* |
| 578 |
* Add any environment variables the PAM modules may have set. |
| 579 |
*/ |
| 580 |
export_pam_environment(); |
| 581 |
|
| 582 |
/* |
| 583 |
* We're done with PAM now; our parent will deal with the rest. |
| 584 |
*/ |
| 585 |
pam_end(pamh, 0); |
| 586 |
pamh = NULL; |
| 587 |
|
| 588 |
/* |
| 589 |
* We don't need to be root anymore, so set the login name and |
| 590 |
* the UID. |
| 591 |
*/ |
| 592 |
if (setlogin(username) != 0) { |
| 593 |
syslog(LOG_ERR, "setlogin(%s): %m - exiting", username); |
| 594 |
bail(NO_SLEEP_EXIT, 1); |
| 595 |
} |
| 596 |
if (setusercontext(lc, pwd, pwd->pw_uid, |
| 597 |
LOGIN_SETALL & ~(LOGIN_SETLOGIN|LOGIN_SETGROUP)) != 0) { |
| 598 |
syslog(LOG_ERR, "setusercontext() failed - exiting"); |
| 599 |
exit(1); |
| 600 |
} |
| 601 |
|
| 602 |
(void)setenv("SHELL", pwd->pw_shell, 1); |
| 603 |
(void)setenv("HOME", pwd->pw_dir, 1); |
| 604 |
/* Overwrite "term" from login.conf(5) for any known TERM */ |
| 605 |
if (term == NULL && (tp = stypeof(tty)) != NULL) |
| 606 |
(void)setenv("TERM", tp, 1); |
| 607 |
else |
| 608 |
(void)setenv("TERM", TERM_UNKNOWN, 0); |
| 609 |
(void)setenv("LOGNAME", username, 1); |
| 610 |
(void)setenv("USER", username, 1); |
| 611 |
(void)setenv("PATH", rootlogin ? _PATH_STDPATH : _PATH_DEFPATH, 0); |
| 612 |
|
| 613 |
if (!quietlog) { |
| 614 |
const char *cw; |
| 615 |
|
| 616 |
cw = login_getcapstr(lc, "welcome", NULL, NULL); |
| 617 |
if (cw != NULL && access(cw, F_OK) == 0) |
| 618 |
motd(cw); |
| 619 |
else |
| 620 |
motd(_PATH_MOTDFILE); |
| 621 |
|
| 622 |
if (login_getcapbool(lc_user, "nocheckmail", 0) == 0 && |
| 623 |
login_getcapbool(lc, "nocheckmail", 0) == 0) { |
| 624 |
char *cx; |
| 625 |
|
| 626 |
/* $MAIL may have been set by class. */ |
| 627 |
cx = getenv("MAIL"); |
| 628 |
if (cx == NULL) { |
| 629 |
asprintf(&cx, "%s/%s", |
| 630 |
_PATH_MAILDIR, pwd->pw_name); |
| 631 |
} |
| 632 |
if (cx && stat(cx, &st) == 0 && st.st_size != 0) |
| 633 |
(void)printf("You have %smail.\n", |
| 634 |
(st.st_mtime > st.st_atime) ? "new " : ""); |
| 635 |
if (getenv("MAIL") == NULL) |
| 636 |
free(cx); |
| 637 |
} |
| 638 |
} |
| 639 |
|
| 640 |
login_close(lc_user); |
| 641 |
login_close(lc); |
| 642 |
|
| 643 |
sa.sa_handler = SIG_DFL; |
| 644 |
(void)sigaction(SIGALRM, &sa, NULL); |
| 645 |
(void)sigaction(SIGQUIT, &sa, NULL); |
| 646 |
(void)sigaction(SIGINT, &sa, NULL); |
| 647 |
(void)sigaction(SIGTERM, &sa, NULL); |
| 648 |
(void)sigaction(SIGHUP, &sa, NULL); |
| 649 |
sa.sa_handler = SIG_IGN; |
| 650 |
(void)sigaction(SIGTSTP, &sa, NULL); |
| 651 |
(void)sigprocmask(SIG_SETMASK, &omask, NULL); |
| 652 |
|
| 653 |
/* |
| 654 |
* Login shells have a leading '-' in front of argv[0] |
| 655 |
*/ |
| 656 |
p = strrchr(pwd->pw_shell, '/'); |
| 657 |
if (asprintf(&arg0, "-%s", p ? p + 1 : pwd->pw_shell) >= MAXPATHLEN) { |
| 658 |
syslog(LOG_ERR, "user: %s: shell exceeds maximum pathname size", |
| 659 |
username); |
| 660 |
errx(1, "shell exceeds maximum pathname size"); |
| 661 |
} else if (arg0 == NULL) { |
| 662 |
err(1, "asprintf()"); |
| 663 |
} |
| 664 |
|
| 665 |
execlp(shell, arg0, (char *)0); |
| 666 |
err(1, "%s", shell); |
| 667 |
|
| 668 |
/* |
| 669 |
* That's it, folks! |
| 670 |
*/ |
| 671 |
} |
| 672 |
|
| 673 |
/* |
| 674 |
* Attempt to authenticate the user using PAM. Returns 0 if the user is |
| 675 |
* authenticated, or 1 if not authenticated. If some sort of PAM system |
| 676 |
* error occurs (e.g., the "/etc/pam.conf" file is missing) then this |
| 677 |
* function returns -1. This can be used as an indication that we should |
| 678 |
* fall back to a different authentication mechanism. |
| 679 |
*/ |
| 680 |
static int |
| 681 |
auth_pam(void) |
| 682 |
{ |
| 683 |
const char *tmpl_user; |
| 684 |
const void *item; |
| 685 |
int rval; |
| 686 |
|
| 687 |
pam_err = pam_authenticate(pamh, pam_silent); |
| 688 |
switch (pam_err) { |
| 689 |
|
| 690 |
case PAM_SUCCESS: |
| 691 |
/* |
| 692 |
* With PAM we support the concept of a "template" |
| 693 |
* user. The user enters a login name which is |
| 694 |
* authenticated by PAM, usually via a remote service |
| 695 |
* such as RADIUS or TACACS+. If authentication |
| 696 |
* succeeds, a different but related "template" name |
| 697 |
* is used for setting the credentials, shell, and |
| 698 |
* home directory. The name the user enters need only |
| 699 |
* exist on the remote authentication server, but the |
| 700 |
* template name must be present in the local password |
| 701 |
* database. |
| 702 |
* |
| 703 |
* This is supported by two various mechanisms in the |
| 704 |
* individual modules. However, from the application's |
| 705 |
* point of view, the template user is always passed |
| 706 |
* back as a changed value of the PAM_USER item. |
| 707 |
*/ |
| 708 |
pam_err = pam_get_item(pamh, PAM_USER, &item); |
| 709 |
if (pam_err == PAM_SUCCESS) { |
| 710 |
tmpl_user = (const char *)item; |
| 711 |
if (strcmp(username, tmpl_user) != 0) |
| 712 |
pwd = getpwnam(tmpl_user); |
| 713 |
} else { |
| 714 |
pam_syslog("pam_get_item(PAM_USER)"); |
| 715 |
} |
| 716 |
rval = 0; |
| 717 |
break; |
| 718 |
|
| 719 |
case PAM_AUTH_ERR: |
| 720 |
case PAM_USER_UNKNOWN: |
| 721 |
case PAM_MAXTRIES: |
| 722 |
rval = 1; |
| 723 |
break; |
| 724 |
|
| 725 |
default: |
| 726 |
pam_syslog("pam_authenticate()"); |
| 727 |
rval = -1; |
| 728 |
break; |
| 729 |
} |
| 730 |
|
| 731 |
if (rval == 0) { |
| 732 |
pam_err = pam_acct_mgmt(pamh, pam_silent); |
| 733 |
switch (pam_err) { |
| 734 |
case PAM_SUCCESS: |
| 735 |
break; |
| 736 |
case PAM_NEW_AUTHTOK_REQD: |
| 737 |
pam_err = pam_chauthtok(pamh, |
| 738 |
pam_silent|PAM_CHANGE_EXPIRED_AUTHTOK); |
| 739 |
if (pam_err != PAM_SUCCESS) { |
| 740 |
pam_syslog("pam_chauthtok()"); |
| 741 |
rval = 1; |
| 742 |
} |
| 743 |
break; |
| 744 |
default: |
| 745 |
pam_syslog("pam_acct_mgmt()"); |
| 746 |
rval = 1; |
| 747 |
break; |
| 748 |
} |
| 749 |
} |
| 750 |
|
| 751 |
if (rval != 0) { |
| 752 |
pam_end(pamh, pam_err); |
| 753 |
pamh = NULL; |
| 754 |
} |
| 755 |
return (rval); |
| 756 |
} |
| 757 |
|
| 758 |
/* |
| 759 |
* Export any environment variables PAM modules may have set |
| 760 |
*/ |
| 761 |
static void |
| 762 |
export_pam_environment(void) |
| 763 |
{ |
| 764 |
char **pam_env; |
| 765 |
char **pp; |
| 766 |
|
| 767 |
pam_env = pam_getenvlist(pamh); |
| 768 |
if (pam_env != NULL) { |
| 769 |
for (pp = pam_env; *pp != NULL; pp++) { |
| 770 |
(void)export(*pp); |
| 771 |
free(*pp); |
| 772 |
} |
| 773 |
} |
| 774 |
} |
| 775 |
|
| 776 |
/* |
| 777 |
* Perform sanity checks on an environment variable: |
| 778 |
* - Make sure there is an '=' in the string. |
| 779 |
* - Make sure the string doesn't run on too long. |
| 780 |
* - Do not export certain variables. This list was taken from the |
| 781 |
* Solaris pam_putenv(3) man page. |
| 782 |
* Then export it. |
| 783 |
*/ |
| 784 |
static int |
| 785 |
export(const char *s) |
| 786 |
{ |
| 787 |
static const char *noexport[] = { |
| 788 |
"SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH", |
| 789 |
"IFS", "PATH", NULL |
| 790 |
}; |
| 791 |
char *p; |
| 792 |
const char **pp; |
| 793 |
size_t n; |
| 794 |
|
| 795 |
if (strlen(s) > 1024 || (p = strchr(s, '=')) == NULL) |
| 796 |
return (0); |
| 797 |
if (strncmp(s, "LD_", 3) == 0) |
| 798 |
return (0); |
| 799 |
for (pp = noexport; *pp != NULL; pp++) { |
| 800 |
n = strlen(*pp); |
| 801 |
if (s[n] == '=' && strncmp(s, *pp, n) == 0) |
| 802 |
return (0); |
| 803 |
} |
| 804 |
*p = '\0'; |
| 805 |
(void)setenv(s, p + 1, 1); |
| 806 |
*p = '='; |
| 807 |
return (1); |
| 808 |
} |
| 809 |
|
| 810 |
static void |
| 811 |
usage(void) |
| 812 |
{ |
| 813 |
|
| 814 |
(void)fprintf(stderr, "usage: login [-fp] [-h hostname] [username]\n"); |
| 815 |
exit(1); |
| 816 |
} |
| 817 |
|
| 818 |
/* |
| 819 |
* Prompt user and read login name from stdin. |
| 820 |
*/ |
| 821 |
static char * |
| 822 |
getloginname(void) |
| 823 |
{ |
| 824 |
char *nbuf, *p; |
| 825 |
int ch; |
| 826 |
|
| 827 |
nbuf = malloc(MAXLOGNAME); |
| 828 |
if (nbuf == NULL) |
| 829 |
err(1, "malloc()"); |
| 830 |
do { |
| 831 |
(void)printf("%s", prompt); |
| 832 |
for (p = nbuf; (ch = getchar()) != '\n'; ) { |
| 833 |
if (ch == EOF) { |
| 834 |
badlogin(username); |
| 835 |
bail(NO_SLEEP_EXIT, 0); |
| 836 |
} |
| 837 |
if (p < nbuf + MAXLOGNAME - 1) |
| 838 |
*p++ = ch; |
| 839 |
} |
| 840 |
} while (p == nbuf); |
| 841 |
|
| 842 |
*p = '\0'; |
| 843 |
if (nbuf[0] == '-') { |
| 844 |
pam_silent = 0; |
| 845 |
memmove(nbuf, nbuf + 1, strlen(nbuf)); |
| 846 |
} else { |
| 847 |
pam_silent = PAM_SILENT; |
| 848 |
} |
| 849 |
return nbuf; |
| 850 |
} |
| 851 |
|
| 852 |
/* |
| 853 |
* SIGINT handler for motd(). |
| 854 |
*/ |
| 855 |
static volatile int motdinterrupt; |
| 856 |
static void |
| 857 |
sigint(int signo __unused) |
| 858 |
{ |
| 859 |
motdinterrupt = 1; |
| 860 |
} |
| 861 |
|
| 862 |
/* |
| 863 |
* Display the contents of a file (such as /etc/motd). |
| 864 |
*/ |
| 865 |
static int |
| 866 |
motd(const char *motdfile) |
| 867 |
{ |
| 868 |
struct sigaction newint, oldint; |
| 869 |
FILE *f; |
| 870 |
int ch; |
| 871 |
|
| 872 |
if ((f = fopen(motdfile, "r")) == NULL) |
| 873 |
return (-1); |
| 874 |
motdinterrupt = 0; |
| 875 |
newint.sa_handler = sigint; |
| 876 |
newint.sa_flags = 0; |
| 877 |
sigfillset(&newint.sa_mask); |
| 878 |
sigaction(SIGINT, &newint, &oldint); |
| 879 |
while ((ch = fgetc(f)) != EOF && !motdinterrupt) |
| 880 |
putchar(ch); |
| 881 |
sigaction(SIGINT, &oldint, NULL); |
| 882 |
if (ch != EOF || ferror(f)) { |
| 883 |
fclose(f); |
| 884 |
return (-1); |
| 885 |
} |
| 886 |
fclose(f); |
| 887 |
return (0); |
| 888 |
} |
| 889 |
|
| 890 |
/* |
| 891 |
* SIGALRM handler, to enforce login prompt timeout. |
| 892 |
* |
| 893 |
* XXX This can potentially confuse the hell out of PAM. We should |
| 894 |
* XXX instead implement a conversation function that returns |
| 895 |
* XXX PAM_CONV_ERR when interrupted by a signal, and have the signal |
| 896 |
* XXX handler just set a flag. |
| 897 |
*/ |
| 898 |
static void |
| 899 |
timedout(int signo __unused) |
| 900 |
{ |
| 901 |
|
| 902 |
longjmp(timeout_buf, signo); |
| 903 |
} |
| 904 |
|
| 905 |
static void |
| 906 |
badlogin(char *name) |
| 907 |
{ |
| 908 |
|
| 909 |
if (failures == 0) |
| 910 |
return; |
| 911 |
if (hflag) { |
| 912 |
syslog(LOG_NOTICE, "%d LOGIN FAILURE%s FROM %s", |
| 913 |
failures, failures > 1 ? "S" : "", hostname); |
| 914 |
syslog(LOG_AUTHPRIV|LOG_NOTICE, |
| 915 |
"%d LOGIN FAILURE%s FROM %s, %s", |
| 916 |
failures, failures > 1 ? "S" : "", hostname, name); |
| 917 |
} else { |
| 918 |
syslog(LOG_NOTICE, "%d LOGIN FAILURE%s ON %s", |
| 919 |
failures, failures > 1 ? "S" : "", tty); |
| 920 |
syslog(LOG_AUTHPRIV|LOG_NOTICE, |
| 921 |
"%d LOGIN FAILURE%s ON %s, %s", |
| 922 |
failures, failures > 1 ? "S" : "", tty, name); |
| 923 |
} |
| 924 |
failures = 0; |
| 925 |
} |
| 926 |
|
| 927 |
const char * |
| 928 |
stypeof(char *ttyid) |
| 929 |
{ |
| 930 |
struct ttyent *t; |
| 931 |
|
| 932 |
if (ttyid != NULL && *ttyid != '\0') { |
| 933 |
t = getttynam(ttyid); |
| 934 |
if (t != NULL && t->ty_type != NULL) |
| 935 |
return (t->ty_type); |
| 936 |
} |
| 937 |
return (NULL); |
| 938 |
} |
| 939 |
|
| 940 |
static void |
| 941 |
refused(const char *msg, const char *rtype, int lout) |
| 942 |
{ |
| 943 |
|
| 944 |
if (msg != NULL) |
| 945 |
printf("%s.\n", msg); |
| 946 |
if (hflag) |
| 947 |
syslog(LOG_NOTICE, "LOGIN %s REFUSED (%s) FROM %s ON TTY %s", |
| 948 |
pwd->pw_name, rtype, hostname, tty); |
| 949 |
else |
| 950 |
syslog(LOG_NOTICE, "LOGIN %s REFUSED (%s) ON TTY %s", |
| 951 |
pwd->pw_name, rtype, tty); |
| 952 |
if (lout) |
| 953 |
bail(SLEEP_EXIT, 1); |
| 954 |
} |
| 955 |
|
| 956 |
/* |
| 957 |
* Log a PAM error |
| 958 |
*/ |
| 959 |
static void |
| 960 |
pam_syslog(const char *msg) |
| 961 |
{ |
| 962 |
syslog(LOG_ERR, "%s: %s", msg, pam_strerror(pamh, pam_err)); |
| 963 |
} |
| 964 |
|
| 965 |
/* |
| 966 |
* Shut down PAM |
| 967 |
*/ |
| 968 |
static void |
| 969 |
pam_cleanup(void) |
| 970 |
{ |
| 971 |
|
| 972 |
if (pamh != NULL) { |
| 973 |
if (pam_session_established) { |
| 974 |
pam_err = pam_close_session(pamh, 0); |
| 975 |
if (pam_err != PAM_SUCCESS) |
| 976 |
pam_syslog("pam_close_session()"); |
| 977 |
} |
| 978 |
pam_session_established = 0; |
| 979 |
if (pam_cred_established) { |
| 980 |
pam_err = pam_setcred(pamh, pam_silent|PAM_DELETE_CRED); |
| 981 |
if (pam_err != PAM_SUCCESS) |
| 982 |
pam_syslog("pam_setcred()"); |
| 983 |
} |
| 984 |
pam_cred_established = 0; |
| 985 |
pam_end(pamh, pam_err); |
| 986 |
pamh = NULL; |
| 987 |
} |
| 988 |
} |
| 989 |
|
| 990 |
static void |
| 991 |
bail_internal(int sec, int eval, int signo) |
| 992 |
{ |
| 993 |
struct sigaction sa; |
| 994 |
|
| 995 |
pam_cleanup(); |
| 996 |
#ifdef USE_BSM_AUDIT |
| 997 |
if (pwd != NULL) |
| 998 |
audit_logout(); |
| 999 |
#endif |
| 1000 |
(void)sleep(sec); |
| 1001 |
if (signo == 0) |
| 1002 |
exit(eval); |
| 1003 |
else { |
| 1004 |
sa.sa_handler = SIG_DFL; |
| 1005 |
sa.sa_flags = 0; |
| 1006 |
(void)sigemptyset(&sa.sa_mask); |
| 1007 |
(void)sigaction(signo, &sa, NULL); |
| 1008 |
(void)sigaddset(&sa.sa_mask, signo); |
| 1009 |
(void)sigprocmask(SIG_UNBLOCK, &sa.sa_mask, NULL); |
| 1010 |
raise(signo); |
| 1011 |
exit(128 + signo); |
| 1012 |
} |
| 1013 |
} |
| 1014 |
|
| 1015 |
/* |
| 1016 |
* Exit, optionally after sleeping a few seconds |
| 1017 |
*/ |
| 1018 |
static void |
| 1019 |
bail(int sec, int eval) |
| 1020 |
{ |
| 1021 |
bail_internal(sec, eval, 0); |
| 1022 |
} |
| 1023 |
|
| 1024 |
/* |
| 1025 |
* Exit because of a signal. |
| 1026 |
* This is not async-signal safe, so only call async-signal safe functions |
| 1027 |
* while the signal is unmasked. |
| 1028 |
*/ |
| 1029 |
static void |
| 1030 |
bail_sig(int signo) |
| 1031 |
{ |
| 1032 |
bail_internal(NO_SLEEP_EXIT, 0, signo); |
| 1033 |
} |