/* * CDE - Common Desktop Environment * * Copyright (c) 1993-2012, The Open Group. All rights reserved. * * These libraries and programs are free software; you can * redistribute them and/or modify them under the terms of the GNU * Lesser General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) * any later version. * * These libraries and programs are distributed in the hope that * they will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public * License along with these libraries and programs; if not, write * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth * Floor, Boston, MA 02110-1301 USA */ /* $XConsortium: login.c /main/8 1996/10/29 13:49:30 drk $ */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */ /* The copyright notice above does not evidence any */ /* actual or intended publication of such source code. */ #ident "@(#)login.c 1.63 96/02/15 SMI" /* SVr4.0 1.43.6.26 */ /* PROPRIETARY NOTICE(Combined) This source code is unpublished proprietary information constituting, or derived under license from AT&T's UNIX(r) System V. In addition, portions of such source code were derived from Berkeley 4.3 BSD under license from the Regents of the University of California. Copyright Notice Notice of copyright on this source code product does not indicate publication. (c) 1986, 1987, 1988, 1989, 1990, 1991, 1992 Sun Microsystems, Inc (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T. All rights reserved. ******************************************************************* */ /* Copyright (c) 1987, 1988 Microsoft Corporation */ /* All Rights Reserved */ /* This Module contains Proprietary Information of Microsoft */ /* Corporation and should be treated as Confidential. */ /* * Usage: login [ -d device ] [ name ] [ environment args ] * * */ /* * * *** Header Files *** * * */ #include #include #include /* For logfile locking */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * * *** Defines, Macros, and String Constants *** * * */ #define ISSUEFILE "/etc/issue" /* file to print before prompt */ #define NOLOGIN "/etc/nologin" /* file to lock users out during shutdown */ /* * These need to be defined for UTMP management. * If we add in the utility functions later, we * can remove them. */ #define __UPDATE_ENTRY 1 #define __LOGIN 2 /* * Intervals to sleep after failed login */ #ifndef SLEEPTIME #define SLEEPTIME 4 /* sleeptime before login incorrect msg */ #endif static int Sleeptime = SLEEPTIME; /* * seconds login disabled after allowable number of unsuccessful attempts */ #ifndef DISABLETIME #define DISABLETIME 20 #endif #define MAXTRYS 5 /* * Login logging support */ #define LOGINLOG "/var/adm/loginlog" /* login log file */ #define LNAME_SIZE 20 /* size of logged logname */ #define TTYN_SIZE 15 /* size of logged tty name */ #define TIME_SIZE 30 /* size of logged time string */ #define ENT_SIZE (LNAME_SIZE + TTYN_SIZE + TIME_SIZE + 3) #define L_WAITTIME 5 /* waittime for log file to unlock */ #define LOGTRYS 10 /* depth of 'try' logging */ /* * String manipulation macros: SCPYN, EQN and ENVSTRNCAT */ #define SCPYN(a, b) (void) strncpy(a, b, sizeof (a)) #define EQN(a, b) (strncmp(a, b, sizeof (a)-1) == 0) #define ENVSTRNCAT(to, from) {int deflen; deflen = strlen(to); \ (void) strncpy((to)+ deflen, (from), sizeof (to) - (1 + deflen)); } /* * Other macros */ #define NMAX sizeof (utmp.ut_name) #define HMAX sizeof (utmp.ut_host) #define min(a, b) (((a) < (b)) ? (a) : (b)) /* * Various useful files and string constants */ #define SHELL "/usr/bin/sh" #define SHELL2 "/sbin/sh" #define SUBLOGIN "" #define LASTLOG "/var/adm/lastlog" #define PROG_NAME "login" #define HUSHLOGIN ".hushlogin" /* * Array and Buffer sizes */ #define PBUFSIZE 8 /* max significant characters in a password */ #define MAXARGS 63 #define MAXENV 1024 #define MAXLINE 2048 /* * Miscellaneous constants */ #define ROOTUID 0 #define ERROR 1 #define OK 0 #define LOG_ERROR 1 #define DONT_LOG_ERROR 0 #define TRUE 1 #define FALSE 0 /* * Counters for counting the number of failed login attempts */ static int trys = 0; /* * Externs a plenty */ extern int defopen(char *filename); extern int getsecretkey(); /* * BSM hooks */ extern int audit_login_save_flags(int rflag, int hflag); extern int audit_login_save_host(char *host); extern int audit_login_save_ttyn(char *ttyn); extern int audit_login_save_port(void); extern int audit_login_success(void); extern int audit_login_save_pw(struct passwd *pwd); extern int audit_login_bad_pw(void); extern int audit_login_maxtrys(void); extern int audit_login_not_console(void); extern int audit_login_bad_dialup(void); extern int audit_login_maxtrys(void); /* * utmp file variables */ static struct utmpx utmp; /* * The current user name */ static char user_name[64]; static char minusnam[16] = "-"; /* * locale environments to be passed to shells. */ static char *localeenv[] = { "LANG", "LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE", "LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0}; static int locale_envmatch(char *lenv, char *penv); /* * Environment variable support */ static char shell[256] = { "SHELL=" }; static char home[MAXPATHLEN] = { "HOME=" }; static char term[64] = { "TERM=" }; static char logname[30] = { "LOGNAME=" }; static char timez[100] = { "TZ=" }; static char hertz[10] = { "HZ=" }; static char path[MAXPATHLEN] = { "PATH=" }; static char *newenv[10+MAXARGS] = {home, path, logname, hertz, term, 0, 0}; static char **envinit = newenv; static int basicenv; static char envblk[MAXENV]; static char *zero = (char *)0; static char **envp; static char krb5ccname[256] = { "KRB5CCNAME=" }; static char krb4ccname[256] = { "KRBTKFILE=" }; #ifndef NO_MAIL static char mail[30] = { "MAIL=/var/mail/" }; #endif extern char **environ; char inputline[MAXLINE]; /* * Strings used to prompt the user. */ static char loginmsg[] = "login: "; static char passwdmsg[] = "Password:"; static char incorrectmsg[] = "Login incorrect\n"; /* * Password file support */ static struct passwd *pwd; static char remote_host[HMAX]; /* * Illegal passwd entries. */ static struct passwd nouser = { "", "no:password", ~ROOTUID }; /* * Log file support */ static char *log_entry[LOGTRYS]; static int writelog = 0; static int lastlogok = 0; static struct lastlog ll; static int dosyslog = 0; /* * Default file toggles */ static char *Pndefault = "/etc/default/login"; static char *Altshell = NULL; static char *Console = NULL; static char *Passreq = NULL; #define DEFUMASK 022 static mode_t Umask = DEFUMASK; static char *Def_tz = NULL; static char *tmp_tz = NULL; static char *Def_hertz = NULL; #define SET_FSIZ 2 /* ulimit() command arg */ static long Def_ulimit = 0; #define MAX_TIMEOUT (15 * 60) #define DEF_TIMEOUT (5 * 60) static unsigned Def_timeout = DEF_TIMEOUT; static char *Def_path = NULL; static char *Def_supath = NULL; #define DEF_PATH "/usr/bin:" /* same as PATH */ #define DEF_SUPATH "/usr/sbin:/usr/bin" /* same as ROOTPATH */ /* * ttyprompt will point to the environment variable TTYPROMPT. * TTYPROMPT is set by ttymon if ttymon already wrote out the prompt. */ static char *ttyprompt = NULL; static char *ttyn = NULL; static struct group *grpstr; static char *ttygrp = "tty"; static char *progname = PROG_NAME; /* * Pass inherited environment. Used by telnetd in support of the telnet * ENVIRON option. */ static int pflag; /* * Remote login support */ static int hflag, rflag; static char rusername[NMAX+1], lusername[NMAX+1]; static char terminal[MAXPATHLEN]; /* * Pre-authentication flag support */ static int fflag; static int login_conv(int num_msg, struct pam_message **msg, struct pam_response **response, void *appdata_ptr); static struct pam_conv pam_conv = {login_conv, NULL}; static pam_handle_t *pamh; /* Authentication handle */ /* * Function declarations */ static void turn_on_logging(void); static void defaults(void); static void usage(void); static void process_rlogin(void); static void login_authenticate(); static void setup_credentials(void); static void adjust_nice(void); static void update_utmp_entry(int sublogin); static void establish_user_environment(char **renvp); static void print_banner(void); static void display_last_login_time(void); static void exec_the_shell(void); static int process_chroot_logins(void); static int chdir_to_dir_root(void); static void chdir_to_dir_user(void); static void logindevperm(char *, uid_t, gid_t); static void dir_dev_acc(char *, uid_t, gid_t, mode_t, char *); static void check_log(void); static void validate_account(); static void doremoteterm(char *term); static int get_options(int argc, char *argv[]); static void uppercaseterm(char *strp); static void getstr(char *buf, int cnt, char *err); static int legalenvvar(char *s); static void check_for_root_user(void); static void check_for_dueling_unix(char inputline[]); static void get_user_name(void); static void login_exit(int exit_code); static int logins_disabled(char *user_name); static void log_bad_attempts(void); /* * *** main *** * * The primary flow of control is directed in this routine. * Control moves in line from top to bottom calling subfunctions * which perform the bulk of the work. Many of these calls exit * when a fatal error is encountered and do not return to main. * * */ void main(int argc, char *argv[], char **renvp) { int sublogin; /* * Set up Defaults and flags */ defaults(); /* * Set up default umask */ if (Umask > ((mode_t) 0777)) Umask = DEFUMASK; (void) umask(Umask); /* * Set up default timeouts and delays */ if (Def_timeout > MAX_TIMEOUT) Def_timeout = MAX_TIMEOUT; if (Sleeptime < 0 || Sleeptime > 5) Sleeptime = SLEEPTIME; (void) alarm(Def_timeout); /* * Ignore SIGQUIT and SIGINT and set nice to 0 */ (void) signal(SIGQUIT, SIG_IGN); (void) signal(SIGINT, SIG_IGN); (void) nice(0); /* * Set flag to disable the pid check if you find that you are * a subsystem login. */ sublogin = 0; if (*renvp && strcmp(*renvp, SUBLOGIN) == 0) sublogin = 1; /* * Parse Arguments */ if (get_options(argc, argv) == -1) { usage(); login_exit(1); } audit_login_save_flags(rflag, hflag); audit_login_save_host(remote_host); /* * if devicename is not passed as argument, call ttyname(0) */ if (ttyn == NULL) { ttyn = ttyname(0); if (ttyn == NULL) ttyn = "/dev/???"; } audit_login_save_ttyn(ttyn); audit_login_save_port(); /* * Call pam_start to initiate a PAM authentication operation */ if ((pam_start(progname, user_name, &pam_conv, &pamh)) != PAM_SUCCESS) login_exit(1); if ((pam_set_item(pamh, PAM_TTY, ttyn)) != PAM_SUCCESS) { login_exit(1); } if ((pam_set_item(pamh, PAM_RHOST, remote_host)) != PAM_SUCCESS) { login_exit(1); } /* * Open the log file which contains a record of successful and failed * login attempts */ turn_on_logging(); /* * say "hi" to syslogd .. */ openlog("login", 0, LOG_AUTH); /* * Do special processing for -r (rlogin) flag */ if (rflag) process_rlogin(); /* * validate user */ /* we are already authenticated. fill in what we must, then continue */ if (fflag) { if (pwd = getpwnam(user_name)) audit_login_save_pw(pwd); else { audit_login_bad_pw(); log_bad_attempts(); login_exit(1); } } else { /* * Perform the primary login authentication activity. */ login_authenticate(); } /* change root login, then we exec another login and try again */ if (process_chroot_logins() != OK) login_exit(1); /* * If root login and not on system console then call exit(2) */ check_for_root_user(); /* * Check to see if a shutdown is in progress, if it is and * we are not root then throw the user off the system */ if (logins_disabled(user_name) == TRUE) login_exit(1); if (pwd->pw_uid == 0) { if (Def_supath != NULL) Def_path = Def_supath; else Def_path = DEF_SUPATH; } /* * Check account expiration and passwd aging */ validate_account(); /* * We only get here if we've been authenticated. */ update_utmp_entry(sublogin); /* * Now we set up the environment for the new user, which includes * the users ulimit, nice value, ownership of this tty, uid, gid, * and environment variables. */ if (Def_ulimit > 0L && ulimit(SET_FSIZ, Def_ulimit) < 0L) (void) printf("Could not set ULIMIT to %ld\n", Def_ulimit); /* * Set mode to r/w user & w group, owner to user and group to tty */ (void) chmod(ttyn, S_IRUSR|S_IWUSR|S_IWGRP); if ((grpstr = getgrnam(ttygrp)) == NULL) (void) chown(ttyn, pwd->pw_uid, pwd->pw_gid); else (void) chown(ttyn, pwd->pw_uid, grpstr->gr_gid); logindevperm(ttyn, pwd->pw_uid, pwd->pw_gid); adjust_nice(); /* passwd file can specify nice value */ /* * Record successful login and fork process that records logout. * We have to do this before setting credentials because we need * to be root in order do a setaudit() and an audit(). */ audit_login_success(); setup_credentials(); /* Set uid/gid - exits on failure */ pam_end(pamh, PAM_SUCCESS); /* Done using PAM */ /* * Set up the basic environment for the exec. This includes * HOME, PATH, LOGNAME, SHELL, TERM, TZ, HZ, and MAIL. */ chdir_to_dir_user(); establish_user_environment(renvp); if (pwd->pw_uid == 0) if (remote_host[0] && dosyslog) syslog(LOG_NOTICE, "ROOT LOGIN %s FROM %.*s", ttyn, HMAX, remote_host); else if (dosyslog) syslog(LOG_NOTICE, "ROOT LOGIN %s", ttyn); closelog(); (void) signal(SIGQUIT, SIG_DFL); (void) signal(SIGINT, SIG_DFL); /* * Display some useful information to the new user like the banner * and last login time if not a quiet login. */ if (access(HUSHLOGIN, F_OK) != 0) { print_banner(); display_last_login_time(); } /* * Now fire off the shell of choice */ exec_the_shell(); /* * All done */ login_exit(1); /* NOTREACHED */ } /* * *** Utility functions *** */ /* * donothing & catch - Signal catching functions */ /*ARGSUSED*/ static void donothing(int sig) { if (pamh) pam_end(pamh, PAM_ABORT); } #ifdef notdef static int intrupt; /*ARGSUSED*/ static void catch(int sig) { ++intrupt; } #endif /* * *** Bad login logging support *** */ /* * badlogin() - log to the log file 'trys' * unsuccessful attempts */ static void badlogin(void) { int retval, count1, fildes; /* * Tries to open the log file. If succeed, lock it and write * in the failed attempts */ if ((fildes = open(LOGINLOG, O_APPEND|O_WRONLY)) != -1) { (void) sigset(SIGALRM, donothing); (void) alarm(L_WAITTIME); retval = lockf(fildes, F_LOCK, 0L); (void) alarm(0); (void) sigset(SIGALRM, SIG_DFL); if (retval == 0) { for (count1 = 0; count1 < trys; count1++) (void) write(fildes, log_entry[count1], (unsigned) strlen(log_entry[count1])); (void) lockf(fildes, F_ULOCK, 0L); } (void) close(fildes); } } /* * log_bad_attempts - log each bad login attempt - called from * login_authenticate. Exits when the maximum attempt * count is exceeded. */ static void log_bad_attempts(void) { time_t timenow; if (writelog == 1 && trys < LOGTRYS) { (void) time(&timenow); (void) strncat(log_entry[trys], user_name, LNAME_SIZE); (void) strncat(log_entry[trys], ":", (size_t) 1); (void) strncat(log_entry[trys], ttyn, TTYN_SIZE); (void) strncat(log_entry[trys], ":", (size_t) 1); (void) strncat(log_entry[trys], ctime(&timenow), TIME_SIZE); trys++; } } /* * turn_on_logging - if the logfile exist, turn on attempt logging and * initialize the string storage area */ static void turn_on_logging(void) { struct stat dbuf; int i; if (stat(LOGINLOG, &dbuf) == 0) { writelog = 1; for (i = 0; i < LOGTRYS; i++) { if (!(log_entry[i] = malloc((size_t) ENT_SIZE))) { writelog = 0; break; } *log_entry[i] = '\0'; } } } /* * login_conv(): * This is the conv (conversation) function called from * a PAM authentication module to print error messages * or garner information from the user. */ static int login_conv(int num_msg, struct pam_message **msg, struct pam_response **response, void *appdata_ptr) { struct pam_message *m; struct pam_response *r; char *temp; int k, i; if (num_msg <= 0) return (PAM_CONV_ERR); *response = calloc(num_msg, sizeof (struct pam_response)); if (*response == NULL) return (PAM_BUF_ERR); k = num_msg; m = *msg; r = *response; while (k--) { switch (m->msg_style) { case PAM_PROMPT_ECHO_OFF: temp = getpass(m->msg); if (temp != NULL) { r->resp = strdup(temp); if (r->resp == NULL) { /* free responses */ r = *response; for (i = 0; i < num_msg; i++, r++) { if (r->resp) free(r->resp); } free(*response); *response = NULL; return (PAM_BUF_ERR); } } m++; r++; break; case PAM_PROMPT_ECHO_ON: if (m->msg != NULL) (void) fputs(m->msg, stdout); r->resp = calloc(1, PAM_MAX_RESP_SIZE); if (r->resp == NULL) { /* free responses */ r = *response; for (i = 0; i < num_msg; i++, r++) { if (r->resp) free(r->resp); } free(*response); *response = NULL; return (PAM_BUF_ERR); } if (fgets(r->resp, PAM_MAX_RESP_SIZE-1, stdin)) { int len; r->resp[PAM_MAX_RESP_SIZE-1] = NULL; len = strlen(r->resp); if (r->resp[len-1] == '\n') r->resp[len-1] = '\0'; } else { login_exit(1); } m++; r++; break; case PAM_ERROR_MSG: if (m->msg != NULL) { (void) fputs(m->msg, stderr); (void) fputs("\n", stderr); } m++; r++; break; case PAM_TEXT_INFO: if (m->msg != NULL) { (void) fputs(m->msg, stdout); (void) fputs("\n", stdout); } m++; r++; break; default: break; } } return (PAM_SUCCESS); } /* * verify_passwd - Checks the users password * Returns: OK if password check successful, * ERROR if password check fails. */ static int verify_passwd() { int flags; int error; char *user; /* * PAM authenticates the user for us. */ error = pam_authenticate(pamh, 0); /* get the user_name from the pam handle */ pam_get_item(pamh, PAM_USER, (void**)&user); SCPYN(user_name, user); check_for_dueling_unix(user_name); if (error != PAM_SUCCESS) { /* something bad has happended - bye bye */ if (error == PAM_ABORT) { audit_login_bad_pw(); log_bad_attempts(); (void) sleep(DISABLETIME); (void) printf(incorrectmsg); login_exit(1); } return (error); } if ((pwd = getpwnam(user_name)) == NULL) { audit_login_bad_pw(); log_bad_attempts(); return (PAM_USER_UNKNOWN); } audit_login_save_pw(pwd); return (error); } /* * quotec - Called by getargs */ static int quotec(void) { register int c, i, num; switch (c = getc(stdin)) { case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 'v': c = '\013'; break; case 'b': c = '\b'; break; case 't': c = '\t'; break; case 'f': c = '\f'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': for (num = 0, i = 0; i < 3; i++) { num = num * 8 + (c - '0'); if ((c = getc(stdin)) < '0' || c > '7') break; } (void) ungetc(c, stdin); c = num & 0377; break; default: break; } return (c); } /* * getargs - returns an input line. Exits if EOF encountered. */ #define WHITESPACE 0 #define ARGUMENT 1 static char ** getargs(char *in_line) { static char envbuf[MAXLINE]; static char *args[MAXARGS]; register char *ptr, **answer; register int c; int state; for (ptr = envbuf; ptr < &envbuf[sizeof (envbuf)]; /* cstyle */) *ptr++ = '\0'; for (answer = args; answer < &args[MAXARGS]; /* cstyle */) *answer++ = (char *)NULL; for (ptr = envbuf, answer = &args[0], state = WHITESPACE; (c = getc(stdin)) != EOF; /* cstyle */) { *(in_line++) = c; switch (c) { case '\n': if (ptr == &envbuf[0]) return ((char **)NULL); return (&args[0]); case ' ': case '\t': if (state == ARGUMENT) { *ptr++ = '\0'; state = WHITESPACE; } break; case '\\': c = quotec(); default: if (state == WHITESPACE) { *answer++ = ptr; state = ARGUMENT; } *ptr++ = c; } /* * If the buffer is full, force the next character to be read to * be a . */ if (ptr == &envbuf[sizeof (envbuf)-1]) { (void) ungetc('\n', stdin); (void) putc('\n', stdout); } } /* * If we left loop because an EOF was received, exit immediately. */ login_exit(0); /* NOTREACHED */ } /* * get_user_name - Gets the user name either passed in, or from the * login: prompt. */ static void get_user_name() { FILE *fp; int gotname = 0; if ((fp = fopen(ISSUEFILE, "r")) != NULL) { char *ptr, buffer[BUFSIZ]; while ((ptr = fgets(buffer, sizeof (buffer), fp)) != NULL) { (void) fputs(ptr, stdout); } (void) fclose(fp); } /* * if TTYPROMPT is not set, use our own prompt * otherwise, use ttyprompt. We just set PAM_USER_PROMPT * and let the module do the prompting. */ if ((ttyprompt == NULL) || (*ttyprompt == '\0')) (void) pam_set_item(pamh, PAM_USER_PROMPT, (void *)loginmsg); else (void) pam_set_item(pamh, PAM_USER_PROMPT, (void *)ttyprompt); envp = &zero; /* XXX: is this right? */ } /* * Check_for_dueling_unix - Check to see if the another login is talking * to the line we've got open as a login port * Exits if we're talking to another unix system */ static void check_for_dueling_unix(char *inputline) { if (EQN(loginmsg, inputline) || EQN(passwdmsg, inputline) || EQN(incorrectmsg, inputline)) { (void) printf("Looking at a login line.\n"); login_exit(8); } } /* * logins_disabled - if the file /etc/nologin exists and the user is not * root then do not permit them to login */ static int logins_disabled(char *user_name) { FILE *nlfd; int c; if (!EQN("root", user_name) && ((nlfd = fopen(NOLOGIN, "r")) != (FILE *)NULL)) { while ((c = getc(nlfd)) != EOF) putchar(c); (void) fflush(stdout); sleep(5); return (TRUE); } return (FALSE); } /* * check_for_root_user - Checks if we're getting a root login on the console * Exits if root login not on system console. * */ static void check_for_root_user(void) { if (pwd->pw_uid == 0) { if ((Console != NULL) && (strcmp(ttyn, Console) != 0)) { audit_login_not_console(); (void) printf("Not on system console\n"); login_exit(10); } } } static char *illegal[] = { "SHELL=", "HOME=", "LOGNAME=", #ifndef NO_MAIL "MAIL=", #endif "CDPATH=", "IFS=", "PATH=", 0 }; /* * legalenvvar - Is it legal to insert this environmental variable? */ static int legalenvvar(char *s) { register char **p; for (p = illegal; *p; p++) if (strncmp(s, *p, strlen(*p)) == 0) return (0); if (s[0] == 'L' && s[1] == 'D' && s[2] == '_') return (0); return (1); } /* * getstr - Get a string from standard input * Calls exit if read(2) fails. */ static void getstr(char *buf, int cnt, char *err) { char c; do { if (read(0, &c, 1) != 1) login_exit(1); *buf++ = c; } while (cnt > 1 && c != 0); *buf = 0; err = err; /* For lint */ } /* * uppercaseterm - if all input char are upper case * set the corresponding termio */ static void uppercaseterm(char *strp) { int upper = 0; int lower = 0; char *sp; struct termio termio; for (sp = strp; *sp; sp++) { if (islower(*sp)) lower++; else if (isupper(*sp)) upper++; } if (upper > 0 && lower == 0) { (void) ioctl(0, TCGETA, &termio); termio.c_iflag |= IUCLC; termio.c_oflag |= OLCUC; termio.c_lflag |= XCASE; (void) ioctl(0, TCSETAW, &termio); for (sp = strp; *sp; sp++) if (*sp >= 'A' && *sp <= 'Z') *sp += ('a' - 'A'); } } /* * defaults - read defaults */ static void defaults(void) { extern int defcntl(); register int flags; register char *ptr; if (defopen(Pndefault) == 0) { /* * ignore case */ flags = defcntl(DC_GETFLAGS, 0); TURNOFF(flags, DC_CASE); defcntl(DC_SETFLAGS, flags); if ((Console = defread("CONSOLE=")) != NULL) Console = strdup(Console); if ((Altshell = defread("ALTSHELL=")) != NULL) Altshell = strdup(Altshell); if ((Passreq = defread("PASSREQ=")) != NULL) Passreq = strdup(Passreq); if ((Def_tz = defread("TIMEZONE=")) != NULL) Def_tz = strdup(Def_tz); if ((Def_hertz = defread("HZ=")) != NULL) Def_hertz = strdup(Def_hertz); if ((Def_path = defread("PATH=")) != NULL) Def_path = strdup(Def_path); if ((Def_supath = defread("SUPATH=")) != NULL) Def_supath = strdup(Def_supath); if ((ptr = defread("ULIMIT=")) != NULL) Def_ulimit = atol(ptr); if ((ptr = defread("TIMEOUT=")) != NULL) Def_timeout = (unsigned) atoi(ptr); if ((ptr = defread("UMASK=")) != NULL) { long long_umask; if (sscanf(ptr, "%lo", &long_umask) != 1) Umask = DEFUMASK; else Umask = (mode_t) long_umask; } if ((ptr = defread("SLEEPTIME=")) != NULL) Sleeptime = atoi(ptr); if ((ptr = defread("SYSLOG=")) != NULL) dosyslog = strcmp(ptr, "YES") == 0; (void) defopen((char *)NULL); } } /* * get_options(argc, argv) * - parse the cmd line. * - return 0 if successful, -1 if failed. * Calls login_exit() on misuse of -r and -h flags */ static int get_options(int argc, char *argv[]) { int c; int errflg = 0; while ((c = getopt(argc, argv, "f:h:r:pad:")) != -1) { switch (c) { case 'a': break; case 'd': /* * Must be root to pass in device name * otherwise we exit() as punishment for trying. */ if (getuid() != 0 || geteuid() != 0) { login_exit(1); /* sigh */ /*NOTREACHED*/ } ttyn = optarg; break; case 'h': if (hflag || rflag) { (void) fprintf(stderr, "Only one of -r and -h allowed\n"); login_exit(1); } hflag++; SCPYN(remote_host, optarg); if (argv[optind]) { if (argv[optind][0] != '-') SCPYN(terminal, argv[optind]); optind++; } progname = "telnet"; break; case 'r': if (hflag || rflag) { (void) fprintf(stderr, "Only one of -r and -h allowed\n"); login_exit(1); } rflag++; SCPYN(remote_host, optarg); progname = "rlogin"; break; case 'p': pflag++; break; case 'f': /* * Must be root to bypass authentication * otherwise we exit() as punishment for trying. */ if (getuid() != 0 || geteuid() != 0) { login_exit(1); /* sigh */ /*NOTREACHED*/ } /* save fflag user name for future use */ SCPYN(user_name, optarg); fflag = 1; break; default: errflg++; break; } /* end switch */ } /* end while */ /* * get the prompt set by ttymon */ ttyprompt = getenv("TTYPROMPT"); if ((ttyprompt != NULL) && (*ttyprompt != '\0')) { /* * if ttyprompt is set, there should be data on * the stream already. */ if ((envp = getargs(inputline)) != (char **)NULL) { uppercaseterm(*envp); /* * don't get name if name passed as argument. */ SCPYN(user_name, *envp++); } } else if (optind < argc) { SCPYN(user_name, argv[optind]); (void) strcpy(inputline, user_name); (void) strcat(inputline, " \n"); envp = &argv[optind+1]; } if (errflg) return (-1); return (0); } /* * usage - Print usage message * */ static void usage(void) { (void) fprintf(stderr, "Usage:\tlogin [-h|-r] [ name [ env-var ... ]]\n"); } /* * *** Remote login support *** * */ /* * doremoteterm - Sets the appropriate ioctls for a remote terminal */ static char *speeds[] = { "0", "50", "75", "110", "134", "150", "200", "300", "600", "1200", "1800", "2400", "4800", "9600", "19200", "38400", "57600", "76800", "115200", "153600", "230400", "307200", "460800" }; #define NSPEEDS (sizeof (speeds) / sizeof (speeds[0])) static void doremoteterm(char *term) { struct termios tp; register char *cp = strchr(term, '/'), **cpp; char *speed; (void) ioctl(0, TCGETS, &tp); if (cp) { *cp++ = '\0'; speed = cp; cp = strchr(speed, '/'); if (cp) *cp++ = '\0'; for (cpp = speeds; cpp < &speeds[NSPEEDS]; cpp++) if (strcmp(*cpp, speed) == 0) { cfsetospeed(&tp, cpp-speeds); break; } } tp.c_lflag |= ECHO|ICANON; tp.c_iflag |= IGNPAR|ICRNL; (void) ioctl(0, TCSETS, &tp); } /* * Process_rlogin - Does the work that rlogin and telnet * need done */ static void process_rlogin(void) { getstr(rusername, sizeof (rusername), "remuser"); getstr(lusername, sizeof (lusername), "locuser"); getstr(terminal, sizeof (terminal), "Terminal type"); /* fflag has precedence over stuff passed by rlogind */ if (fflag || getuid()) { pwd = &nouser; doremoteterm(terminal); return; } else { if (pam_set_item(pamh, PAM_USER, lusername) != PAM_SUCCESS) login_exit(1); pwd = getpwnam(lusername); if (pwd == NULL) { pwd = &nouser; doremoteterm(terminal); return; } } doremoteterm(terminal); /* * Update PAM on the user name */ if (pam_set_item(pamh, PAM_USER, lusername) != PAM_SUCCESS) login_exit(1); if (pam_set_item(pamh, PAM_RUSER, rusername) != PAM_SUCCESS) login_exit(1); SCPYN(user_name, lusername); envp = &zero; lusername[0] = '\0'; } /* * *** Account validation routines *** * */ /* * validate_account - This is the PAM version of validate. */ static void validate_account() { int error; int n; int flag; (void) alarm(0); /* give user time to come up with password */ check_log(); if ((Passreq != NULL) && (!strcasecmp("YES", Passreq))) flag = PAM_DISALLOW_NULL_AUTHTOK; else flag = 0; if ((error = pam_acct_mgmt(pamh, flag)) != PAM_SUCCESS) { if (error == PAM_AUTHTOKEN_REQD) { (void) printf("Choose a new password.\n"); if ((error = pam_chauthtok(pamh, 0)) != PAM_SUCCESS) { if (dosyslog) syslog(LOG_CRIT, "change password failure: %s", pam_strerror(pamh, error)); login_exit(1); } } else { (void) printf(incorrectmsg); if (dosyslog) syslog(LOG_CRIT, "login account failure: %s", pam_strerror(pamh, error)); login_exit(1); } } } /* * Check_log - This is really a hack because PAM checks the log, but login * wants to know if the log is okay and PAM doesn't have * a module independent way of handing this info back. */ static void check_log(void) { int fdl; long long offset; offset = (long long) pwd->pw_uid * (long long) sizeof (struct lastlog); if ((fdl = open(LASTLOG, O_RDWR|O_CREAT, 0444)) >= 0) { if (llseek(fdl, offset, SEEK_SET) == offset && read(fdl, (char *)&ll, sizeof (ll)) == sizeof (ll) && ll.ll_time != 0) lastlogok = 1; (void) close(fdl); } } /* * chdir_to_dir_root - Attempts to chdir us to the home directory. * defaults to "/" if it can't cd to the home * directory, and returns ERROR if it can't do that. */ static int chdir_to_dir_root(void) { if (chdir(pwd->pw_dir) < 0) { if (chdir("/") < 0) { (void) printf("No directory!\n"); return (ERROR); } } return (OK); } /* * chdir_to_dir_user - Now chdir after setuid/setgid have happened to * place us in the user's home directory just in * case it was protected and the first chdir failed. * No chdir errors should happen at this point because * all failures should have happened on the first * time around. */ static void chdir_to_dir_user(void) { if (chdir(pwd->pw_dir) < 0) { if (chdir("/") < 0) { (void) printf("No directory!\n"); /* * This probably won't work since we can't get to /. */ if (remote_host[0] && dosyslog) { syslog(LOG_CRIT, "REPEATED LOGIN FAILURES ON %s FROM %.*s", ttyn, HMAX, remote_host); } else if (dosyslog) { syslog(LOG_CRIT, "REPEATED LOGIN FAILURES ON %s", ttyn); } closelog(); (void) sleep(DISABLETIME); exit(1); } else { (void) printf("No directory! Logging in with home=/\n"); pwd->pw_dir = "/"; } } } /* * login_authenticate - Performs the main authentication work * 1. Prints the login prompt * 2. Requests and verifys the password * 3. Checks the port password */ static void login_authenticate() { int cnt = 1; char *user; int err; do { /* if scheme broken, then nothing to do but quit */ if (pam_get_item(pamh, PAM_USER, (void **)&user) != PAM_SUCCESS) exit(1); /* * only get name from utility if it is not already * supplied by pam_start or a pam_set_item. */ if (!user || !user[0]) { /* use call back to get user name */ get_user_name(); } err = verify_passwd(); if ((err == PAM_SUCCESS) && (chdir_to_dir_root() == OK)) { cnt = 0; break; } /* scheme has kept count of retries. time to bail */ if (err == PAM_MAXTRIES) cnt = MAXTRYS; /* only sleep after first bad passwd */ if (cnt) (void) sleep(Sleeptime); (void) printf(incorrectmsg); user_name[0] = '\0'; /* is this needed? */ /* force name to be null in this case */ if (pam_set_item(pamh, PAM_USER, NULL) != PAM_SUCCESS) login_exit(1); if (pam_set_item(pamh, PAM_RUSER, NULL) != PAM_SUCCESS) login_exit(1); } while (cnt++ < MAXTRYS); if (cnt >= MAXTRYS) { audit_login_maxtrys(); /* * If logging is turned on, output the * string storage area to the log file, * and sleep for DISABLETIME * seconds before exiting. */ if (writelog) badlogin(); if (remote_host[0] && dosyslog) { syslog(LOG_CRIT, "REPEATED LOGIN FAILURES ON %s FROM %.*s", ttyn, HMAX, remote_host); } else if (dosyslog) { syslog(LOG_CRIT, "REPEATED LOGIN FAILURES ON %s", ttyn); } (void) sleep(DISABLETIME); exit(1); } } /* * *** Credential Related routines *** * */ /* * setup_credentials - sets the group ID, initializes the groups * and sets up the secretkey. * Exits if a failure occurrs. */ /* * setup_credentials - PAM does all the work for us on this one. */ static void setup_credentials(void) { int error = 0; int flags; /* set the real (and effective) GID */ if (setgid(pwd->pw_gid) == -1) { login_exit(1); } /* * Initialize the supplementary group access list. */ if (!user_name) login_exit(1); if (initgroups(user_name, pwd->pw_gid) == -1) { login_exit(1); } /* XXX really should be after setgid */ if ((error = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { login_exit(error); } /* set the real (and effective) UID */ if (setuid(pwd->pw_uid) == -1) { login_exit(1); } } /* * * *** Routines to get a new user set up and running *** * * Things to do when starting up a new user: * adjust_nice * update_utmp_entry * establish_user_environment * print_banner * display_last_login_time * exec_the_shell * */ /* * adjust_nice - Set the nice (process priority) value if the * gecos value contains an appropriate value. */ static void adjust_nice(void) { int pri, mflg, i; if (strncmp("pri=", pwd->pw_gecos, 4) == 0) { pri = 0; mflg = 0; i = 4; if (pwd->pw_gecos[i] == '-') { mflg++; i++; } while (pwd->pw_gecos[i] >= '0' && pwd->pw_gecos[i] <= '9') pri = (pri * 10) + pwd->pw_gecos[i++] - '0'; if (mflg) pri = -pri; (void) nice(pri); } } /* * update_utmp_entry - Searchs for the correct utmp entry, making an * entry there if it finds one, otherwise exits. */ static void update_utmp_entry(int sublogin) { char *user; int error; static char *errmsg = "No utmpx entry. " "You must exec \"login\" from the lowest level \"shell\"."; int tmplen; struct utmpx *u = (struct utmpx *)0; struct utmpx utmp; char *ttyntail; /* * If we're not a sublogin then * we'll get an error back if our PID doesn't match the PID of the * entry we are updating, otherwise if its a sublogin the flags * field is set to 0, which means we just write a matching entry * (without checking the pid), or a new entry if an entry doesn't * exist. */ if (error = pam_open_session(pamh, 0) != PAM_SUCCESS) { login_exit(1); } if ((error = pam_get_item(pamh, PAM_USER, (void **) &user)) != PAM_SUCCESS) { login_exit(1); } (void) memset((void *)&utmp, 0, sizeof (utmp)); (void) time(&utmp.ut_tv.tv_sec); utmp.ut_pid = getpid(); if (rflag || hflag) { SCPYN(utmp.ut_host, remote_host); tmplen = strlen(remote_host) + 1; if (tmplen < sizeof (utmp.ut_host)) utmp.ut_syslen = tmplen; else utmp.ut_syslen = sizeof (utmp.ut_host); } else { utmp.ut_syslen = 0; } SCPYN(utmp.ut_user, user); /* skip over "/dev/" */ ttyntail = basename(ttyn); while ((u = getutxent()) != NULL) { if ((u->ut_type == INIT_PROCESS || u->ut_type == LOGIN_PROCESS || u->ut_type == USER_PROCESS) && ((sublogin && strncmp(u->ut_line, ttyntail, sizeof (u->ut_line)) == 0) || u->ut_pid == utmp.ut_pid)) { SCPYN(utmp.ut_line, (ttyn+sizeof ("/dev/")-1)); (void) memcpy(utmp.ut_id, u->ut_id, sizeof (utmp.ut_id)); utmp.ut_type = USER_PROCESS; pututxline(&utmp); break; } } endutxent(); if (u == (struct utmpx *)NULL) { if (!sublogin) { /* * no utmp entry already setup * (init or rlogind/telnetd) */ (void) puts(errmsg); login_exit(1); } } else { /* Now attempt to write out this entry to the wtmp file if */ /* we were successful in getting it from the utmp file and */ /* the wtmp file exists. */ updwtmpx(WTMPX_FILE, &utmp); } } /* * process_chroot_logins - Chroots to the specified subdirectory and * re executes login. */ static int process_chroot_logins(void) { /* * If the shell field starts with a '*', do a chroot to the home * directory and perform a new login. */ if (*pwd->pw_shell == '*') { pam_end(pamh, PAM_SUCCESS); /* Done using PAM */ if (chroot(pwd->pw_dir) < 0) { (void) printf("No Root Directory\n"); return (ERROR); } /* * Set the environment flag so that the next login * knows that it is a sublogin. */ envinit[0] = SUBLOGIN; envinit[1] = (char *)NULL; (void) printf("Subsystem root: %s\n", pwd->pw_dir); (void) execle("/usr/bin/login", "login", (char *)0, &envinit[0]); (void) execle("/etc/login", "login", (char *)0, &envinit[0]); (void) printf("No /usr/bin/login or /etc/login on root\n"); login_exit(1); } return (OK); } /* * establish_user_environment - Set up the new users environment */ static void establish_user_environment(char **renvp) { int i, j, k, l_index, length; char *ptr; char *endptr; char **lenvp; char *krb5p; char *krb4p; lenvp = environ; while (*lenvp++) ; envinit = (char **) calloc(lenvp - environ + 10 + MAXARGS, sizeof (char *)); if (envinit == NULL) { (void) printf("Calloc failed - out of swap space.\n"); login_exit(8); } memcpy(envinit, newenv, sizeof (newenv)); /* Set up environment */ if (rflag) { ENVSTRNCAT(term, terminal); } else if (hflag) { /* XXX: this causes an environment variable with the value */ /* "" to be created later on, because terminal is "". */ (void) strncpy(term, terminal, sizeof (term) - 1); } else { char *tp = getenv("TERM"); if ((tp != NULL) && (*tp != '\0')) ENVSTRNCAT(term, tp); } ENVSTRNCAT(logname, pwd->pw_name); /* * There are three places to get timezone info. init.c sets * TZ if the file /etc/TIMEZONE contains a value for TZ. * login.c looks in the file /etc/default/login for a * variable called TIMEZONE being set. If TIMEZONE has a * value, TZ is set to that value; no environment variable * TIMEZONE is set, only TZ. If neither of these methods * work to set TZ, then the library routines will default * to using the file /usr/lib/locale/TZ/localtime. * * There is a priority set up here. If /etc/TIMEZONE has * a value for TZ, that value remains top priority. If the * file /etc/default/login has TIMEZONE set, that has second * highest priority not overriding the value of TZ in * /etc/TIMEZONE. The reason for this priority is that the * file /etc/TIMEZONE is supposed to be sourced by * /etc/profile. We are doing the "sourcing" prematurely in * init.c. Additionally, a login C shell doesn't source the * file /etc/profile thus not sourcing /etc/TIMEZONE thus not * allowing an administrator to globally set TZ for all users */ if (Def_tz != NULL) /* Is there a TZ from defaults/login? */ tmp_tz = Def_tz; if ((Def_tz = getenv("TZ")) != NULL) { ENVSTRNCAT(timez, Def_tz); } else if (tmp_tz != NULL) { Def_tz = tmp_tz; ENVSTRNCAT(timez, Def_tz); } if (Def_hertz == NULL) (void) sprintf(hertz + strlen(hertz), "%u", HZ); else ENVSTRNCAT(hertz, Def_hertz); if (Def_path == NULL) (void) strcat(path, DEF_PATH); else ENVSTRNCAT(path, Def_path); ENVSTRNCAT(home, pwd->pw_dir); /* * Find the end of the basic environment */ for (basicenv = 0; envinit[basicenv] != NULL; basicenv++) ; /* * If TZ has a value, add it. */ if (strcmp(timez, "TZ=") != 0) envinit[basicenv++] = timez; if (*pwd->pw_shell == '\0') { /* * If possible, use the primary default shell, * otherwise, use the secondary one. */ if (access(SHELL, X_OK) == 0) pwd->pw_shell = SHELL; else pwd->pw_shell = SHELL2; } else if (Altshell != NULL && strcmp(Altshell, "YES") == 0) { envinit[basicenv++] = shell; ENVSTRNCAT(shell, pwd->pw_shell); } #ifndef NO_MAIL envinit[basicenv++] = mail; (void) strcat(mail, pwd->pw_name); #endif /* * Pick up locale environment variables, if any. */ lenvp = renvp; while (*lenvp != NULL) { j = 0; while (localeenv[j] != 0) { /* * locale_envmatch() returns 1 if * *lenvp is localenev[j] and valid. */ if (locale_envmatch(localeenv[j], *lenvp) == 1) { envinit[basicenv++] = *lenvp; break; } j++; } lenvp++; } /* * If '-p' flag, then try to pass on allowable environment * variables. Note that by processing this first, what is * passed on the final "login:" line may over-ride the invocation * values. XXX is this correct? */ if (pflag) { for (lenvp = renvp; *lenvp; lenvp++) { if (!legalenvvar(*lenvp)) { continue; } /* * If this isn't 'xxx=yyy', skip it. XXX */ if ((endptr = strchr(*lenvp, '=')) == NULL) { continue; } length = endptr + 1 - *lenvp; for (j = 0; j < basicenv; j++) { if (strncmp(envinit[j], *lenvp, length) == 0) { /* * Replace previously established value */ envinit[j] = *lenvp; break; } } if (j == basicenv) { /* * It's a new definition, so add it at the end. */ envinit[basicenv++] = *lenvp; } } } /* * Add in all the environment variables picked up from the * argument list to "login" or from the user response to the * "login" request. */ for (j = 0, k = 0, l_index = 0, ptr = &envblk[0]; *envp && j < (MAXARGS-1); j++, envp++) { /* * Scan each string provided. If it doesn't have the * format xxx=yyy, then add the string "Ln=" to the beginning. */ if ((endptr = strchr(*envp, '=')) == NULL) { envinit[basicenv+k] = ptr; (void) sprintf(ptr, "L%d=%s", l_index, *envp); /* * Advance "ptr" to the beginning of the * next argument. */ while (*ptr++) ; k++; l_index++; } else { if (!legalenvvar(*envp)) { /* this env var permited? */ continue; } else { /* * Check to see whether this string replaces * any previously defined string */ for (i = 0, length = endptr + 1 - *envp; i < basicenv + k; i++) { if (strncmp(*envp, envinit[i], length) == 0) { envinit[i] = *envp; break; } } /* * If it doesn't, place it at the end of * environment array. */ if (i == basicenv+k) { envinit[basicenv+k] = *envp; k++; } } } } /* for (j = 0 ... ) */ /* * Add DCE/Kerberos cred name, if any. * XXX - The module specific stuff should be removed from login * program eventually. This is better placed in DCE module. * So far, we haven't come up with a way to deal with these * environment variables. */ krb5p = getenv("KRB5CCNAME"); if ((krb5p != NULL) && (*krb5p != '\0')) { ENVSTRNCAT(krb5ccname, krb5p); envinit[basicenv++] = krb5ccname; } krb4p = getenv("KRBTKFILE"); if ((krb4p != NULL) && (*krb4p != '\0')) { ENVSTRNCAT(krb4ccname, krb4p); envinit[basicenv++] = krb4ccname; } /* * Switch to the new environment. */ environ = envinit; } /* * print_banner - Print the banner at start up * Do not turn on DOBANNER ifdef. This is not * relevant to SunOS. */ static void print_banner(void) { #ifdef DOBANNER uname(&un); #if i386 (void) printf("UNIX System V/386 Release %s\n%s\n" "Copyright (C) 1984, 1986, 1987, 1988 AT&T\n" "Copyright (C) 1987, 1988 Microsoft Corp.\nAll Rights Reserved\n", un.release, un.nodename); #elif sun (void) printf("SunOS Release %s Sun Microsystems %s\n%s\n" "Copyright (c) 1984, 1986, 1987, 1988 AT&T\n" "Copyright (c) 1988, 1989, 1990, 1991 Sun Microsystems\n" "All Rights Reserved\n", un.release, un.machine, un.nodename); #else (void) printf("UNIX System V Release %s AT&T %s\n%s\n" "Copyright (c) 1984, 1986, 1987, 1988 AT&T\nAll Rights Reserved\n", un.release, un.machine, un.nodename); #endif /* i386 */ #endif /* DOBANNER */ } /* * display_last_login_time - Advise the user the time and date * that this login-id was last used. */ static void display_last_login_time(void) { if (lastlogok) { (void) printf("Last login: %.*s ", 24-5, ctime(&ll.ll_time)); if (*ll.ll_host != '\0') (void) printf("from %.*s\n", sizeof (ll.ll_host), ll.ll_host); else (void) printf("on %.*s\n", sizeof (ll.ll_line), ll.ll_line); } } /* * exec_the_shell - invoke the specified shell or start up program */ static void exec_the_shell(void) { char *endptr; int i; (void) strcat(minusnam, basename(pwd->pw_shell)); /* * Exec the shell */ (void) execl(pwd->pw_shell, minusnam, (char *)0); /* * pwd->pw_shell was not an executable object file, maybe it * is a shell proceedure or a command line with arguments. * If so, turn off the SHELL= environment variable. */ for (i = 0; envinit[i] != NULL; ++i) { if ((envinit[i] == shell) && ((endptr = strchr(shell, '=')) != NULL)) (*++endptr) = '\0'; } if (access(pwd->pw_shell, R_OK|X_OK) == 0) { (void) execl(SHELL, "sh", pwd->pw_shell, (char *)0); (void) execl(SHELL2, "sh", pwd->pw_shell, (char *)0); } (void) printf("No shell\n"); } /* * login_exit - Call exit() and terminate. * This function is here for PAM so cleanup can * be done before the process exits. */ static void login_exit(int exit_code) { if (pamh) pam_end(pamh, PAM_ABORT); exit(exit_code); /*NOTREACHED*/ } /* * Check if lenv and penv matches or not. */ static int locale_envmatch(char *lenv, char *penv) { while ((*lenv == *penv) && *lenv && *penv != '=') { lenv++; penv++; } /* * '/' is eliminated for security reason. */ if (*lenv == '\0' && *penv == '=' && *(penv + 1) != '/') return (1); return (0); } /* * logindevperm - change owner/group/permissions of devices * list in /etc/logindevperm. (Code derived from set_fb_attrs() * in 4.x usr/src/bin/login.c and usr/src/etc/getty/main.c.) */ #define MAX_LINELEN 256 #define LOGINDEVPERM "/etc/logindevperm" #define DIRWILD "/*" /* directory wildcard */ #define DIRWLDLEN 2 /* strlen(DIRWILD) */ static void logindevperm(char *ttyn, uid_t uid, gid_t gid) { char *field_delims = " \t\n"; char *permfile = LOGINDEVPERM; char line[MAX_LINELEN]; char *console; char *mode_str; char *dev_list; char *device; char *ptr; int mode; FILE *fp; size_t l; int lineno; if ((fp = fopen(permfile, "r")) == NULL) return; lineno = 0; while (fgets(line, MAX_LINELEN, fp) != NULL) { lineno++; if ((ptr = strchr(line, '#')) != NULL) *ptr = '\0'; /* handle comments */ if ((console = strtok(line, field_delims)) == NULL) continue; /* ignore blank lines */ if (strcmp(console, ttyn) != 0) continue; /* not our tty, skip */ mode_str = strtok((char *)NULL, field_delims); if (mode_str == NULL) { (void) fprintf(stderr, "%s: line %d, invalid entry -- %s\n", permfile, lineno, line); continue; } /* convert string to octal value */ mode = strtol(mode_str, &ptr, 8); if (mode < 0 || mode > 0777 || *ptr != '\0') { (void) fprintf(stderr, "%s: line %d, invalid mode -- %s\n", permfile, lineno, mode_str); continue; } dev_list = strtok((char *)NULL, field_delims); if (dev_list == NULL) { (void) fprintf(stderr, "%s: line %d, %s -- empty device list\n", permfile, lineno, console); continue; } device = strtok(dev_list, ":"); while (device != NULL) { l = strlen(device); ptr = &device[l - DIRWLDLEN]; if ((l > DIRWLDLEN) && (strcmp(ptr, DIRWILD) == 0)) { *ptr = '\0'; /* chop off wildcard */ dir_dev_acc(device, uid, gid, mode, permfile); } else { /* * change the owner/group/permission; * nonexistent devices are ignored */ if (chown(device, uid, gid) == -1) { if (errno != ENOENT) { (void) fprintf(stderr, "%s: ", permfile); perror(device); } } else { if ((chmod(device, mode) == -1) && (errno != ENOENT)) { (void) fprintf(stderr, "%s: ", permfile); perror(device); } } } device = strtok((char *)NULL, ":"); } } (void) fclose(fp); } /* * Apply owner/group/perms to all files (except "." and "..") * in a directory. */ static void dir_dev_acc(char *dir, uid_t uid, gid_t gid, mode_t mode, char *permfile) { DIR *dirp; struct dirent *direntp; char *name, path[MAX_LINELEN + MAXNAMELEN]; dirp = opendir(dir); if (dirp == NULL) return; while ((direntp = readdir(dirp)) != NULL) { name = direntp->d_name; if ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0)) continue; (void) sprintf(path, "%s/%s", dir, name); if (chown(path, uid, gid) == -1) { if (errno != ENOENT) { (void) fprintf(stderr, "%s: ", permfile); perror(path); } } else { if ((chmod(path, mode) == -1) && (errno != ENOENT)) { (void) fprintf(stderr, "%s: ", permfile); perror(path); } } } (void) closedir(dirp); }