Print this page
onc plus-be-gone
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/cmd/login/login.c
+++ new/usr/src/cmd/login/login.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
↓ open down ↓ |
16 lines elided |
↑ open up ↑ |
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21
22 22 /*
23 23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 24 * Use is subject to license terms.
25 25 */
26 26
27 -/* ONC_PLUS EXTRACT START */
28 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
29 28 /* All Rights Reserved */
30 29
31 30 /*
32 31 * University Copyright- Copyright (c) 1982, 1986, 1988
33 32 * The Regents of the University of California
34 33 * All Rights Reserved
35 34 *
36 35 * University Acknowledgment- Portions of this document are derived from
37 36 * software developed by the University of California, Berkeley, and its
38 37 * contributors.
39 38 */
40 39
41 40 /* Copyright (c) 1987, 1988 Microsoft Corporation */
42 41 /* All Rights Reserved */
43 42
44 -/* ONC_PLUS EXTRACT END */
45 -
46 43 /*
47 44 * For a complete reference to login(1), see the manual page. However,
48 45 * login has accreted some intentionally undocumented options, which are
49 46 * explained here:
50 47 *
51 48 * -a: This legacy flag appears to be unused.
52 49 *
53 50 * -f <username>: This flag was introduced by PSARC 1995/039 in support
54 51 * of Kerberos. But it's not used by Sun's Kerberos implementation.
55 52 * It is however employed by zlogin(1), since it allows one to tell
56 53 * login: "This user is authenticated." In the case of zlogin that's
57 54 * true because the zone always trusts the global zone.
58 55 *
59 56 * -z <zonename>: This flag is passed to login when zlogin(1) executes a
60 57 * zone login. This tells login(1) to skip it's normal CONSOLE check
61 58 * (i.e. that the root login must be on /dev/console) and tells us the
62 59 * name of the zone from which the login is occurring.
63 60 */
64 61
65 62 #include <sys/types.h>
66 63 #include <sys/param.h>
67 64 #include <unistd.h> /* For logfile locking */
68 65 #include <signal.h>
69 66 #include <stdio.h>
70 67 #include <sys/stat.h>
71 68 #include <string.h>
72 69 #include <deflt.h>
73 70 #include <grp.h>
74 71 #include <fcntl.h>
75 72 #include <lastlog.h>
76 73 #include <termio.h>
77 74 #include <utmpx.h>
78 75 #include <stdlib.h>
79 76 #include <wait.h>
80 77 #include <errno.h>
81 78 #include <ctype.h>
82 79 #include <syslog.h>
83 80 #include <ulimit.h>
84 81 #include <libgen.h>
85 82 #include <pwd.h>
86 83 #include <security/pam_appl.h>
87 84 #include <strings.h>
88 85 #include <libdevinfo.h>
89 86 #include <zone.h>
90 87 #include "login_audit.h"
91 88
92 89 #include <krb5_repository.h>
93 90 /*
94 91 *
95 92 * *** Defines, Macros, and String Constants ***
96 93 *
97 94 *
98 95 */
99 96
100 97 #define ISSUEFILE "/etc/issue" /* file to print before prompt */
101 98 #define NOLOGIN "/etc/nologin" /* file to lock users out during shutdown */
102 99
103 100 /*
104 101 * These need to be defined for UTMPX management.
105 102 * If we add in the utility functions later, we
106 103 * can remove them.
107 104 */
108 105 #define __UPDATE_ENTRY 1
109 106 #define __LOGIN 2
110 107
111 108 /*
112 109 * Intervals to sleep after failed login
113 110 */
114 111 #ifndef SLEEPTIME
115 112 #define SLEEPTIME 4 /* sleeptime before login incorrect msg */
116 113 #endif
117 114 static int Sleeptime = SLEEPTIME;
118 115
119 116 /*
120 117 * seconds login disabled after allowable number of unsuccessful attempts
121 118 */
122 119 #ifndef DISABLETIME
123 120 #define DISABLETIME 20
124 121 #endif
125 122 static int Disabletime = DISABLETIME;
126 123
127 124 #define MAXTRYS 5
128 125
129 126 static int retry = MAXTRYS;
130 127
131 128 /*
132 129 * Login logging support
133 130 */
134 131 #define LOGINLOG "/var/adm/loginlog" /* login log file */
135 132 #define LNAME_SIZE 20 /* size of logged logname */
136 133 #define TTYN_SIZE 15 /* size of logged tty name */
137 134 #define TIME_SIZE 30 /* size of logged time string */
138 135 #define ENT_SIZE (LNAME_SIZE + TTYN_SIZE + TIME_SIZE + 3)
139 136 #define L_WAITTIME 5 /* waittime for log file to unlock */
140 137 #define LOGTRYS 10 /* depth of 'try' logging */
141 138
142 139 /*
143 140 * String manipulation macros: SCPYN, SCPYL, EQN and ENVSTRNCAT
144 141 * SCPYL is the safer version of SCPYN
145 142 */
146 143 #define SCPYL(a, b) (void) strlcpy(a, b, sizeof (a))
147 144 #define SCPYN(a, b) (void) strncpy(a, b, sizeof (a))
148 145 #define EQN(a, b) (strncmp(a, b, sizeof (a)-1) == 0)
149 146 #define ENVSTRNCAT(to, from) {int deflen; deflen = strlen(to); \
150 147 (void) strncpy((to)+ deflen, (from), sizeof (to) - (1 + deflen)); }
151 148
152 149 /*
153 150 * Other macros
154 151 */
155 152 #define NMAX sizeof (((struct utmpx *)0)->ut_name)
156 153 #define HMAX sizeof (((struct utmpx *)0)->ut_host)
157 154 #define min(a, b) (((a) < (b)) ? (a) : (b))
158 155
↓ open down ↓ |
103 lines elided |
↑ open up ↑ |
159 156 /*
160 157 * Various useful files and string constants
161 158 */
162 159 #define SHELL "/usr/bin/sh"
163 160 #define SHELL2 "/sbin/sh"
164 161 #define SUBLOGIN "<!sublogin>"
165 162 #define LASTLOG "/var/adm/lastlog"
166 163 #define PROG_NAME "login"
167 164 #define HUSHLOGIN ".hushlogin"
168 165
169 -/* ONC_PLUS EXTRACT START */
170 166 /*
171 167 * Array and Buffer sizes
172 168 */
173 169 #define PBUFSIZE 8 /* max significant characters in a password */
174 -/* ONC_PLUS EXTRACT END */
175 170 #define MAXARGS 63 /* change value below if changing this */
176 171 #define MAXARGSWIDTH 2 /* log10(MAXARGS) */
177 172 #define MAXENV 1024
178 173 #define MAXLINE 2048
179 174
180 175 /*
181 176 * Miscellaneous constants
182 177 */
183 178 #define ROOTUID 0
184 179 #define ERROR 1
185 180 #define OK 0
186 181 #define LOG_ERROR 1
187 182 #define DONT_LOG_ERROR 0
188 183 #define TRUE 1
189 184 #define FALSE 0
190 185
191 186 /*
192 187 * Counters for counting the number of failed login attempts
193 188 */
194 189 static int trys = 0;
↓ open down ↓ |
10 lines elided |
↑ open up ↑ |
195 190 static int count = 1;
196 191
197 192 /*
198 193 * error value for login_exit() audit output (0 == no audit record)
199 194 */
200 195 static int audit_error = 0;
201 196
202 197 /*
203 198 * Externs a plenty
204 199 */
205 -/* ONC_PLUS EXTRACT START */
206 200 extern int getsecretkey();
207 -/* ONC_PLUS EXTRACT START */
208 201
209 202 /*
210 203 * The current user name
211 204 */
212 205 static char user_name[NMAX];
213 206 static char minusnam[16] = "-";
214 207
215 208 /*
216 209 * login_pid, used to find utmpx entry to update.
217 210 */
218 211 static pid_t login_pid;
219 212
220 213 /*
221 214 * locale environments to be passed to shells.
222 215 */
223 216 static char *localeenv[] = {
224 217 "LANG",
225 218 "LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE",
226 219 "LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0};
227 220 static int locale_envmatch(char *, char *);
228 221
229 222 /*
230 223 * Environment variable support
231 224 */
232 225 static char shell[256] = { "SHELL=" };
233 226 static char home[MAXPATHLEN] = { "HOME=" };
234 227 static char term[64] = { "TERM=" };
235 228 static char logname[30] = { "LOGNAME=" };
236 229 static char timez[100] = { "TZ=" };
237 230 static char hertz[10] = { "HZ=" };
238 231 static char path[MAXPATHLEN] = { "PATH=" };
239 232 static char *newenv[10+MAXARGS] =
240 233 {home, path, logname, hertz, term, 0, 0};
241 234 static char **envinit = newenv;
242 235 static int basicenv;
243 236 static char *zero = (char *)0;
244 237 static char **envp;
245 238 #ifndef NO_MAIL
246 239 static char mail[30] = { "MAIL=/var/mail/" };
247 240 #endif
248 241 extern char **environ;
249 242 static char inputline[MAXLINE];
250 243
251 244 #define MAX_ID_LEN 256
252 245 #define MAX_REPOSITORY_LEN 256
253 246 #define MAX_PAMSERVICE_LEN 256
254 247
255 248 static char identity[MAX_ID_LEN];
256 249 static char repository[MAX_REPOSITORY_LEN];
↓ open down ↓ |
39 lines elided |
↑ open up ↑ |
257 250 static char progname[MAX_PAMSERVICE_LEN];
258 251
259 252
260 253 /*
261 254 * Strings used to prompt the user.
262 255 */
263 256 static char loginmsg[] = "login: ";
264 257 static char passwdmsg[] = "Password:";
265 258 static char incorrectmsg[] = "Login incorrect\n";
266 259
267 -/* ONC_PLUS EXTRACT START */
268 260 /*
269 261 * Password file support
270 262 */
271 263 static struct passwd *pwd = NULL;
272 264 static char remote_host[HMAX];
273 265 static char zone_name[ZONENAME_MAX];
274 266
275 267 /*
276 268 * Illegal passwd entries.
277 269 */
278 270 static struct passwd nouser = { "", "no:password", (uid_t)-1 };
279 -/* ONC_PLUS EXTRACT END */
280 271
281 272 /*
282 273 * Log file support
283 274 */
284 275 static char *log_entry[LOGTRYS];
285 276 static int writelog = 0;
286 277 static int lastlogok = 0;
287 278 static struct lastlog ll;
288 279 static int dosyslog = 0;
289 280 static int flogin = MAXTRYS; /* flag for SYSLOG_FAILED_LOGINS */
290 281
291 282 /*
292 283 * Default file toggles
293 284 */
294 285 static char *Pndefault = "/etc/default/login";
295 286 static char *Altshell = NULL;
296 287 static char *Console = NULL;
297 288 static int Passreqflag = 0;
298 289
299 290 #define DEFUMASK 022
300 291 static mode_t Umask = DEFUMASK;
301 292 static char *Def_tz = NULL;
302 293 static char *tmp_tz = NULL;
303 294 static char *Def_hertz = NULL;
304 295 #define SET_FSIZ 2 /* ulimit() command arg */
305 296 static long Def_ulimit = 0;
306 297 #define MAX_TIMEOUT (15 * 60)
307 298 #define DEF_TIMEOUT (5 * 60)
308 299 static unsigned Def_timeout = DEF_TIMEOUT;
309 300 static char *Def_path = NULL;
310 301 static char *Def_supath = NULL;
311 302 #define DEF_PATH "/usr/bin:" /* same as PATH */
312 303 #define DEF_SUPATH "/usr/sbin:/usr/bin" /* same as ROOTPATH */
313 304
314 305 /*
315 306 * Defaults for updating expired passwords
316 307 */
317 308 #define DEF_ATTEMPTS 3
318 309
319 310 /*
320 311 * ttyprompt will point to the environment variable TTYPROMPT.
321 312 * TTYPROMPT is set by ttymon if ttymon already wrote out the prompt.
322 313 */
323 314 static char *ttyprompt = NULL;
324 315 static char *ttyn = NULL;
325 316
326 317 /*
327 318 * Pass inherited environment. Used by telnetd in support of the telnet
328 319 * ENVIRON option.
329 320 */
330 321 static boolean_t pflag = B_FALSE;
331 322 static boolean_t uflag = B_FALSE;
332 323 static boolean_t Rflag = B_FALSE;
333 324 static boolean_t sflag = B_FALSE;
334 325 static boolean_t Uflag = B_FALSE;
335 326 static boolean_t tflag = B_FALSE;
↓ open down ↓ |
46 lines elided |
↑ open up ↑ |
336 327 static boolean_t hflag = B_FALSE;
337 328 static boolean_t rflag = B_FALSE;
338 329 static boolean_t zflag = B_FALSE;
339 330
340 331 /*
341 332 * Remote login support
342 333 */
343 334 static char rusername[NMAX+1], lusername[NMAX+1];
344 335 static char terminal[MAXPATHLEN];
345 336
346 -/* ONC_PLUS EXTRACT START */
347 337 /*
348 338 * Pre-authentication flag support
349 339 */
350 340 static int fflag;
351 341
352 342 static char ** getargs(char *);
353 343
354 344 static int login_conv(int, struct pam_message **,
355 345 struct pam_response **, void *);
356 346
357 347 static struct pam_conv pam_conv = {login_conv, NULL};
358 348 static pam_handle_t *pamh; /* Authentication handle */
359 -/* ONC_PLUS EXTRACT END */
360 349
361 350 /*
362 351 * Function declarations
363 352 */
364 353 static void turn_on_logging(void);
365 354 static void defaults(void);
366 355 static void usage(void);
367 356 static void process_rlogin(void);
368 -/* ONC_PLUS EXTRACT START */
369 357 static void login_authenticate();
370 358 static void setup_credentials(void);
371 -/* ONC_PLUS EXTRACT END */
372 359 static void adjust_nice(void);
373 360 static void update_utmpx_entry(int);
374 361 static void establish_user_environment(char **);
375 362 static void print_banner(void);
376 363 static void display_last_login_time(void);
377 364 static void exec_the_shell(void);
378 365 static int process_chroot_logins(void);
379 366 static void chdir_to_dir_user(void);
380 367 static void check_log(void);
381 368 static void validate_account(void);
382 369 static void doremoteterm(char *);
383 370 static int get_options(int, char **);
384 371 static void getstr(char *, int, char *);
↓ open down ↓ |
3 lines elided |
↑ open up ↑ |
385 372 static int legalenvvar(char *);
386 373 static void check_for_console(void);
387 374 static void check_for_dueling_unix(char *);
388 375 static void get_user_name(void);
389 376 static uint_t get_audit_id(void);
390 377 static void login_exit(int)__NORETURN;
391 378 static int logins_disabled(char *);
392 379 static void log_bad_attempts(void);
393 380 static int is_number(char *);
394 381
395 -/* ONC_PLUS EXTRACT START */
396 382 /*
397 383 * *** main ***
398 384 *
399 385 * The primary flow of control is directed in this routine.
400 386 * Control moves in line from top to bottom calling subfunctions
401 387 * which perform the bulk of the work. Many of these calls exit
402 388 * when a fatal error is encountered and do not return to main.
403 389 *
404 390 *
405 391 */
406 392
407 393 int
408 394 main(int argc, char *argv[], char **renvp)
409 395 {
410 -/* ONC_PLUS EXTRACT END */
411 396 int sublogin;
412 397 int pam_rc;
413 398
414 399 login_pid = getpid();
415 400
416 401 /*
417 402 * Set up Defaults and flags
418 403 */
419 404 defaults();
420 405 SCPYL(progname, PROG_NAME);
421 406
422 407 /*
423 408 * Set up default umask
424 409 */
425 410 if (Umask > ((mode_t)0777))
426 411 Umask = DEFUMASK;
427 412 (void) umask(Umask);
428 413
429 414 /*
430 415 * Set up default timeouts and delays
431 416 */
432 417 if (Def_timeout > MAX_TIMEOUT)
433 418 Def_timeout = MAX_TIMEOUT;
434 419 if (Sleeptime < 0 || Sleeptime > 5)
435 420 Sleeptime = SLEEPTIME;
436 421
437 422 (void) alarm(Def_timeout);
438 423
439 424 /*
440 425 * Ignore SIGQUIT and SIGINT and set nice to 0
441 426 */
442 427 (void) signal(SIGQUIT, SIG_IGN);
443 428 (void) signal(SIGINT, SIG_IGN);
444 429 (void) nice(0);
445 430
446 431 /*
447 432 * Set flag to disable the pid check if you find that you are
448 433 * a subsystem login.
449 434 */
450 435 sublogin = 0;
451 436 if (*renvp && strcmp(*renvp, SUBLOGIN) == 0)
452 437 sublogin = 1;
453 438
454 439 /*
455 440 * Parse Arguments
456 441 */
457 442 if (get_options(argc, argv) == -1) {
458 443 usage();
459 444 audit_error = ADT_FAIL_VALUE_BAD_CMD;
460 445 login_exit(1);
461 446 }
↓ open down ↓ |
41 lines elided |
↑ open up ↑ |
462 447
463 448 /*
464 449 * if devicename is not passed as argument, call ttyname(0)
465 450 */
466 451 if (ttyn == NULL) {
467 452 ttyn = ttyname(0);
468 453 if (ttyn == NULL)
469 454 ttyn = "/dev/???";
470 455 }
471 456
472 -/* ONC_PLUS EXTRACT START */
473 457 /*
474 458 * Call pam_start to initiate a PAM authentication operation
475 459 */
476 460
477 461 if ((pam_rc = pam_start(progname, user_name, &pam_conv, &pamh))
478 462 != PAM_SUCCESS) {
479 463 audit_error = ADT_FAIL_PAM + pam_rc;
480 464 login_exit(1);
481 465 }
482 466 if ((pam_rc = pam_set_item(pamh, PAM_TTY, ttyn)) != PAM_SUCCESS) {
483 467 audit_error = ADT_FAIL_PAM + pam_rc;
484 468 login_exit(1);
485 469 }
486 470 if ((pam_rc = pam_set_item(pamh, PAM_RHOST, remote_host)) !=
487 471 PAM_SUCCESS) {
488 472 audit_error = ADT_FAIL_PAM + pam_rc;
489 473 login_exit(1);
490 474 }
491 475
492 476 /*
493 477 * We currently only support special handling of the KRB5 PAM repository
494 478 */
495 479 if ((Rflag && strlen(repository)) &&
496 480 strcmp(repository, KRB5_REPOSITORY_NAME) == 0 &&
497 481 (uflag && strlen(identity))) {
498 482 krb5_repository_data_t krb5_data;
499 483 pam_repository_t pam_rep_data;
500 484
↓ open down ↓ |
18 lines elided |
↑ open up ↑ |
501 485 krb5_data.principal = identity;
502 486 krb5_data.flags = SUNW_PAM_KRB5_ALREADY_AUTHENTICATED;
503 487
504 488 pam_rep_data.type = repository;
505 489 pam_rep_data.scope = (void *)&krb5_data;
506 490 pam_rep_data.scope_len = sizeof (krb5_data);
507 491
508 492 (void) pam_set_item(pamh, PAM_REPOSITORY,
509 493 (void *)&pam_rep_data);
510 494 }
511 -/* ONC_PLUS EXTRACT END */
512 495
513 496 /*
514 497 * Open the log file which contains a record of successful and failed
515 498 * login attempts
516 499 */
517 500 turn_on_logging();
518 501
519 502 /*
520 503 * say "hi" to syslogd ..
521 504 */
522 505 openlog("login", 0, LOG_AUTH);
523 506
524 507 /*
525 508 * Do special processing for -r (rlogin) flag
526 509 */
527 510 if (rflag)
528 511 process_rlogin();
529 512
530 -/* ONC_PLUS EXTRACT START */
531 513 /*
532 514 * validate user
533 515 */
534 516 /* we are already authenticated. fill in what we must, then continue */
535 517 if (fflag) {
536 -/* ONC_PLUS EXTRACT END */
537 518 if ((pwd = getpwnam(user_name)) == NULL) {
538 519 audit_error = ADT_FAIL_VALUE_USERNAME;
539 520
540 521 log_bad_attempts();
541 522 (void) printf("Login failed: unknown user '%s'.\n",
542 523 user_name);
543 524 login_exit(1);
544 525 }
545 -/* ONC_PLUS EXTRACT START */
546 526 } else {
547 527 /*
548 528 * Perform the primary login authentication activity.
549 529 */
550 530 login_authenticate();
551 531 }
552 -/* ONC_PLUS EXTRACT END */
553 532
554 533 /* change root login, then we exec another login and try again */
555 534 if (process_chroot_logins() != OK)
556 535 login_exit(1);
557 536
558 537 /*
559 538 * If root login and not on system console then call exit(2)
560 539 */
561 540 check_for_console();
562 541
563 542 /*
564 543 * Check to see if a shutdown is in progress, if it is and
565 544 * we are not root then throw the user off the system
566 545 */
567 546 if (logins_disabled(user_name) == TRUE) {
568 547 audit_error = ADT_FAIL_VALUE_LOGIN_DISABLED;
569 548 login_exit(1);
570 549 }
571 550
572 551 if (pwd->pw_uid == 0) {
573 552 if (Def_supath != NULL)
574 553 Def_path = Def_supath;
575 554 else
576 555 Def_path = DEF_SUPATH;
577 556 }
578 557
579 558 /*
580 559 * Check account expiration and passwd aging
581 560 */
582 561 validate_account();
583 562
584 563 /*
585 564 * We only get here if we've been authenticated.
586 565 */
587 566
588 567 /*
589 568 * Now we set up the environment for the new user, which includes
590 569 * the users ulimit, nice value, ownership of this tty, uid, gid,
591 570 * and environment variables.
592 571 */
593 572 if (Def_ulimit > 0L && ulimit(SET_FSIZ, Def_ulimit) < 0L)
594 573 (void) printf("Could not set ULIMIT to %ld\n", Def_ulimit);
↓ open down ↓ |
32 lines elided |
↑ open up ↑ |
595 574
596 575 /* di_devperm_login() sends detailed errors to syslog */
597 576 if (di_devperm_login((const char *)ttyn, pwd->pw_uid, pwd->pw_gid,
598 577 NULL) == -1) {
599 578 (void) fprintf(stderr, "error processing /etc/logindevperm,"
600 579 " see syslog for more details\n");
601 580 }
602 581
603 582 adjust_nice(); /* passwd file can specify nice value */
604 583
605 -/* ONC_PLUS EXTRACT START */
606 584 setup_credentials(); /* Set user credentials - exits on failure */
607 585
608 586 /*
609 587 * NOTE: telnetd and rlogind rely upon this updating of utmpx
610 588 * to indicate that the authentication completed successfully,
611 589 * pam_open_session was called and therefore they are required to
612 590 * call pam_close_session.
613 591 */
614 592 update_utmpx_entry(sublogin);
615 593
616 594 /* set the real (and effective) UID */
617 595 if (setuid(pwd->pw_uid) == -1) {
618 596 login_exit(1);
619 597 }
620 598
↓ open down ↓ |
5 lines elided |
↑ open up ↑ |
621 599 /*
622 600 * Set up the basic environment for the exec. This includes
623 601 * HOME, PATH, LOGNAME, SHELL, TERM, TZ, HZ, and MAIL.
624 602 */
625 603 chdir_to_dir_user();
626 604
627 605 establish_user_environment(renvp);
628 606
629 607 (void) pam_end(pamh, PAM_SUCCESS); /* Done using PAM */
630 608 pamh = NULL;
631 -/* ONC_PLUS EXTRACT END */
632 609
633 610 if (pwd->pw_uid == 0) {
634 611 if (dosyslog) {
635 612 if (remote_host[0]) {
636 613 syslog(LOG_NOTICE, "ROOT LOGIN %s FROM %.*s",
637 614 ttyn, HMAX, remote_host);
638 615 } else
639 616 syslog(LOG_NOTICE, "ROOT LOGIN %s", ttyn);
640 617 }
641 618 }
642 619 closelog();
643 620
644 621 (void) signal(SIGQUIT, SIG_DFL);
645 622 (void) signal(SIGINT, SIG_DFL);
646 623
647 624 /*
648 625 * Display some useful information to the new user like the banner
649 626 * and last login time if not a quiet login.
650 627 */
651 628
652 629 if (access(HUSHLOGIN, F_OK) != 0) {
653 630 print_banner();
654 631 display_last_login_time();
655 632 }
656 633
657 634 /*
658 635 * Set SIGXCPU and SIGXFSZ to default disposition.
659 636 * Shells inherit signal disposition from parent.
660 637 * And the shells should have default dispositions
661 638 * for the two below signals.
662 639 */
663 640 (void) signal(SIGXCPU, SIG_DFL);
664 641 (void) signal(SIGXFSZ, SIG_DFL);
665 642
666 643 /*
667 644 * Now fire off the shell of choice
668 645 */
669 646 exec_the_shell();
670 647
671 648 /*
672 649 * All done
673 650 */
674 651 login_exit(1);
↓ open down ↓ |
33 lines elided |
↑ open up ↑ |
675 652 return (0);
676 653 }
677 654
678 655
679 656 /*
680 657 * *** Utility functions ***
681 658 */
682 659
683 660
684 661
685 -/* ONC_PLUS EXTRACT START */
686 662 /*
687 663 * donothing & catch - Signal catching functions
688 664 */
689 665
690 666 /*ARGSUSED*/
691 667 static void
692 668 donothing(int sig)
693 669 {
694 670 if (pamh)
695 671 (void) pam_end(pamh, PAM_ABORT);
696 672 }
697 -/* ONC_PLUS EXTRACT END */
698 673
699 674 #ifdef notdef
700 675 static int intrupt;
701 676
702 677 /*ARGSUSED*/
703 678 static void
704 679 catch(int sig)
705 680 {
706 681 ++intrupt;
707 682 }
708 683 #endif
709 684
710 685 /*
711 686 * *** Bad login logging support ***
712 687 */
713 688
714 689 /*
715 690 * badlogin() - log to the log file 'trys'
716 691 * unsuccessful attempts
717 692 */
718 693
719 694 static void
720 695 badlogin(void)
721 696 {
722 697 int retval, count1, fildes;
723 698
724 699 /*
725 700 * Tries to open the log file. If succeed, lock it and write
726 701 * in the failed attempts
727 702 */
728 703 if ((fildes = open(LOGINLOG, O_APPEND|O_WRONLY)) != -1) {
729 704
730 705 (void) sigset(SIGALRM, donothing);
731 706 (void) alarm(L_WAITTIME);
732 707 retval = lockf(fildes, F_LOCK, 0L);
733 708 (void) alarm(0);
734 709 (void) sigset(SIGALRM, SIG_DFL);
735 710 if (retval == 0) {
736 711 for (count1 = 0; count1 < trys; count1++)
737 712 (void) write(fildes, log_entry[count1],
738 713 (unsigned)strlen(log_entry[count1]));
739 714 (void) lockf(fildes, F_ULOCK, 0L);
740 715 }
741 716 (void) close(fildes);
742 717 }
743 718 }
744 719
745 720
746 721 /*
747 722 * log_bad_attempts - log each bad login attempt - called from
748 723 * login_authenticate. Exits when the maximum attempt
749 724 * count is exceeded.
750 725 */
751 726
752 727 static void
753 728 log_bad_attempts(void)
754 729 {
755 730 time_t timenow;
756 731
757 732 if (trys >= LOGTRYS)
758 733 return;
759 734 if (writelog) {
760 735 (void) time(&timenow);
761 736 (void) strncat(log_entry[trys], user_name, LNAME_SIZE);
762 737 (void) strncat(log_entry[trys], ":", (size_t)1);
763 738 (void) strncat(log_entry[trys], ttyn, TTYN_SIZE);
764 739 (void) strncat(log_entry[trys], ":", (size_t)1);
765 740 (void) strncat(log_entry[trys], ctime(&timenow), TIME_SIZE);
766 741 trys++;
767 742 }
768 743 if (count > flogin) {
769 744 if ((pwd = getpwnam(user_name)) != NULL) {
770 745 if (remote_host[0]) {
771 746 syslog(LOG_NOTICE,
772 747 "Login failure on %s from %.*s, "
773 748 "%.*s", ttyn, HMAX, remote_host,
774 749 NMAX, user_name);
775 750 } else {
776 751 syslog(LOG_NOTICE,
777 752 "Login failure on %s, %.*s",
778 753 ttyn, NMAX, user_name);
779 754 }
780 755 } else {
781 756 if (remote_host[0]) {
782 757 syslog(LOG_NOTICE,
783 758 "Login failure on %s from %.*s",
784 759 ttyn, HMAX, remote_host);
785 760 } else {
786 761 syslog(LOG_NOTICE,
787 762 "Login failure on %s", ttyn);
788 763 }
789 764 }
790 765 }
791 766 }
792 767
793 768
794 769 /*
795 770 * turn_on_logging - if the logfile exist, turn on attempt logging and
796 771 * initialize the string storage area
797 772 */
798 773
799 774 static void
800 775 turn_on_logging(void)
801 776 {
802 777 struct stat dbuf;
803 778 int i;
804 779
805 780 if (stat(LOGINLOG, &dbuf) == 0) {
806 781 writelog = 1;
807 782 for (i = 0; i < LOGTRYS; i++) {
↓ open down ↓ |
100 lines elided |
↑ open up ↑ |
808 783 if (!(log_entry[i] = malloc((size_t)ENT_SIZE))) {
809 784 writelog = 0;
810 785 break;
811 786 }
812 787 *log_entry[i] = '\0';
813 788 }
814 789 }
815 790 }
816 791
817 792
818 -/* ONC_PLUS EXTRACT START */
819 793 /*
820 794 * login_conv():
821 795 * This is the conv (conversation) function called from
822 796 * a PAM authentication module to print error messages
823 797 * or garner information from the user.
824 798 */
825 799 /*ARGSUSED*/
826 800 static int
827 801 login_conv(int num_msg, struct pam_message **msg,
828 802 struct pam_response **response, void *appdata_ptr)
829 803 {
830 804 struct pam_message *m;
831 805 struct pam_response *r;
832 806 char *temp;
833 807 int k, i;
834 808
835 809 if (num_msg <= 0)
836 810 return (PAM_CONV_ERR);
837 811
838 812 *response = calloc(num_msg, sizeof (struct pam_response));
839 813 if (*response == NULL)
840 814 return (PAM_BUF_ERR);
841 815
842 816 k = num_msg;
843 817 m = *msg;
844 818 r = *response;
845 819 while (k--) {
846 820
847 821 switch (m->msg_style) {
848 822
849 823 case PAM_PROMPT_ECHO_OFF:
850 824 errno = 0;
851 825 temp = getpassphrase(m->msg);
852 826 if (temp != NULL) {
853 827 if (errno == EINTR)
854 828 return (PAM_CONV_ERR);
855 829
856 830 r->resp = strdup(temp);
857 831 if (r->resp == NULL) {
858 832 /* free responses */
859 833 r = *response;
860 834 for (i = 0; i < num_msg; i++, r++) {
861 835 if (r->resp)
862 836 free(r->resp);
863 837 }
864 838 free(*response);
865 839 *response = NULL;
866 840 return (PAM_BUF_ERR);
867 841 }
868 842 }
869 843
870 844 m++;
871 845 r++;
872 846 break;
873 847
874 848 case PAM_PROMPT_ECHO_ON:
875 849 if (m->msg != NULL)
876 850 (void) fputs(m->msg, stdout);
877 851 r->resp = calloc(1, PAM_MAX_RESP_SIZE);
878 852 if (r->resp == NULL) {
879 853 /* free responses */
880 854 r = *response;
881 855 for (i = 0; i < num_msg; i++, r++) {
882 856 if (r->resp)
883 857 free(r->resp);
884 858 }
885 859 free(*response);
886 860 *response = NULL;
887 861 return (PAM_BUF_ERR);
888 862 }
889 863 /*
890 864 * The response might include environment variables
891 865 * information. We should store that information in
892 866 * envp if there is any; otherwise, envp is set to
893 867 * NULL.
894 868 */
895 869 bzero((void *)inputline, MAXLINE);
896 870
897 871 envp = getargs(inputline);
898 872
899 873 /* If we read in any input, process it. */
900 874 if (inputline[0] != '\0') {
901 875 int len;
902 876
903 877 if (envp != (char **)NULL)
904 878 /*
905 879 * If getargs() did not return NULL,
906 880 * *envp is the first string in
907 881 * inputline. envp++ makes envp point
908 882 * to environment variables information
909 883 * or be NULL.
910 884 */
911 885 envp++;
912 886
913 887 (void) strncpy(r->resp, inputline,
914 888 PAM_MAX_RESP_SIZE-1);
915 889 r->resp[PAM_MAX_RESP_SIZE-1] = NULL;
916 890 len = strlen(r->resp);
917 891 if (r->resp[len-1] == '\n')
918 892 r->resp[len-1] = '\0';
919 893 } else {
920 894 login_exit(1);
921 895 }
922 896 m++;
923 897 r++;
924 898 break;
925 899
926 900 case PAM_ERROR_MSG:
927 901 if (m->msg != NULL) {
928 902 (void) fputs(m->msg, stderr);
929 903 (void) fputs("\n", stderr);
930 904 }
931 905 m++;
932 906 r++;
933 907 break;
934 908 case PAM_TEXT_INFO:
935 909 if (m->msg != NULL) {
936 910 (void) fputs(m->msg, stdout);
937 911 (void) fputs("\n", stdout);
938 912 }
939 913 m++;
940 914 r++;
941 915 break;
942 916
943 917 default:
944 918 break;
945 919 }
946 920 }
947 921 return (PAM_SUCCESS);
948 922 }
949 923
950 924 /*
951 925 * verify_passwd - Authenticates the user.
952 926 * Returns: PAM_SUCCESS if authentication successful,
953 927 * PAM error code if authentication fails.
954 928 */
955 929
956 930 static int
957 931 verify_passwd(void)
958 932 {
959 933 int error;
960 934 char *user;
961 935 int flag = (Passreqflag ? PAM_DISALLOW_NULL_AUTHTOK : 0);
962 936
963 937 /*
964 938 * PAM authenticates the user for us.
965 939 */
966 940 error = pam_authenticate(pamh, flag);
967 941
968 942 /* get the user_name from the pam handle */
969 943 (void) pam_get_item(pamh, PAM_USER, (void**)&user);
970 944
971 945 if (user == NULL || *user == '\0')
972 946 return (PAM_SYSTEM_ERR);
973 947
↓ open down ↓ |
145 lines elided |
↑ open up ↑ |
974 948 SCPYL(user_name, user);
975 949 check_for_dueling_unix(user_name);
976 950
977 951 if (((pwd = getpwnam(user_name)) == NULL) &&
978 952 (error != PAM_USER_UNKNOWN)) {
979 953 return (PAM_SYSTEM_ERR);
980 954 }
981 955
982 956 return (error);
983 957 }
984 -/* ONC_PLUS EXTRACT END */
985 958
986 959 /*
987 960 * quotec - Called by getargs
988 961 */
989 962
990 963 static int
991 964 quotec(void)
992 965 {
993 966 int c, i, num;
994 967
995 968 switch (c = getc(stdin)) {
996 969
997 970 case 'n':
998 971 c = '\n';
999 972 break;
1000 973
1001 974 case 'r':
1002 975 c = '\r';
1003 976 break;
1004 977
1005 978 case 'v':
1006 979 c = '\013';
1007 980 break;
1008 981
1009 982 case 'b':
1010 983 c = '\b';
1011 984 break;
1012 985
1013 986 case 't':
1014 987 c = '\t';
1015 988 break;
1016 989
1017 990 case 'f':
1018 991 c = '\f';
1019 992 break;
1020 993
1021 994 case '0':
1022 995 case '1':
1023 996 case '2':
1024 997 case '3':
1025 998 case '4':
1026 999 case '5':
1027 1000 case '6':
1028 1001 case '7':
1029 1002 for (num = 0, i = 0; i < 3; i++) {
1030 1003 num = num * 8 + (c - '0');
1031 1004 if ((c = getc(stdin)) < '0' || c > '7')
1032 1005 break;
1033 1006 }
1034 1007 (void) ungetc(c, stdin);
1035 1008 c = num & 0377;
1036 1009 break;
1037 1010
1038 1011 default:
1039 1012 break;
1040 1013 }
1041 1014 return (c);
1042 1015 }
1043 1016
1044 1017 /*
1045 1018 * getargs - returns an input line. Exits if EOF encountered.
1046 1019 */
1047 1020 #define WHITESPACE 0
1048 1021 #define ARGUMENT 1
1049 1022
1050 1023 static char **
1051 1024 getargs(char *input_line)
1052 1025 {
1053 1026 static char envbuf[MAXLINE];
1054 1027 static char *args[MAXARGS];
1055 1028 char *ptr, **answer;
1056 1029 int c;
1057 1030 int state;
1058 1031 char *p = input_line;
1059 1032
1060 1033 ptr = envbuf;
1061 1034 answer = &args[0];
1062 1035 state = WHITESPACE;
1063 1036
1064 1037 while ((c = getc(stdin)) != EOF && answer < &args[MAXARGS-1]) {
1065 1038
1066 1039 *(input_line++) = c;
1067 1040
1068 1041 switch (c) {
1069 1042
1070 1043 case '\n':
1071 1044 if (ptr == &envbuf[0])
1072 1045 return ((char **)NULL);
1073 1046 *input_line = *ptr = '\0';
1074 1047 *answer = NULL;
1075 1048 return (&args[0]);
1076 1049
1077 1050 case ' ':
1078 1051 case '\t':
1079 1052 if (state == ARGUMENT) {
1080 1053 *ptr++ = '\0';
1081 1054 state = WHITESPACE;
1082 1055 }
1083 1056 break;
1084 1057
1085 1058 case '\\':
1086 1059 c = quotec();
1087 1060
1088 1061 default:
1089 1062 if (state == WHITESPACE) {
1090 1063 *answer++ = ptr;
1091 1064 state = ARGUMENT;
1092 1065 }
1093 1066 *ptr++ = c;
1094 1067 }
1095 1068
1096 1069 /* Attempt at overflow, exit */
1097 1070 if (input_line - p >= MAXLINE - 1 ||
1098 1071 ptr >= &envbuf[sizeof (envbuf) - 1]) {
1099 1072 audit_error = ADT_FAIL_VALUE_INPUT_OVERFLOW;
1100 1073 login_exit(1);
1101 1074 }
1102 1075 }
1103 1076
1104 1077 /*
1105 1078 * If we left loop because an EOF was received or we've overflown
1106 1079 * args[], exit immediately.
1107 1080 */
1108 1081 login_exit(0);
1109 1082 /* NOTREACHED */
1110 1083 }
1111 1084
1112 1085 /*
1113 1086 * get_user_name - Gets the user name either passed in, or from the
1114 1087 * login: prompt.
1115 1088 */
1116 1089
1117 1090 static void
1118 1091 get_user_name(void)
1119 1092 {
1120 1093 FILE *fp;
1121 1094
1122 1095 if ((fp = fopen(ISSUEFILE, "r")) != NULL) {
1123 1096 char *ptr, buffer[BUFSIZ];
1124 1097 while ((ptr = fgets(buffer, sizeof (buffer), fp)) != NULL) {
1125 1098 (void) fputs(ptr, stdout);
1126 1099 }
1127 1100 (void) fclose(fp);
1128 1101 }
1129 1102
1130 1103 /*
1131 1104 * if TTYPROMPT is not set, use our own prompt
1132 1105 * otherwise, use ttyprompt. We just set PAM_USER_PROMPT
1133 1106 * and let the module do the prompting.
1134 1107 */
1135 1108
1136 1109 if ((ttyprompt == NULL) || (*ttyprompt == '\0'))
1137 1110 (void) pam_set_item(pamh, PAM_USER_PROMPT, (void *)loginmsg);
1138 1111 else
1139 1112 (void) pam_set_item(pamh, PAM_USER_PROMPT, (void *)ttyprompt);
1140 1113
1141 1114 envp = &zero; /* XXX: is this right? */
1142 1115 }
1143 1116
1144 1117
1145 1118 /*
1146 1119 * Check_for_dueling_unix - Check to see if the another login is talking
1147 1120 * to the line we've got open as a login port
1148 1121 * Exits if we're talking to another unix system
1149 1122 */
1150 1123
1151 1124 static void
1152 1125 check_for_dueling_unix(char *inputline)
1153 1126 {
1154 1127 if (EQN(loginmsg, inputline) || EQN(passwdmsg, inputline) ||
1155 1128 EQN(incorrectmsg, inputline)) {
1156 1129 (void) printf("Looking at a login line.\n");
1157 1130 login_exit(8);
1158 1131 }
1159 1132 }
1160 1133
1161 1134 /*
1162 1135 * logins_disabled - if the file /etc/nologin exists and the user is not
1163 1136 * root then do not permit them to login
1164 1137 */
1165 1138 static int
1166 1139 logins_disabled(char *user_name)
1167 1140 {
1168 1141 FILE *nlfd;
1169 1142 int c;
1170 1143 if (!EQN("root", user_name) &&
1171 1144 ((nlfd = fopen(NOLOGIN, "r")) != (FILE *)NULL)) {
1172 1145 while ((c = getc(nlfd)) != EOF)
1173 1146 (void) putchar(c);
1174 1147 (void) fflush(stdout);
1175 1148 (void) sleep(5);
1176 1149 return (TRUE);
1177 1150 }
1178 1151 return (FALSE);
1179 1152 }
1180 1153
1181 1154 #define DEFAULT_CONSOLE "/dev/console"
1182 1155
1183 1156 /*
1184 1157 * check_for_console - Checks if we're getting a root login on the
1185 1158 * console, or a login from the global zone. Exits if not.
1186 1159 *
1187 1160 * If CONSOLE is set to /dev/console in /etc/default/login, then root logins
1188 1161 * on /dev/vt/# are permitted as well. /dev/vt/# does not exist in non-global
1189 1162 * zones, but checking them does no harm.
1190 1163 */
1191 1164 static void
1192 1165 check_for_console(void)
1193 1166 {
1194 1167 const char *consoles[] = { "/dev/console", "/dev/vt/", NULL };
1195 1168 int i;
1196 1169
1197 1170 if (pwd == NULL || pwd->pw_uid != 0 || zflag != B_FALSE ||
1198 1171 Console == NULL)
1199 1172 return;
1200 1173
1201 1174 if (strcmp(Console, DEFAULT_CONSOLE) == 0) {
1202 1175 for (i = 0; consoles[i] != NULL; i ++) {
1203 1176 if (strncmp(ttyn, consoles[i],
1204 1177 strlen(consoles[i])) == 0)
1205 1178 return;
1206 1179 }
1207 1180 } else {
1208 1181 if (strcmp(ttyn, Console) == 0)
1209 1182 return;
1210 1183 }
1211 1184
1212 1185 (void) printf("Not on system console\n");
1213 1186
1214 1187 audit_error = ADT_FAIL_VALUE_CONSOLE;
1215 1188 login_exit(10);
1216 1189
1217 1190 }
1218 1191
1219 1192 /*
1220 1193 * List of environment variables or environment variable prefixes that should
1221 1194 * not be propagated across logins, such as when the login -p option is used.
1222 1195 */
1223 1196 static const char *const illegal[] = {
1224 1197 "SHELL=",
1225 1198 "HOME=",
1226 1199 "LOGNAME=",
1227 1200 #ifndef NO_MAIL
1228 1201 "MAIL=",
1229 1202 #endif
1230 1203 "CDPATH=",
1231 1204 "IFS=",
1232 1205 "PATH=",
1233 1206 "LD_",
1234 1207 "SMF_",
1235 1208 NULL
1236 1209 };
1237 1210
1238 1211 /*
1239 1212 * legalenvvar - Is it legal to insert this environmental variable?
1240 1213 */
1241 1214
1242 1215 static int
1243 1216 legalenvvar(char *s)
1244 1217 {
1245 1218 const char *const *p;
1246 1219
1247 1220 for (p = &illegal[0]; *p; p++) {
1248 1221 if (strncmp(s, *p, strlen(*p)) == 0)
1249 1222 return (0);
1250 1223 }
1251 1224
1252 1225 return (1);
1253 1226 }
1254 1227
1255 1228
1256 1229 /*
1257 1230 * getstr - Get a string from standard input
1258 1231 * Calls exit if read(2) fails.
1259 1232 */
1260 1233
1261 1234 static void
1262 1235 getstr(char *buf, int cnt, char *err)
1263 1236 {
1264 1237 char c;
1265 1238
1266 1239 do {
1267 1240 if (read(0, &c, 1) != 1)
1268 1241 login_exit(1);
1269 1242 *buf++ = c;
1270 1243 } while (--cnt > 1 && c != 0);
1271 1244
1272 1245 *buf = 0;
1273 1246 err = err; /* For lint */
1274 1247 }
1275 1248
1276 1249
1277 1250 /*
1278 1251 * defaults - read defaults
1279 1252 */
1280 1253
1281 1254 static void
1282 1255 defaults(void)
1283 1256 {
1284 1257 int flags;
1285 1258 char *ptr;
1286 1259
1287 1260 if (defopen(Pndefault) == 0) {
1288 1261 /*
1289 1262 * ignore case
1290 1263 */
1291 1264 flags = defcntl(DC_GETFLAGS, 0);
1292 1265 TURNOFF(flags, DC_CASE);
1293 1266 (void) defcntl(DC_SETFLAGS, flags);
1294 1267
1295 1268 if ((Console = defread("CONSOLE=")) != NULL)
1296 1269 Console = strdup(Console);
1297 1270
1298 1271 if ((Altshell = defread("ALTSHELL=")) != NULL)
1299 1272 Altshell = strdup(Altshell);
1300 1273
1301 1274 if ((ptr = defread("PASSREQ=")) != NULL &&
1302 1275 strcasecmp("YES", ptr) == 0)
1303 1276 Passreqflag = 1;
1304 1277
1305 1278 if ((Def_tz = defread("TIMEZONE=")) != NULL)
1306 1279 Def_tz = strdup(Def_tz);
1307 1280
1308 1281 if ((Def_hertz = defread("HZ=")) != NULL)
1309 1282 Def_hertz = strdup(Def_hertz);
1310 1283
1311 1284 if ((Def_path = defread("PATH=")) != NULL)
1312 1285 Def_path = strdup(Def_path);
1313 1286
1314 1287 if ((Def_supath = defread("SUPATH=")) != NULL)
1315 1288 Def_supath = strdup(Def_supath);
1316 1289
1317 1290 if ((ptr = defread("ULIMIT=")) != NULL)
1318 1291 Def_ulimit = atol(ptr);
1319 1292
1320 1293 if ((ptr = defread("TIMEOUT=")) != NULL)
1321 1294 Def_timeout = (unsigned)atoi(ptr);
1322 1295
1323 1296 if ((ptr = defread("UMASK=")) != NULL)
1324 1297 if (sscanf(ptr, "%lo", &Umask) != 1)
1325 1298 Umask = DEFUMASK;
1326 1299
1327 1300 if ((ptr = defread("SLEEPTIME=")) != NULL) {
1328 1301 if (is_number(ptr))
1329 1302 Sleeptime = atoi(ptr);
1330 1303 }
1331 1304
1332 1305 if ((ptr = defread("DISABLETIME=")) != NULL) {
1333 1306 if (is_number(ptr))
1334 1307 Disabletime = atoi(ptr);
1335 1308 }
1336 1309
1337 1310 if ((ptr = defread("SYSLOG=")) != NULL)
1338 1311 dosyslog = strcmp(ptr, "YES") == 0;
1339 1312
1340 1313 if ((ptr = defread("RETRIES=")) != NULL) {
1341 1314 if (is_number(ptr))
1342 1315 retry = atoi(ptr);
1343 1316 }
1344 1317
1345 1318 if ((ptr = defread("SYSLOG_FAILED_LOGINS=")) != NULL) {
1346 1319 if (is_number(ptr))
1347 1320 flogin = atoi(ptr);
1348 1321 else
1349 1322 flogin = retry;
1350 1323 } else
1351 1324 flogin = retry;
1352 1325 (void) defopen((char *)NULL);
1353 1326 }
1354 1327 }
1355 1328
1356 1329
1357 1330 /*
1358 1331 * get_options(argc, argv)
1359 1332 * - parse the cmd line.
1360 1333 * - return 0 if successful, -1 if failed.
1361 1334 * Calls login_exit() on misuse of -r, -h, and -z flags
1362 1335 */
1363 1336
1364 1337 static int
1365 1338 get_options(int argc, char *argv[])
1366 1339 {
1367 1340 int c;
1368 1341 int errflg = 0;
1369 1342 char sflagname[NMAX+1];
1370 1343 const char *flags_message = "Only one of -r, -h and -z allowed\n";
1371 1344
1372 1345 while ((c = getopt(argc, argv, "u:s:R:f:h:r:pad:t:U:z:")) != -1) {
1373 1346 switch (c) {
1374 1347 case 'a':
1375 1348 break;
1376 1349
1377 1350 case 'd':
1378 1351 /*
1379 1352 * Must be root to pass in device name
1380 1353 * otherwise we exit() as punishment for trying.
1381 1354 */
1382 1355 if (getuid() != 0 || geteuid() != 0) {
1383 1356 audit_error = ADT_FAIL_VALUE_DEVICE_PERM;
1384 1357 login_exit(1); /* sigh */
1385 1358 /*NOTREACHED*/
1386 1359 }
1387 1360 ttyn = optarg;
1388 1361 break;
1389 1362
1390 1363 case 'h':
1391 1364 if (hflag || rflag || zflag) {
1392 1365 (void) fprintf(stderr, flags_message);
1393 1366 login_exit(1);
1394 1367 }
1395 1368 hflag = B_TRUE;
1396 1369 SCPYL(remote_host, optarg);
1397 1370 if (argv[optind]) {
1398 1371 if (argv[optind][0] != '-') {
1399 1372 SCPYL(terminal, argv[optind]);
1400 1373 optind++;
1401 1374 } else {
1402 1375 /*
1403 1376 * Allow "login -h hostname -" to
1404 1377 * skip setting up an username as "-".
1405 1378 */
1406 1379 if (argv[optind][1] == '\0')
1407 1380 optind++;
1408 1381 }
1409 1382
1410 1383 }
1411 1384 SCPYL(progname, "telnet");
1412 1385 break;
1413 1386
1414 1387 case 'r':
1415 1388 if (hflag || rflag || zflag) {
1416 1389 (void) fprintf(stderr, flags_message);
1417 1390 login_exit(1);
1418 1391 }
1419 1392 rflag = B_TRUE;
1420 1393 SCPYL(remote_host, optarg);
1421 1394 SCPYL(progname, "rlogin");
1422 1395 break;
1423 1396
1424 1397 case 'p':
1425 1398 pflag = B_TRUE;
1426 1399 break;
1427 1400
1428 1401 case 'f':
1429 1402 /*
1430 1403 * Must be root to bypass authentication
1431 1404 * otherwise we exit() as punishment for trying.
1432 1405 */
1433 1406 if (getuid() != 0 || geteuid() != 0) {
1434 1407 audit_error = ADT_FAIL_VALUE_AUTH_BYPASS;
1435 1408
1436 1409 login_exit(1); /* sigh */
1437 1410 /*NOTREACHED*/
1438 1411 }
1439 1412 /* save fflag user name for future use */
1440 1413 SCPYL(user_name, optarg);
1441 1414 fflag = B_TRUE;
1442 1415 break;
1443 1416 case 'u':
1444 1417 if (!strlen(optarg)) {
1445 1418 (void) fprintf(stderr,
1446 1419 "Empty string supplied with -u\n");
1447 1420 login_exit(1);
1448 1421 }
1449 1422 SCPYL(identity, optarg);
1450 1423 uflag = B_TRUE;
1451 1424 break;
1452 1425 case 's':
1453 1426 if (!strlen(optarg)) {
1454 1427 (void) fprintf(stderr,
1455 1428 "Empty string supplied with -s\n");
1456 1429 login_exit(1);
1457 1430 }
1458 1431 SCPYL(sflagname, optarg);
1459 1432 sflag = B_TRUE;
1460 1433 break;
1461 1434 case 'R':
1462 1435 if (!strlen(optarg)) {
1463 1436 (void) fprintf(stderr,
1464 1437 "Empty string supplied with -R\n");
1465 1438 login_exit(1);
1466 1439 }
1467 1440 SCPYL(repository, optarg);
1468 1441 Rflag = B_TRUE;
1469 1442 break;
1470 1443 case 't':
1471 1444 if (!strlen(optarg)) {
1472 1445 (void) fprintf(stderr,
1473 1446 "Empty string supplied with -t\n");
1474 1447 login_exit(1);
1475 1448 }
1476 1449 SCPYL(terminal, optarg);
1477 1450 tflag = B_TRUE;
1478 1451 break;
1479 1452 case 'U':
1480 1453 /*
1481 1454 * Kerberized rlogind may fork us with
1482 1455 * -U "" if the rlogin client used the "-a"
1483 1456 * option to send a NULL username. This is done
1484 1457 * to force login to prompt for a user/password.
1485 1458 * However, if Kerberos auth was used, we dont need
1486 1459 * to prompt, so we will accept the option and
1487 1460 * handle the situation later.
1488 1461 */
1489 1462 SCPYL(rusername, optarg);
1490 1463 Uflag = B_TRUE;
1491 1464 break;
1492 1465 case 'z':
1493 1466 if (hflag || rflag || zflag) {
1494 1467 (void) fprintf(stderr, flags_message);
1495 1468 login_exit(1);
1496 1469 }
1497 1470 (void) snprintf(zone_name, sizeof (zone_name),
1498 1471 "zone:%s", optarg);
1499 1472 SCPYL(progname, "zlogin");
1500 1473 zflag = B_TRUE;
1501 1474 break;
1502 1475 default:
1503 1476 errflg++;
1504 1477 break;
1505 1478 } /* end switch */
1506 1479 } /* end while */
1507 1480
1508 1481 /*
1509 1482 * If the 's svcname' flag was used, override the progname
1510 1483 * value that is to be used in the pam_start call.
1511 1484 */
1512 1485 if (sflag)
1513 1486 SCPYL(progname, sflagname);
1514 1487
1515 1488 /*
1516 1489 * get the prompt set by ttymon
1517 1490 */
1518 1491 ttyprompt = getenv("TTYPROMPT");
1519 1492
1520 1493 if ((ttyprompt != NULL) && (*ttyprompt != '\0')) {
1521 1494 /*
1522 1495 * if ttyprompt is set, there should be data on
1523 1496 * the stream already.
1524 1497 */
1525 1498 if ((envp = getargs(inputline)) != (char **)NULL) {
1526 1499 /*
1527 1500 * don't get name if name passed as argument.
1528 1501 */
1529 1502 SCPYL(user_name, *envp++);
1530 1503 }
1531 1504 } else if (optind < argc) {
1532 1505 SCPYL(user_name, argv[optind]);
1533 1506 (void) SCPYL(inputline, user_name);
1534 1507 (void) strlcat(inputline, " \n", sizeof (inputline));
1535 1508 envp = &argv[optind+1];
1536 1509
1537 1510 if (!fflag)
1538 1511 SCPYL(lusername, user_name);
1539 1512 }
1540 1513
1541 1514 if (errflg)
1542 1515 return (-1);
1543 1516 return (0);
1544 1517 }
1545 1518
1546 1519 /*
1547 1520 * usage - Print usage message
1548 1521 *
1549 1522 */
1550 1523 static void
1551 1524 usage(void)
1552 1525 {
1553 1526 (void) fprintf(stderr,
1554 1527 "usage:\n"
1555 1528 " login [-p] [-d device] [-R repository] [-s service]\n"
1556 1529 "\t[-t terminal] [-u identity] [-U ruser]\n"
1557 1530 "\t[-h hostname [terminal] | -r hostname] [name [environ]...]\n");
1558 1531
1559 1532 }
1560 1533
1561 1534 /*
1562 1535 * doremoteterm - Sets the appropriate ioctls for a remote terminal
1563 1536 */
1564 1537 static char *speeds[] = {
1565 1538 "0", "50", "75", "110", "134", "150", "200", "300",
1566 1539 "600", "1200", "1800", "2400", "4800", "9600", "19200", "38400",
1567 1540 "57600", "76800", "115200", "153600", "230400", "307200", "460800",
1568 1541 "921600"
1569 1542 };
1570 1543
1571 1544 #define NSPEEDS (sizeof (speeds) / sizeof (speeds[0]))
1572 1545
1573 1546
1574 1547 static void
1575 1548 doremoteterm(char *term)
1576 1549 {
1577 1550 struct termios tp;
1578 1551 char *cp = strchr(term, '/'), **cpp;
1579 1552 char *speed;
1580 1553
1581 1554 (void) ioctl(0, TCGETS, &tp);
1582 1555
1583 1556 if (cp) {
1584 1557 *cp++ = '\0';
1585 1558 speed = cp;
1586 1559 cp = strchr(speed, '/');
1587 1560
1588 1561 if (cp)
1589 1562 *cp++ = '\0';
1590 1563
1591 1564 for (cpp = speeds; cpp < &speeds[NSPEEDS]; cpp++)
1592 1565 if (strcmp(*cpp, speed) == 0) {
1593 1566 (void) cfsetospeed(&tp, cpp-speeds);
1594 1567 break;
1595 1568 }
1596 1569 }
1597 1570
1598 1571 tp.c_lflag |= ECHO|ICANON;
1599 1572 tp.c_iflag |= IGNPAR|ICRNL;
1600 1573
1601 1574 (void) ioctl(0, TCSETS, &tp);
1602 1575
1603 1576 }
1604 1577
1605 1578 /*
1606 1579 * Process_rlogin - Does the work that rlogin and telnet
1607 1580 * need done
1608 1581 */
1609 1582 static void
1610 1583 process_rlogin(void)
1611 1584 {
1612 1585 /*
1613 1586 * If a Kerberized rlogin was initiated, then these fields
1614 1587 * must be read by rlogin daemon itself and passed down via
1615 1588 * cmd line args.
1616 1589 */
1617 1590 if (!Uflag && !strlen(rusername))
1618 1591 getstr(rusername, sizeof (rusername), "remuser");
1619 1592 if (!strlen(lusername))
1620 1593 getstr(lusername, sizeof (lusername), "locuser");
1621 1594 if (!tflag && !strlen(terminal))
1622 1595 getstr(terminal, sizeof (terminal), "Terminal type");
1623 1596
1624 1597 if (strlen(terminal))
1625 1598 doremoteterm(terminal);
1626 1599
1627 1600 /* fflag has precedence over stuff passed by rlogind */
1628 1601 if (fflag || getuid()) {
1629 1602 pwd = &nouser;
1630 1603 return;
1631 1604 } else {
1632 1605 if (pam_set_item(pamh, PAM_USER, lusername) != PAM_SUCCESS)
1633 1606 login_exit(1);
1634 1607
1635 1608 pwd = getpwnam(lusername);
1636 1609 if (pwd == NULL) {
1637 1610 pwd = &nouser;
1638 1611 return;
1639 1612 }
1640 1613 }
1641 1614
1642 1615 /*
1643 1616 * Update PAM on the user name
1644 1617 */
1645 1618 if (strlen(lusername) &&
1646 1619 pam_set_item(pamh, PAM_USER, lusername) != PAM_SUCCESS)
1647 1620 login_exit(1);
1648 1621
1649 1622 if (strlen(rusername) &&
1650 1623 pam_set_item(pamh, PAM_RUSER, rusername) != PAM_SUCCESS)
1651 1624 login_exit(1);
1652 1625
1653 1626 SCPYL(user_name, lusername);
1654 1627 envp = &zero;
1655 1628 lusername[0] = '\0';
1656 1629 }
1657 1630
1658 1631 /*
1659 1632 * *** Account validation routines ***
1660 1633 *
1661 1634 */
1662 1635
1663 1636 /*
1664 1637 * validate_account - This is the PAM version of validate.
1665 1638 */
1666 1639
1667 1640 static void
1668 1641 validate_account(void)
1669 1642 {
1670 1643 int error;
1671 1644 int flag;
1672 1645 int tries; /* new password retries */
1673 1646
1674 1647 (void) alarm(0); /* give user time to come up with password */
1675 1648
1676 1649 check_log();
1677 1650
1678 1651 if (Passreqflag)
1679 1652 flag = PAM_DISALLOW_NULL_AUTHTOK;
1680 1653 else
1681 1654 flag = 0;
1682 1655
1683 1656 if ((error = pam_acct_mgmt(pamh, flag)) != PAM_SUCCESS) {
1684 1657 if (error == PAM_NEW_AUTHTOK_REQD) {
1685 1658 tries = 1;
1686 1659 error = PAM_AUTHTOK_ERR;
1687 1660 while (error == PAM_AUTHTOK_ERR &&
1688 1661 tries <= DEF_ATTEMPTS) {
1689 1662 if (tries > 1)
1690 1663 (void) printf("Try again\n\n");
1691 1664
1692 1665 (void) printf("Choose a new password.\n");
1693 1666
1694 1667 error = pam_chauthtok(pamh,
1695 1668 PAM_CHANGE_EXPIRED_AUTHTOK);
1696 1669 if (error == PAM_TRY_AGAIN) {
1697 1670 (void) sleep(1);
1698 1671 error = pam_chauthtok(pamh,
1699 1672 PAM_CHANGE_EXPIRED_AUTHTOK);
1700 1673 }
1701 1674 tries++;
1702 1675 }
1703 1676
1704 1677 if (error != PAM_SUCCESS) {
1705 1678 if (dosyslog)
1706 1679 syslog(LOG_CRIT,
1707 1680 "change password failure: %s",
1708 1681 pam_strerror(pamh, error));
1709 1682 audit_error = ADT_FAIL_PAM + error;
1710 1683 login_exit(1);
1711 1684 } else {
1712 1685 audit_success(ADT_passwd, pwd, zone_name);
1713 1686 }
1714 1687 } else {
1715 1688 (void) printf(incorrectmsg);
1716 1689
1717 1690 if (dosyslog)
1718 1691 syslog(LOG_CRIT,
1719 1692 "login account failure: %s",
1720 1693 pam_strerror(pamh, error));
1721 1694 audit_error = ADT_FAIL_PAM + error;
1722 1695 login_exit(1);
1723 1696 }
1724 1697 }
1725 1698 }
1726 1699
1727 1700 /*
1728 1701 * Check_log - This is really a hack because PAM checks the log, but login
1729 1702 * wants to know if the log is okay and PAM doesn't have
1730 1703 * a module independent way of handing this info back.
1731 1704 */
1732 1705
1733 1706 static void
1734 1707 check_log(void)
1735 1708 {
1736 1709 int fdl;
1737 1710 long long offset;
1738 1711
1739 1712 offset = (long long) pwd->pw_uid * (long long) sizeof (struct lastlog);
1740 1713
1741 1714 if ((fdl = open(LASTLOG, O_RDWR|O_CREAT, 0444)) >= 0) {
1742 1715 if (llseek(fdl, offset, SEEK_SET) == offset &&
1743 1716 read(fdl, (char *)&ll, sizeof (ll)) == sizeof (ll) &&
1744 1717 ll.ll_time != 0)
1745 1718 lastlogok = 1;
1746 1719 (void) close(fdl);
1747 1720 }
1748 1721 }
1749 1722
1750 1723 /*
1751 1724 * chdir_to_dir_user - Now chdir after setuid/setgid have happened to
1752 1725 * place us in the user's home directory just in
1753 1726 * case it was protected and the first chdir failed.
1754 1727 * No chdir errors should happen at this point because
1755 1728 * all failures should have happened on the first
1756 1729 * time around.
1757 1730 */
1758 1731
1759 1732 static void
1760 1733 chdir_to_dir_user(void)
1761 1734 {
1762 1735 if (chdir(pwd->pw_dir) < 0) {
1763 1736 if (chdir("/") < 0) {
1764 1737 (void) printf("No directory!\n");
1765 1738 /*
1766 1739 * This probably won't work since we can't get to /.
1767 1740 */
1768 1741 if (dosyslog) {
1769 1742 if (remote_host[0]) {
1770 1743 syslog(LOG_CRIT,
1771 1744 "LOGIN FAILURES ON %s FROM %.*s ",
1772 1745 " %.*s", ttyn, HMAX,
1773 1746 remote_host, NMAX, pwd->pw_name);
1774 1747 } else {
1775 1748 syslog(LOG_CRIT,
1776 1749 "LOGIN FAILURES ON %s, %.*s",
1777 1750 ttyn, NMAX, pwd->pw_name);
1778 1751 }
1779 1752 }
1780 1753 closelog();
↓ open down ↓ |
786 lines elided |
↑ open up ↑ |
1781 1754 (void) sleep(Disabletime);
1782 1755 exit(1);
1783 1756 } else {
1784 1757 (void) printf("No directory! Logging in with home=/\n");
1785 1758 pwd->pw_dir = "/";
1786 1759 }
1787 1760 }
1788 1761 }
1789 1762
1790 1763
1791 -/* ONC_PLUS EXTRACT START */
1792 1764 /*
1793 1765 * login_authenticate - Performs the main authentication work
1794 1766 * 1. Prints the login prompt
1795 1767 * 2. Requests and verifys the password
1796 1768 * 3. Checks the port password
1797 1769 */
1798 1770
1799 1771 static void
1800 1772 login_authenticate(void)
1801 1773 {
1802 1774 char *user;
1803 1775 int err;
1804 1776 int login_successful = 0;
1805 1777
1806 1778 do {
1807 1779 /* if scheme broken, then nothing to do but quit */
1808 1780 if (pam_get_item(pamh, PAM_USER, (void **)&user) != PAM_SUCCESS)
1809 1781 exit(1);
1810 1782
1811 1783 /*
1812 1784 * only get name from utility if it is not already
1813 1785 * supplied by pam_start or a pam_set_item.
1814 1786 */
1815 1787 if (!user || !user[0]) {
1816 1788 /* use call back to get user name */
1817 1789 get_user_name();
1818 1790 }
1819 1791
1820 1792 err = verify_passwd();
1821 1793
1822 1794 /*
1823 1795 * If root login and not on system console then call exit(2)
1824 1796 */
1825 1797 check_for_console();
1826 1798
1827 1799 switch (err) {
1828 1800 case PAM_SUCCESS:
1829 1801 case PAM_NEW_AUTHTOK_REQD:
1830 1802 /*
1831 1803 * Officially, pam_authenticate() shouldn't return this
1832 1804 * but it's probably the right thing to return if
1833 1805 * PAM_DISALLOW_NULL_AUTHTOK is set so the user will
1834 1806 * be forced to change password later in this code.
1835 1807 */
1836 1808 count = 0;
1837 1809 login_successful = 1;
1838 1810 break;
1839 1811 case PAM_MAXTRIES:
1840 1812 count = retry;
1841 1813 /*FALLTHROUGH*/
1842 1814 case PAM_AUTH_ERR:
1843 1815 case PAM_AUTHINFO_UNAVAIL:
1844 1816 case PAM_USER_UNKNOWN:
1845 1817 audit_failure(get_audit_id(), ADT_FAIL_PAM + err, pwd,
1846 1818 remote_host, ttyn, zone_name);
1847 1819 log_bad_attempts();
1848 1820 break;
1849 1821 case PAM_ABORT:
1850 1822 log_bad_attempts();
1851 1823 (void) sleep(Disabletime);
1852 1824 (void) printf(incorrectmsg);
1853 1825
1854 1826 audit_error = ADT_FAIL_PAM + err;
1855 1827 login_exit(1);
1856 1828 /*NOTREACHED*/
1857 1829 default: /* Some other PAM error */
1858 1830 audit_error = ADT_FAIL_PAM + err;
1859 1831 login_exit(1);
1860 1832 /*NOTREACHED*/
1861 1833 }
1862 1834
1863 1835 if (login_successful)
1864 1836 break;
1865 1837
1866 1838 /* sleep after bad passwd */
1867 1839 if (count)
1868 1840 (void) sleep(Sleeptime);
1869 1841 (void) printf(incorrectmsg);
1870 1842 /* force name to be null in this case */
1871 1843 if (pam_set_item(pamh, PAM_USER, NULL) != PAM_SUCCESS)
1872 1844 login_exit(1);
1873 1845 if (pam_set_item(pamh, PAM_RUSER, NULL) != PAM_SUCCESS)
1874 1846 login_exit(1);
1875 1847 } while (count++ < retry);
1876 1848
1877 1849 if (count >= retry) {
1878 1850 audit_failure(get_audit_id(), ADT_FAIL_VALUE_MAX_TRIES, pwd,
1879 1851 remote_host, ttyn, zone_name);
1880 1852 /*
1881 1853 * If logging is turned on, output the
1882 1854 * string storage area to the log file,
1883 1855 * and sleep for Disabletime
1884 1856 * seconds before exiting.
1885 1857 */
1886 1858 if (writelog)
1887 1859 badlogin();
1888 1860 if (dosyslog) {
1889 1861 if ((pwd = getpwnam(user_name)) != NULL) {
1890 1862 if (remote_host[0]) {
1891 1863 syslog(LOG_CRIT,
1892 1864 "REPEATED LOGIN FAILURES ON %s "
1893 1865 "FROM %.*s, %.*s",
1894 1866 ttyn, HMAX, remote_host, NMAX,
1895 1867 user_name);
1896 1868 } else {
1897 1869 syslog(LOG_CRIT,
1898 1870 "REPEATED LOGIN FAILURES ON "
1899 1871 "%s, %.*s",
1900 1872 ttyn, NMAX, user_name);
1901 1873 }
1902 1874 } else {
1903 1875 if (remote_host[0]) {
1904 1876 syslog(LOG_CRIT,
1905 1877 "REPEATED LOGIN FAILURES ON %s "
1906 1878 "FROM %.*s",
1907 1879 ttyn, HMAX, remote_host);
1908 1880 } else {
1909 1881 syslog(LOG_CRIT,
1910 1882 "REPEATED LOGIN FAILURES ON %s",
1911 1883 ttyn);
1912 1884 }
1913 1885 }
1914 1886 }
1915 1887 (void) sleep(Disabletime);
1916 1888 exit(1);
1917 1889 }
1918 1890
1919 1891 }
1920 1892
1921 1893 /*
1922 1894 * *** Credential Related routines ***
1923 1895 *
1924 1896 */
1925 1897
1926 1898 /*
1927 1899 * setup_credentials - sets the group ID, initializes the groups
1928 1900 * and sets up the secretkey.
1929 1901 * Exits if a failure occurrs.
1930 1902 */
1931 1903
1932 1904
1933 1905 /*
1934 1906 * setup_credentials - PAM does all the work for us on this one.
1935 1907 */
1936 1908
1937 1909 static void
1938 1910 setup_credentials(void)
1939 1911 {
1940 1912 int error = 0;
1941 1913
1942 1914 /* set the real (and effective) GID */
1943 1915 if (setgid(pwd->pw_gid) == -1) {
1944 1916 login_exit(1);
1945 1917 }
1946 1918
1947 1919 /*
1948 1920 * Initialize the supplementary group access list.
1949 1921 */
1950 1922 if ((user_name[0] == '\0') ||
1951 1923 (initgroups(user_name, pwd->pw_gid) == -1)) {
1952 1924 audit_error = ADT_FAIL_VALUE_PROGRAM;
1953 1925 login_exit(1);
1954 1926 }
1955 1927
1956 1928 if ((error = pam_setcred(pamh, zflag ? PAM_REINITIALIZE_CRED :
1957 1929 PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
1958 1930 audit_error = ADT_FAIL_PAM + error;
1959 1931 login_exit(error);
↓ open down ↓ |
158 lines elided |
↑ open up ↑ |
1960 1932 }
1961 1933
1962 1934 /*
1963 1935 * Record successful login and fork process that records logout.
1964 1936 * We have to do this after setting credentials because pam_setcred()
1965 1937 * loads key audit info into the cred, but before setuid() so audit
1966 1938 * system calls will work.
1967 1939 */
1968 1940 audit_success(get_audit_id(), pwd, zone_name);
1969 1941 }
1970 -/* ONC_PLUS EXTRACT END */
1971 1942
1972 1943 static uint_t
1973 1944 get_audit_id(void)
1974 1945 {
1975 1946 if (rflag)
1976 1947 return (ADT_rlogin);
1977 1948 else if (hflag)
1978 1949 return (ADT_telnet);
1979 1950 else if (zflag)
1980 1951 return (ADT_zlogin);
1981 1952
1982 1953 return (ADT_login);
1983 1954 }
1984 1955
1985 1956 /*
1986 1957 *
1987 1958 * *** Routines to get a new user set up and running ***
1988 1959 *
1989 1960 * Things to do when starting up a new user:
1990 1961 * adjust_nice
1991 1962 * update_utmpx_entry
1992 1963 * establish_user_environment
1993 1964 * print_banner
1994 1965 * display_last_login_time
1995 1966 * exec_the_shell
1996 1967 *
1997 1968 */
1998 1969
1999 1970
2000 1971 /*
2001 1972 * adjust_nice - Set the nice (process priority) value if the
2002 1973 * gecos value contains an appropriate value.
2003 1974 */
2004 1975
2005 1976 static void
2006 1977 adjust_nice(void)
2007 1978 {
2008 1979 int pri, mflg, i;
2009 1980
2010 1981 if (strncmp("pri=", pwd->pw_gecos, 4) == 0) {
2011 1982 pri = 0;
2012 1983 mflg = 0;
2013 1984 i = 4;
2014 1985
2015 1986 if (pwd->pw_gecos[i] == '-') {
2016 1987 mflg++;
2017 1988 i++;
2018 1989 }
2019 1990
↓ open down ↓ |
39 lines elided |
↑ open up ↑ |
2020 1991 while (pwd->pw_gecos[i] >= '0' && pwd->pw_gecos[i] <= '9')
2021 1992 pri = (pri * 10) + pwd->pw_gecos[i++] - '0';
2022 1993
2023 1994 if (mflg)
2024 1995 pri = -pri;
2025 1996
2026 1997 (void) nice(pri);
2027 1998 }
2028 1999 }
2029 2000
2030 -/* ONC_PLUS EXTRACT START */
2031 2001 /*
2032 2002 * update_utmpx_entry - Searchs for the correct utmpx entry, making an
2033 2003 * entry there if it finds one, otherwise exits.
2034 2004 */
2035 2005
2036 2006 static void
2037 2007 update_utmpx_entry(int sublogin)
2038 2008 {
2039 2009 int err;
2040 2010 char *user;
2041 2011 static char *errmsg = "No utmpx entry. "
2042 2012 "You must exec \"login\" from the lowest level \"shell\".";
2043 2013 int tmplen;
2044 2014 struct utmpx *u = (struct utmpx *)0;
2045 2015 struct utmpx utmpx;
2046 2016 char *ttyntail;
2047 2017
2048 2018 /*
2049 2019 * If we're not a sublogin then
2050 2020 * we'll get an error back if our PID doesn't match the PID of the
2051 2021 * entry we are updating, otherwise if its a sublogin the flags
2052 2022 * field is set to 0, which means we just write a matching entry
2053 2023 * (without checking the pid), or a new entry if an entry doesn't
2054 2024 * exist.
2055 2025 */
2056 2026
↓ open down ↓ |
16 lines elided |
↑ open up ↑ |
2057 2027 if ((err = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
2058 2028 audit_error = ADT_FAIL_PAM + err;
2059 2029 login_exit(1);
2060 2030 }
2061 2031
2062 2032 if ((err = pam_get_item(pamh, PAM_USER, (void **) &user)) !=
2063 2033 PAM_SUCCESS) {
2064 2034 audit_error = ADT_FAIL_PAM + err;
2065 2035 login_exit(1);
2066 2036 }
2067 -/* ONC_PLUS EXTRACT END */
2068 2037
2069 2038 (void) memset((void *)&utmpx, 0, sizeof (utmpx));
2070 2039 (void) time(&utmpx.ut_tv.tv_sec);
2071 2040 utmpx.ut_pid = getpid();
2072 2041
2073 2042 if (rflag || hflag) {
2074 2043 SCPYN(utmpx.ut_host, remote_host);
2075 2044 tmplen = strlen(remote_host) + 1;
2076 2045 if (tmplen < sizeof (utmpx.ut_host))
2077 2046 utmpx.ut_syslen = tmplen;
2078 2047 else
2079 2048 utmpx.ut_syslen = sizeof (utmpx.ut_host);
2080 2049 } else if (zflag) {
2081 2050 /*
2082 2051 * If this is a login from another zone, put the
2083 2052 * zone:<zonename> string in the utmpx entry.
2084 2053 */
2085 2054 SCPYN(utmpx.ut_host, zone_name);
2086 2055 tmplen = strlen(zone_name) + 1;
2087 2056 if (tmplen < sizeof (utmpx.ut_host))
2088 2057 utmpx.ut_syslen = tmplen;
2089 2058 else
2090 2059 utmpx.ut_syslen = sizeof (utmpx.ut_host);
2091 2060 } else {
2092 2061 utmpx.ut_syslen = 0;
2093 2062 }
2094 2063
2095 2064 SCPYN(utmpx.ut_user, user);
2096 2065
2097 2066 /* skip over "/dev/" */
2098 2067 ttyntail = basename(ttyn);
2099 2068
2100 2069 while ((u = getutxent()) != NULL) {
2101 2070 if ((u->ut_type == INIT_PROCESS ||
2102 2071 u->ut_type == LOGIN_PROCESS ||
2103 2072 u->ut_type == USER_PROCESS) &&
2104 2073 ((sublogin && strncmp(u->ut_line, ttyntail,
2105 2074 sizeof (u->ut_line)) == 0) ||
2106 2075 u->ut_pid == login_pid)) {
2107 2076 SCPYN(utmpx.ut_line, (ttyn+sizeof ("/dev/")-1));
2108 2077 (void) memcpy(utmpx.ut_id, u->ut_id,
2109 2078 sizeof (utmpx.ut_id));
2110 2079 utmpx.ut_exit.e_exit = u->ut_exit.e_exit;
2111 2080 utmpx.ut_type = USER_PROCESS;
2112 2081 (void) pututxline(&utmpx);
2113 2082 break;
2114 2083 }
2115 2084 }
2116 2085 endutxent();
2117 2086
2118 2087 if (u == (struct utmpx *)NULL) {
2119 2088 if (!sublogin) {
2120 2089 /*
2121 2090 * no utmpx entry already setup
2122 2091 * (init or rlogind/telnetd)
2123 2092 */
2124 2093 (void) puts(errmsg);
↓ open down ↓ |
47 lines elided |
↑ open up ↑ |
2125 2094
2126 2095 audit_error = ADT_FAIL_VALUE_PROGRAM;
2127 2096 login_exit(1);
2128 2097 }
2129 2098 } else {
2130 2099 /* Now attempt to write out this entry to the wtmp file if */
2131 2100 /* we were successful in getting it from the utmpx file and */
2132 2101 /* the wtmp file exists. */
2133 2102 updwtmpx(WTMPX_FILE, &utmpx);
2134 2103 }
2135 -/* ONC_PLUS EXTRACT START */
2136 2104 }
2137 2105
2138 2106
2139 2107
2140 2108 /*
2141 2109 * process_chroot_logins - Chroots to the specified subdirectory and
2142 2110 * re executes login.
2143 2111 */
2144 2112
2145 2113 static int
2146 2114 process_chroot_logins(void)
2147 2115 {
2148 2116 /*
2149 2117 * If the shell field starts with a '*', do a chroot to the home
2150 2118 * directory and perform a new login.
2151 2119 */
2152 2120
2153 2121 if (*pwd->pw_shell == '*') {
2154 2122 (void) pam_end(pamh, PAM_SUCCESS); /* Done using PAM */
2155 2123 pamh = NULL; /* really done */
2156 2124 if (chroot(pwd->pw_dir) < 0) {
2157 2125 (void) printf("No Root Directory\n");
2158 2126
↓ open down ↓ |
13 lines elided |
↑ open up ↑ |
2159 2127 audit_failure(get_audit_id(),
2160 2128 ADT_FAIL_VALUE_CHDIR_FAILED,
2161 2129 pwd, remote_host, ttyn, zone_name);
2162 2130
2163 2131 return (ERROR);
2164 2132 }
2165 2133 /*
2166 2134 * Set the environment flag <!sublogin> so that the next login
2167 2135 * knows that it is a sublogin.
2168 2136 */
2169 -/* ONC_PLUS EXTRACT END */
2170 2137 envinit[0] = SUBLOGIN;
2171 2138 envinit[1] = (char *)NULL;
2172 2139 (void) printf("Subsystem root: %s\n", pwd->pw_dir);
2173 2140 (void) execle("/usr/bin/login", "login", (char *)0,
2174 2141 &envinit[0]);
2175 2142 (void) execle("/etc/login", "login", (char *)0, &envinit[0]);
2176 2143 (void) printf("No /usr/bin/login or /etc/login on root\n");
2177 2144
2178 2145 audit_error = ADT_FAIL_VALUE_PROGRAM;
2179 2146
2180 2147 login_exit(1);
2181 2148 }
2182 2149 return (OK);
2183 -/* ONC_PLUS EXTRACT START */
2184 2150 }
2185 2151
2186 2152 /*
2187 2153 * establish_user_environment - Set up the new users enviornment
2188 2154 */
2189 2155
2190 2156 static void
2191 2157 establish_user_environment(char **renvp)
2192 2158 {
2193 2159 int i, j, k, l_index, length, idx = 0;
2194 2160 char *endptr;
2195 2161 char **lenvp;
2196 2162 char **pam_env;
2197 2163
2198 2164 lenvp = environ;
2199 2165 while (*lenvp++)
2200 2166 ;
2201 2167
2202 2168 /* count the number of PAM environment variables set by modules */
2203 2169 if ((pam_env = pam_getenvlist(pamh)) != 0) {
2204 2170 for (idx = 0; pam_env[idx] != 0; idx++)
2205 2171 ;
2206 2172 }
2207 2173
2208 2174 envinit = (char **)calloc(lenvp - environ + 10 + MAXARGS + idx,
2209 2175 sizeof (char *));
2210 2176 if (envinit == NULL) {
2211 2177 (void) printf("Calloc failed - out of swap space.\n");
2212 2178 login_exit(8);
2213 2179 }
2214 2180
2215 2181 /*
2216 2182 * add PAM environment variables first so they
2217 2183 * can be overwritten at login's discretion.
2218 2184 * check for illegal environment variables.
2219 2185 */
2220 2186 idx = 0; basicenv = 0;
↓ open down ↓ |
27 lines elided |
↑ open up ↑ |
2221 2187 if (pam_env != 0) {
2222 2188 while (pam_env[idx] != 0) {
2223 2189 if (legalenvvar(pam_env[idx])) {
2224 2190 envinit[basicenv] = pam_env[idx];
2225 2191 basicenv++;
2226 2192 }
2227 2193 idx++;
2228 2194 }
2229 2195 }
2230 2196 (void) memcpy(&envinit[basicenv], newenv, sizeof (newenv));
2231 -/* ONC_PLUS EXTRACT END */
2232 2197
2233 2198 /* Set up environment */
2234 2199 if (rflag) {
2235 2200 ENVSTRNCAT(term, terminal);
2236 2201 } else if (hflag) {
2237 2202 if (strlen(terminal)) {
2238 2203 ENVSTRNCAT(term, terminal);
2239 2204 }
2240 2205 } else {
2241 2206 char *tp = getenv("TERM");
2242 2207
2243 2208 if ((tp != NULL) && (*tp != '\0'))
2244 2209 ENVSTRNCAT(term, tp);
2245 2210 }
2246 2211
2247 2212 ENVSTRNCAT(logname, pwd->pw_name);
2248 2213
2249 2214 /*
2250 2215 * There are three places to get timezone info. init.c sets
2251 2216 * TZ if the file /etc/TIMEZONE contains a value for TZ.
2252 2217 * login.c looks in the file /etc/default/login for a
2253 2218 * variable called TIMEZONE being set. If TIMEZONE has a
2254 2219 * value, TZ is set to that value; no environment variable
2255 2220 * TIMEZONE is set, only TZ. If neither of these methods
2256 2221 * work to set TZ, then the library routines will default
2257 2222 * to using the file /usr/lib/locale/TZ/localtime.
2258 2223 *
2259 2224 * There is a priority set up here. If /etc/TIMEZONE has
2260 2225 * a value for TZ, that value remains top priority. If the
2261 2226 * file /etc/default/login has TIMEZONE set, that has second
2262 2227 * highest priority not overriding the value of TZ in
2263 2228 * /etc/TIMEZONE. The reason for this priority is that the
2264 2229 * file /etc/TIMEZONE is supposed to be sourced by
2265 2230 * /etc/profile. We are doing the "sourcing" prematurely in
2266 2231 * init.c. Additionally, a login C shell doesn't source the
2267 2232 * file /etc/profile thus not sourcing /etc/TIMEZONE thus not
2268 2233 * allowing an adminstrator to globally set TZ for all users
2269 2234 */
2270 2235 if (Def_tz != NULL) /* Is there a TZ from defaults/login? */
2271 2236 tmp_tz = Def_tz;
2272 2237
2273 2238 if ((Def_tz = getenv("TZ")) != NULL) {
2274 2239 ENVSTRNCAT(timez, Def_tz);
2275 2240 } else if (tmp_tz != NULL) {
2276 2241 Def_tz = tmp_tz;
2277 2242 ENVSTRNCAT(timez, Def_tz);
2278 2243 }
2279 2244
2280 2245 if (Def_hertz == NULL)
2281 2246 (void) sprintf(hertz + strlen(hertz), "%lu", HZ);
2282 2247 else
2283 2248 ENVSTRNCAT(hertz, Def_hertz);
2284 2249
2285 2250 if (Def_path == NULL)
2286 2251 (void) strlcat(path, DEF_PATH, sizeof (path));
2287 2252 else
2288 2253 ENVSTRNCAT(path, Def_path);
2289 2254
2290 2255 ENVSTRNCAT(home, pwd->pw_dir);
2291 2256
2292 2257 /*
2293 2258 * Find the end of the basic environment
2294 2259 */
2295 2260 for (basicenv = 0; envinit[basicenv] != NULL; basicenv++)
2296 2261 ;
2297 2262
2298 2263 /*
2299 2264 * If TZ has a value, add it.
2300 2265 */
2301 2266 if (strcmp(timez, "TZ=") != 0)
2302 2267 envinit[basicenv++] = timez;
2303 2268
2304 2269 if (*pwd->pw_shell == '\0') {
2305 2270 /*
2306 2271 * If possible, use the primary default shell,
2307 2272 * otherwise, use the secondary one.
2308 2273 */
2309 2274 if (access(SHELL, X_OK) == 0)
2310 2275 pwd->pw_shell = SHELL;
2311 2276 else
2312 2277 pwd->pw_shell = SHELL2;
2313 2278 } else if (Altshell != NULL && strcmp(Altshell, "YES") == 0) {
2314 2279 envinit[basicenv++] = shell;
2315 2280 ENVSTRNCAT(shell, pwd->pw_shell);
2316 2281 }
2317 2282
2318 2283 #ifndef NO_MAIL
2319 2284 envinit[basicenv++] = mail;
2320 2285 (void) strlcat(mail, pwd->pw_name, sizeof (mail));
2321 2286 #endif
2322 2287
2323 2288 /*
2324 2289 * Pick up locale environment variables, if any.
2325 2290 */
2326 2291 lenvp = renvp;
2327 2292 while (*lenvp != NULL) {
2328 2293 j = 0;
2329 2294 while (localeenv[j] != 0) {
2330 2295 /*
2331 2296 * locale_envmatch() returns 1 if
2332 2297 * *lenvp is localenev[j] and valid.
2333 2298 */
2334 2299 if (locale_envmatch(localeenv[j], *lenvp) == 1) {
2335 2300 envinit[basicenv++] = *lenvp;
2336 2301 break;
2337 2302 }
2338 2303 j++;
2339 2304 }
2340 2305 lenvp++;
2341 2306 }
2342 2307
2343 2308 /*
2344 2309 * If '-p' flag, then try to pass on allowable environment
2345 2310 * variables. Note that by processing this first, what is
2346 2311 * passed on the final "login:" line may over-ride the invocation
2347 2312 * values. XXX is this correct?
2348 2313 */
2349 2314 if (pflag) {
2350 2315 for (lenvp = renvp; *lenvp; lenvp++) {
2351 2316 if (!legalenvvar(*lenvp)) {
2352 2317 continue;
2353 2318 }
2354 2319 /*
2355 2320 * If this isn't 'xxx=yyy', skip it. XXX
2356 2321 */
2357 2322 if ((endptr = strchr(*lenvp, '=')) == NULL) {
2358 2323 continue;
2359 2324 }
2360 2325 length = endptr + 1 - *lenvp;
2361 2326 for (j = 0; j < basicenv; j++) {
2362 2327 if (strncmp(envinit[j], *lenvp, length) == 0) {
2363 2328 /*
2364 2329 * Replace previously established value
2365 2330 */
2366 2331 envinit[j] = *lenvp;
2367 2332 break;
2368 2333 }
2369 2334 }
2370 2335 if (j == basicenv) {
2371 2336 /*
2372 2337 * It's a new definition, so add it at the end.
2373 2338 */
2374 2339 envinit[basicenv++] = *lenvp;
2375 2340 }
2376 2341 }
2377 2342 }
2378 2343
2379 2344 /*
2380 2345 * Add in all the environment variables picked up from the
2381 2346 * argument list to "login" or from the user response to the
2382 2347 * "login" request, if any.
2383 2348 */
2384 2349
2385 2350 if (envp == NULL)
2386 2351 goto switch_env; /* done */
2387 2352
2388 2353 for (j = 0, k = 0, l_index = 0;
2389 2354 *envp != NULL && j < (MAXARGS-1);
2390 2355 j++, envp++) {
2391 2356
2392 2357 /*
2393 2358 * Scan each string provided. If it doesn't have the
2394 2359 * format xxx=yyy, then add the string "Ln=" to the beginning.
2395 2360 */
2396 2361 if ((endptr = strchr(*envp, '=')) == NULL) {
2397 2362 /*
2398 2363 * This much to be malloc'd:
2399 2364 * strlen(*envp) + 1 char for 'L' +
2400 2365 * MAXARGSWIDTH + 1 char for '=' + 1 for null char;
2401 2366 *
2402 2367 * total = strlen(*envp) + MAXARGSWIDTH + 3
2403 2368 */
2404 2369 int total = strlen(*envp) + MAXARGSWIDTH + 3;
2405 2370 envinit[basicenv+k] = malloc(total);
2406 2371 if (envinit[basicenv+k] == NULL) {
2407 2372 (void) printf("%s: malloc failed\n", PROG_NAME);
2408 2373 login_exit(1);
2409 2374 }
2410 2375 (void) snprintf(envinit[basicenv+k], total, "L%d=%s",
2411 2376 l_index, *envp);
2412 2377
2413 2378 k++;
2414 2379 l_index++;
2415 2380 } else {
2416 2381 if (!legalenvvar(*envp)) { /* this env var permited? */
2417 2382 continue;
2418 2383 } else {
2419 2384
2420 2385 /*
2421 2386 * Check to see whether this string replaces
2422 2387 * any previously defined string
2423 2388 */
2424 2389 for (i = 0, length = endptr + 1 - *envp;
2425 2390 i < basicenv + k; i++) {
2426 2391 if (strncmp(*envp, envinit[i], length)
2427 2392 == 0) {
2428 2393 envinit[i] = *envp;
2429 2394 break;
2430 2395 }
2431 2396 }
2432 2397
2433 2398 /*
2434 2399 * If it doesn't, place it at the end of
2435 2400 * environment array.
2436 2401 */
2437 2402 if (i == basicenv+k) {
2438 2403 envinit[basicenv+k] = *envp;
2439 2404 k++;
2440 2405 }
2441 2406 }
2442 2407 }
2443 2408 } /* for (j = 0 ... ) */
2444 2409
2445 2410 switch_env:
2446 2411 /*
2447 2412 * Switch to the new environment.
2448 2413 */
2449 2414 environ = envinit;
2450 2415 }
2451 2416
2452 2417 /*
2453 2418 * print_banner - Print the banner at start up
2454 2419 * Do not turn on DOBANNER ifdef. This is not
2455 2420 * relevant to SunOS.
2456 2421 */
2457 2422
2458 2423 static void
2459 2424 print_banner(void)
2460 2425 {
2461 2426 #ifdef DOBANNER
2462 2427 uname(&un);
2463 2428 #if i386
2464 2429 (void) printf("UNIX System V/386 Release %s\n%s\n"
2465 2430 "Copyright (C) 1984, 1986, 1987, 1988 AT&T\n"
2466 2431 "Copyright (C) 1987, 1988 Microsoft Corp.\nAll Rights Reserved\n",
2467 2432 un.release, un.nodename);
2468 2433 #elif sun
2469 2434 (void) printf("SunOS Release %s Sun Microsystems %s\n%s\n"
2470 2435 "Copyright (c) 1984, 1986, 1987, 1988 AT&T\n"
2471 2436 "Copyright (c) 1988, 1989, 1990, 1991 Sun Microsystems\n"
2472 2437 "All Rights Reserved\n",
2473 2438 un.release, un.machine, un.nodename);
2474 2439 #else
2475 2440 (void) printf("UNIX System V Release %s AT&T %s\n%s\n"
2476 2441 "Copyright (c) 1984, 1986, 1987, 1988 AT&T\nAll Rights Reserved\n",
2477 2442 un.release, un.machine, un.nodename);
2478 2443 #endif /* i386 */
2479 2444 #endif /* DOBANNER */
2480 2445 }
2481 2446
2482 2447 /*
2483 2448 * display_last_login_time - Advise the user the time and date
2484 2449 * that this login-id was last used.
2485 2450 */
2486 2451
2487 2452 static void
2488 2453 display_last_login_time(void)
2489 2454 {
2490 2455 if (lastlogok) {
2491 2456 (void) printf("Last login: %.*s ", 24-5, ctime(&ll.ll_time));
2492 2457
2493 2458 if (*ll.ll_host != '\0')
2494 2459 (void) printf("from %.*s\n", sizeof (ll.ll_host),
2495 2460 ll.ll_host);
2496 2461 else
2497 2462 (void) printf("on %.*s\n", sizeof (ll.ll_line),
2498 2463 ll.ll_line);
2499 2464 }
2500 2465 }
2501 2466
2502 2467 /*
2503 2468 * exec_the_shell - invoke the specified shell or start up program
2504 2469 */
2505 2470
2506 2471 static void
2507 2472 exec_the_shell(void)
2508 2473 {
2509 2474 char *endptr;
2510 2475 int i;
2511 2476
2512 2477 (void) strlcat(minusnam, basename(pwd->pw_shell),
2513 2478 sizeof (minusnam));
2514 2479
2515 2480 /*
2516 2481 * Exec the shell
2517 2482 */
2518 2483 (void) execl(pwd->pw_shell, minusnam, (char *)0);
2519 2484
2520 2485 /*
2521 2486 * pwd->pw_shell was not an executable object file, maybe it
2522 2487 * is a shell proceedure or a command line with arguments.
2523 2488 * If so, turn off the SHELL= environment variable.
2524 2489 */
2525 2490 for (i = 0; envinit[i] != NULL; ++i) {
2526 2491 if ((envinit[i] == shell) &&
2527 2492 ((endptr = strchr(shell, '=')) != NULL))
2528 2493 (*++endptr) = '\0';
2529 2494 }
2530 2495
2531 2496 if (access(pwd->pw_shell, R_OK|X_OK) == 0) {
2532 2497 (void) execl(SHELL, "sh", pwd->pw_shell, (char *)0);
2533 2498 (void) execl(SHELL2, "sh", pwd->pw_shell, (char *)0);
2534 2499 }
2535 2500
2536 2501 (void) printf("No shell\n");
2537 2502 }
2538 2503
2539 2504 /*
2540 2505 * login_exit - Call exit() and terminate.
2541 2506 * This function is here for PAM so cleanup can
2542 2507 * be done before the process exits.
2543 2508 */
2544 2509 static void
2545 2510 login_exit(int exit_code)
2546 2511 {
2547 2512 if (pamh)
2548 2513 (void) pam_end(pamh, PAM_ABORT);
2549 2514
2550 2515 if (audit_error)
2551 2516 audit_failure(get_audit_id(), audit_error,
2552 2517 pwd, remote_host, ttyn, zone_name);
2553 2518
2554 2519 exit(exit_code);
2555 2520 /*NOTREACHED*/
2556 2521 }
2557 2522
2558 2523 /*
2559 2524 * Check if lenv and penv matches or not.
2560 2525 */
2561 2526 static int
2562 2527 locale_envmatch(char *lenv, char *penv)
2563 2528 {
2564 2529 while ((*lenv == *penv) && *lenv && *penv != '=') {
2565 2530 lenv++;
2566 2531 penv++;
2567 2532 }
2568 2533
2569 2534 /*
2570 2535 * '/' is eliminated for security reason.
2571 2536 */
2572 2537 if (*lenv == '\0' && *penv == '=' && *(penv + 1) != '/')
2573 2538 return (1);
2574 2539 return (0);
2575 2540 }
2576 2541
2577 2542 static int
2578 2543 is_number(char *ptr)
2579 2544 {
2580 2545 while (*ptr != '\0') {
2581 2546 if (!isdigit(*ptr))
2582 2547 return (0);
2583 2548 ptr++;
2584 2549 }
2585 2550 return (1);
2586 2551 }
↓ open down ↓ |
345 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX