/* This program is derived from 4.3 BSD software and is subject to the copyright notice below. The port to HP-UX has been motivated by the incapability of 'rlogin'/'rlogind' as per HP-UX 6.5 (and 7.0) to transfer window sizes. Changes: - General HP-UX portation. Use of facilities not available in HP-UX (e.g. setpriority) has been eliminated. Utmp/wtmp handling has been ported. - The program uses BSD command line options to be used in connection with e.g. 'rlogind' i.e. 'new login'. - HP features left out: logging of bad login attempts in /etc/btmp, they are sent to syslog password expiry '*' as login shell, add it if you need it - BSD features left out: quota checks password expiry analysis of terminal type (tset feature) - BSD features thrown in: Security logging to syslogd. This requires you to have a (ported) syslog system -- 7.0 comes with syslog 'Lastlog' feature. - A lot of nitty gritty details has been adjusted in favour of HP-UX, e.g. /etc/securetty, default paths and the environment variables assigned by 'login'. - We do *nothing* to setup/alter tty state, under HP-UX this is to be done by getty/rlogind/telnetd/some one else. Michael Glad (glad@daimi.dk) Computer Science Department Aarhus University Denmark 1990-07-04 1991-09-24 glad@daimi.aau.dk: HP-UX 8.0 port: - now explictly sets non-blocking mode on descriptors - strcasecmp is now part of HP-UX 1992-02-05 poe@daimi.aau.dk: Ported the stuff to Linux 0.12 From 1992 till now (1995) this code for Linux has been maintained at ftp.daimi.aau.dk:/pub/linux/poe/ */ /* * Copyright (c) 1980, 1987, 1988 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, * advertising materials, and other materials related to such * distribution and use acknowledge that the software was developed * by the University of California, Berkeley. The name of the * University may not be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* * login [ name ] * login -h hostname (for telnetd, etc.) * login -f name (for pre-authenticated login: datakit, xterm, etc.) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SHADOW_PWD #include #endif #include #include "pathnames.h" #define P_(s) s void opentty P_((const char *tty)); int getloginname P_((void)); void timedout P_((int)); void sigint P_((int)); void motd P_((void)); void checknologin P_((void)); void dolastlog P_((int quiet)); void badlogin P_((char *name)); char *stypeof P_((char *ttyid)); void checktty P_((char *user)); void getstr P_((char *buf, int cnt, char *err)); void sleepexit P_((int eval)); void caller_id P_((char **host, char **ip)); char *from P_((void)); #undef P_ #if KERBEROS #include #include char realm[REALM_SZ]; int kerror = KSUCCESS, notickets = 1; #endif #define TTYGRPNAME "tty" /* name of group to own ttys */ /**# define TTYGRPNAME "other" **/ #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif /* * This bounds the time given to login. Not a define so it can * be patched on machines where it's too small. */ int timeout = 120; struct passwd *pwd; int failures; char term[64], *hostname, *username, *tty = (char*)0; char *host = (char*)0; char *ip = (char*)0; char thishost[100]; #ifndef linux struct sgttyb sgttyb; struct tchars tc = { CINTR, CQUIT, CSTART, CSTOP, CEOT, CBRK }; struct ltchars ltc = { CSUSP, CDSUSP, CRPRNT, CFLUSH, CWERASE, CLNEXT }; #endif char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; /* provided by Linus Torvalds 16-Feb-93 */ void opentty(const char *tty) { int i; int fd = open(tty, O_RDWR); for (i = 0; i < fd; i++) close(i); for (i = 0; i < 3; i++) dup2(fd, i); if (fd >= 3) close(fd); } int main(int argc, char **argv) { extern int errno, optind; extern char *optarg, **environ; struct timeval tp; struct tm *ttp; struct group *gr; register int ch; register char *p; int ask, fflag, hflag, pflag, cnt; int quietlog, passwd_req, ioctlval; char *domain, *salt, *ttyn, *pp; char tbuf[MAXPATHLEN + 2], tname[sizeof(_PATH_TTY) + 10]; char *termenv; char tmp[200]; int ok; signal(SIGALRM, timedout); alarm((unsigned int) timeout); signal(SIGQUIT, SIG_IGN); signal(SIGINT, SIG_IGN); setpriority(PRIO_PROCESS, 0, 0); #if QUOTA quota(Q_SETUID, 0, 0, 0); #endif caller_id(&host, &ip); /* * -p is used by getty to tell login not to destroy the environment * -f is used to skip a second login authentication * -h is used by other servers to pass the name of the remote * host to login so that it may be placed in utmp and wtmp */ gethostname(tbuf, sizeof(tbuf)); strncpy(thishost, tbuf, sizeof(thishost) - 1); domain = strchr(tbuf, '.'); fflag = hflag = pflag = 0; passwd_req = 1; while ((ch = getopt(argc, argv, "fh:p")) != EOF) switch (ch) { case 'f': fflag = 1; break; case 'h': if (getuid()) { printf("login: -h for super-user only.\n"); exit(1); } hflag = 1; if (host) free(host); host = strdup(optarg); break; case 'p': pflag = 1; break; case '?': default: printf("usage: login [-p] [username]\n"); exit(1); } argc -= optind; argv += optind; if (*argv) { username = *argv; ask = 0; } else ask = 1; ttyn = ttyname(0); if (*ttyn == '\0') { ttyn = (char*)0; printf("Can't get tty name!\n"); exit(0); } for (cnt = getdtablesize(); cnt > 2; cnt--) close(cnt); setpgrp(); { struct termios tt, ttt; tcgetattr(0, &tt); ttt = tt; ttt.c_cflag &= ~HUPCL; if ((chown(ttyn, 0, 0) == 0) && (chmod(ttyn, 0622) == 0)) { tcsetattr(0, TCSAFLUSH, &ttt); signal(SIGHUP, SIG_IGN); /* so vhangup() wont kill us */ vhangup(); signal(SIGHUP, SIG_DFL); } setsid(); /* re-open stdin,stdout,stderr after vhangup() closed them */ /* if it did, after 0.99.5 it doesn't! */ opentty(ttyn); tcsetattr(0, TCSAFLUSH, &tt); } if (tty = strrchr(ttyn, '/')) ++tty; else tty = ttyn; openlog("login", LOG_ODELAY, LOG_AUTH); for (cnt = 0;; ask = 1) { ok = 1; ioctlval = 0; if (ask) { fflag = 0; ok = getloginname(); } else ok = namevalid(username); if (ok) { checktty(username); strcpy(tbuf, username); if (pwd = getpwnam(username)) salt = pwd->pw_passwd; else salt = "xx"; /* if user not super-user, check for disabled logins */ if (pwd == NULL || pwd->pw_uid) checknologin(); /* * Disallow automatic login to root; if not invoked by * root, disallow if the uid's differ. */ if (fflag && pwd) { int uid = getuid(); passwd_req = pwd->pw_uid == 0 || (uid && uid != pwd->pw_uid); } } else passwd_req = 1; /* * If no pre-authentication and a password exists * for this user, prompt for one and verify it. */ if (!passwd_req || (pwd && !*pwd->pw_passwd)) break; setpriority(PRIO_PROCESS, 0, -4); pp = getpass("Password: "); p = crypt(pp, salt); setpriority(PRIO_PROCESS, 0, 0); memset(pp, 0, strlen(pp)); if (ok && pwd && !strcmp(p, pwd->pw_passwd)) break; printf("Login incorrect\n"); failures++; if (username) badlogin(username); /* we allow 10 tries, but after 3 we start backing off */ if (++cnt > 3) { if (cnt >= 10) { sleepexit(1); } sleep((unsigned int) ((cnt - 3) * 5)); } } /* committed to login -- turn off timeout */ alarm((unsigned int) 0); #ifdef QUOTA if (quota(Q_SETUID, pwd->pw_uid, 0, 0) < 0 && errno != EINVAL) { switch (errno) { case EUSERS: printf("Too many users logged on already.\nTry again later.\n"); break; case EPROCLIM: printf("You have too many processes running.\n"); break; default: perror("quota (Q_SETUID)"); } sleepexit(0); } #endif /* paranoia... */ endpwent(); /* This requires some explanation: As root we may not be able to read the directory of the user if it is on an NFS mounted filesystem. We temporarily set our effective uid to the user-uid making sure that we keep root privs. in the real uid. A portable solution would require a fork(), but we rely on Linux having the BSD setreuid() */ { char tmpstr[MAXPATHLEN]; uid_t ruid = getuid(); gid_t egid = getegid(); strncpy(tmpstr, pwd->pw_dir, MAXPATHLEN - 12); strncat(tmpstr, ("/" _PATH_HUSHLOGIN), MAXPATHLEN); setregid(-1, pwd->pw_gid); setreuid(0, pwd->pw_uid); quietlog = (access(tmpstr, R_OK) == 0); setuid(0); /* setreuid doesn't do it alone! */ setreuid(ruid, 0); setregid(-1, egid); } /* for linux, write entries in utmp and wtmp */ { struct utmp ut; char *ttyabbrev; int wtmp; memset((char *) &ut, 0, sizeof(ut)); ut.ut_type = USER_PROCESS; ut.ut_pid = getpid(); strncpy(ut.ut_line, ttyn + sizeof("/dev/") - 1, sizeof(ut.ut_line)); ttyabbrev = ttyn + sizeof("/dev/tty") - 1; strncpy(ut.ut_id, ttyabbrev, sizeof(ut.ut_id)); time(&ut.ut_time); strncpy(ut.ut_user, username, sizeof(ut.ut_user)); if (host) strncpy(ut.ut_host, host, sizeof ut.ut_host); if (ip) { ulong mip = inet_addr(ip); memcpy(&ut.ut_addr, &mip, sizeof ut.ut_addr); } utmpname(_PATH_UTMP); setutent(); pututline(&ut); endutent(); if ((wtmp = open(_PATH_WTMP, O_APPEND | O_WRONLY)) >= 0) { flock(wtmp, LOCK_EX); write(wtmp, (char *) &ut, sizeof(ut)); flock(wtmp, LOCK_UN); close(wtmp); } } dolastlog(quietlog); if (!hflag) { /* XXX */ static struct winsize win = {0, 0, 0, 0}; ioctl(0, TIOCSWINSZ, &win); } chown(ttyn, pwd->pw_uid, (gr = getgrnam(TTYGRPNAME)) ? gr->gr_gid : pwd->pw_gid); #ifdef USE_TTY_GROUP chmod(ttyn, 0620); #else chmod(ttyn, 0600); #endif setgid(pwd->pw_gid); initgroups(username, pwd->pw_gid); #if QUOTA quota(Q_DOWARN, pwd->pw_uid, (dev_t) - 1, 0); #endif if (*pwd->pw_shell == '\0') pwd->pw_shell = _PATH_BSHELL; #ifndef linux /* turn on new line discipline for the csh */ else if (!strcmp(pwd->pw_shell, _PATH_CSHELL)) { ioctlval = NTTYDISC; ioctl(0, TIOCSETD, &ioctlval); } #endif /* preserve TERM even without -p flag */ { char *ep; if (!((ep = getenv("TERM")) && (termenv = strdup(ep)))) termenv = "dumb"; } /* destroy environment unless user has requested preservation */ if (!pflag) { environ = (char **) malloc(sizeof(char *)); memset(environ, 0, sizeof(char *)); } setenv("HOME", pwd->pw_dir, 0); /* legal to override */ if (pwd->pw_uid) setenv("PATH", _PATH_DEFPATH, 1); else setenv("PATH", _PATH_DEFPATH_ROOT, 1); setenv("SHELL", pwd->pw_shell, 1); setenv("TERM", termenv, 1); /* mailx will give a funny error msg if you forget this one */ sprintf(tmp, "%s/%s", _PATH_MAILDIR, pwd->pw_name); setenv("MAIL", tmp, 0); /* LOGNAME is not documented in login(1) but HP-UX 6.5 does it. We'll not allow modifying it. */ setenv("LOGNAME", pwd->pw_name, 1); if (pwd->pw_uid == 0) syslog(LOG_NOTICE, "root %s", from()); if (!quietlog) { struct stat st; motd(); sprintf(tbuf, "%s/%s", _PATH_MAILDIR, pwd->pw_name); if (stat(tbuf, &st) == 0 && st.st_size != 0) printf("You have %smail.\n", (st.st_mtime > st.st_atime) ? "new " : ""); } signal(SIGALRM, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGTSTP, SIG_IGN); signal(SIGHUP, SIG_DFL); /* discard permissions last so can't get killed and drop core */ if (setuid(pwd->pw_uid) < 0 && pwd->pw_uid) { syslog(LOG_ALERT, "setuid() failed"); exit(1); } /* wait until here to change directory! */ if (chdir(pwd->pw_dir) < 0) { printf("No directory %s!\n", pwd->pw_dir); if (chdir("/")) exit(0); pwd->pw_dir = "/"; printf("Logging in with home = \"/\".\n"); } /* if the shell field has a space: treat it like a shell script */ if (strchr(pwd->pw_shell, ' ')) { char *buff = malloc(strlen(pwd->pw_shell) + 6); if (buff) { strcpy(buff, "exec "); strcat(buff, pwd->pw_shell); execlp("/bin/sh", "-sh", "-c", buff, (char *) 0); printf("login: couldn't exec shell script: %s.\n", strerror(errno)); exit(0); } printf("login: no memory for shell script.\n"); exit(0); } tbuf[0] = '-'; strcpy(tbuf + 1, ((p = strrchr(pwd->pw_shell, '/')) ? p + 1 : pwd->pw_shell)); execlp(pwd->pw_shell, tbuf, (char *) 0); printf("login: no shell: %s.\n", strerror(errno)); exit(0); } char * from() { static char bfr[200]; int siz; bfr[0] = 0; if (tty) snprintf(bfr, sizeof bfr, "on %s", tty); siz = strlen(bfr); if (host && ip) snprintf(bfr+siz, sizeof bfr-siz, " from %s [%s]", host, ip); else if (host) snprintf(bfr+siz, sizeof bfr-siz, " from %s", host); else if (ip) snprintf(bfr+siz, sizeof bfr-siz, " from [%s]", ip); return bfr; } int namevalid(char *p) { return (*p != '-' && *p != '+'); } int getloginname() { register int ch; register char *p; static char nbuf[UT_NAMESIZE + 1]; int cnt, cnt2; cnt2 = 0; username = 0; for (;;) { cnt = 0; printf("\n%s login: ", thishost); fflush(stdout); for (p = nbuf; (ch = getchar()) != '\n';) { if (ch == '\r') continue; if (ch == EOF) { badlogin("EOF"); exit(0); } if (p < nbuf + UT_NAMESIZE) *p++ = ch; cnt++; if (cnt > UT_NAMESIZE + 20) { badlogin("Name too long"); return 0; } } if (p > nbuf) { fflush(stdin); *p = '\0'; username = nbuf; return namevalid(nbuf); } cnt2++; if (cnt2 > 50) { badlogin("EXCESSIVE linefeeds"); return 0; } } return 1; } void timedout(int sig) { struct termio ti; printf("Login timed out after %d seconds\n", timeout); /* reset echo */ ioctl(0, TCGETA, &ti); ti.c_lflag |= ECHO; ioctl(0, TCSETA, &ti); exit(0); } jmp_buf motdinterrupt; void motd() { register int fd, nchars; void (*oldint) (), sigint(); char tbuf[8192]; if ((fd = open(_PATH_MOTDFILE, O_RDONLY, 0)) < 0) return; oldint = signal(SIGINT, sigint); if (setjmp(motdinterrupt) == 0) while ((nchars = read(fd, tbuf, sizeof(tbuf))) > 0) write(fileno(stdout), tbuf, nchars); signal(SIGINT, oldint); close(fd); } void sigint(int signo) { longjmp(motdinterrupt, 1); } void checknologin() { register int fd, nchars; char tbuf[8192]; if ((fd = open(_PATH_NOLOGIN, O_RDONLY, 0)) >= 0) { while ((nchars = read(fd, tbuf, sizeof(tbuf))) > 0) write(fileno(stdout), tbuf, nchars); sleepexit(0); } } void dolastlog(quiet) int quiet; { struct lastlog ll; int fd; if ((fd = open(_PATH_LASTLOG, O_RDWR, 0)) >= 0) { lseek(fd, (off_t) pwd->pw_uid * sizeof(ll), L_SET); if (!quiet) { if (read(fd, (char *) &ll, sizeof(ll)) == sizeof(ll) && ll.ll_time != 0) { printf("Last login: %.*s ", 24 - 5, (char *) ctime(&ll.ll_time)); if (*ll.ll_host != '\0') printf("from %.*s\n", (int) sizeof(ll.ll_host), ll.ll_host); else printf("on %.*s\n", (int) sizeof(ll.ll_line), ll.ll_line); } lseek(fd, (off_t) pwd->pw_uid * sizeof(ll), L_SET); } memset((char *) &ll, 0, sizeof(ll)); time(&ll.ll_time); strncpy(ll.ll_line, tty, sizeof(ll.ll_line)); if (host) strncpy(ll.ll_host, host, sizeof(ll.ll_host)); write(fd, (char *) &ll, sizeof(ll)); close(fd); } } void badlogin(name) char *name; { if (failures == 0) return; syslog(LOG_NOTICE, "%d failure%s for %s %s", failures, failures>1 ? "s" : "", name, from()); } void confusion(char *reason) { if (failures == 0) return; syslog(LOG_NOTICE, "%d failure%s (%s) %s", failures, failures>1 ? "s" : "", reason, from()); } #undef UNKNOWN #define UNKNOWN "su" #ifndef linux char * stypeof(ttyid) char *ttyid; { struct ttyent *t; return (ttyid && (t = getttynam(ttyid)) ? t->ty_type : UNKNOWN); } #endif void checktty(char *user) { #if !I_WISH_THIS_WOULD_WORK if (! (host||ip) ) return; #endif #if CAN_AUTHENTICATE_OURSELF if (!hosts_ctl("login", host, ip, user)) { printf("Login from %s denied.\n", host ? host : (ip ? ip : "unauthenticated host")); syslog(LOG_NOTICE, "refused %s %s", user, from()); badlogin(user); sleepexit(1); } #endif } void getstr(buf, cnt, err) char *buf, *err; int cnt; { char ch; do { if (read(0, &ch, sizeof(ch)) != sizeof(ch)) exit(1); if (--cnt < 0) { printf("%s too long\r\n", err); sleepexit(1); } *buf++ = ch; } while (ch); } void sleepexit(int status) { sleep(2); exit(status); } void caller_id(char **host, char **ip) { #if I_WISH_THIS_WOULD_WORK struct sockaddr_in sin; struct hostent *ha; int sval; char *p; sval = sizeof(sin); if (getpeername(0, &sin, &sval) < 0) { /* If we can't get the peer information, either it's a * local connection or something badly went wrong. If * it's a local connection, we simply return, but if * something went wrong we set the host and IP to a * pair of bogus names before we return. * * In most cases, we won't be able to get the peer information, * because we'll be living on the inside of a pty, with rlogind * or telnetd on the other side. But maybe we'll get lucky. */ if (errno == ENOTSOCK) return; } else { if (p = inet_ntoa(sin.sin_addr)) *ip = strdup(p); if (ha = gethostbyaddr((char*) &sin.sin_addr, 4, sin.sin_family)) *host = strdup(ha->h_name); } if (!*ip) *ip = strdup("unknown"); if (!*host) *host = strdup("unknown"); #endif } /* caller_id */