| /* |
| * sulogin This program gives Unix machines a reasonable |
| * secure way to boot single user. It forces the |
| * user to supply the root password before a |
| * shell is started. |
| * |
| * If there is a shadow password file and the |
| * encrypted root password is "x" the shadow |
| * password will be used. |
| * |
| * Version: @(#)sulogin 2.85-3 23-Apr-2003 miquels@cistron.nl |
| * |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <pwd.h> |
| #include <sys/param.h> |
| #if defined BSD && !defined __GNU__ |
| #define _NO_SHADOW |
| #endif |
| #if defined __CYGWIN__ || defined __INTERIX |
| #ifdef __CYGWIN__ |
| #include <sys/cygwin.h> |
| #else |
| #include <interix/security.h> |
| #endif |
| #define _NO_SHADOW |
| #elif !defined _NO_SHADOW |
| #include <shadow.h> |
| #endif |
| #include <termios.h> |
| #include <errno.h> |
| #include <sys/ioctl.h> |
| #ifndef __INTERIX |
| // The bundled libcrypt for Cygwin is copied from GLIBC |
| #if defined __GLIBC__ || defined __SVR4 || defined __CYGWIN__ |
| # include <crypt.h> |
| #else |
| # include <unistd.h> |
| #endif |
| #endif |
| |
| #ifdef WITH_SELINUX |
| # include <selinux/selinux.h> |
| # include <selinux/get_context_list.h> |
| #endif |
| |
| #include <assert.h> |
| |
| #ifdef NEED_STRSEP |
| #include "strsep.c" |
| #endif |
| |
| #define CHECK_DES 1 |
| #define CHECK_MD5 1 |
| |
| #ifndef __INTERIX |
| #define F_PASSWD "/etc/passwd" |
| #ifndef _NO_SHADOW |
| #define F_SHADOW "/etc/shadow" |
| #endif |
| #endif |
| #define BINSH "/bin/sh" |
| #define STATICSH "/bin/sash" |
| |
| char *Version = "@(#)sulogin 2.85-3 23-Apr-2003 miquels@cistron.nl"; |
| |
| int timeout = 0; |
| int profile = 0; |
| |
| #ifndef IUCLC |
| # define IUCLC 0 |
| #endif |
| |
| #if 0 |
| /* |
| * Fix the tty modes and set reasonable defaults. |
| * (I'm not sure if this is needed under Linux, but..) |
| */ |
| void fixtty(void) |
| { |
| struct termios tty; |
| |
| tcgetattr(0, &tty); |
| |
| /* |
| * Set or adjust tty modes. |
| */ |
| tty.c_iflag &= ~(INLCR|IGNCR|IUCLC); |
| tty.c_iflag |= ICRNL; |
| tty.c_oflag &= ~(OCRNL|OLCUC|ONOCR|ONLRET|OFILL); |
| tty.c_oflag |= OPOST|ONLCR; |
| tty.c_cflag |= CLOCAL; |
| tty.c_lflag = ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE; |
| |
| /* |
| * Set the most important characters */ |
| */ |
| tty.c_cc[VINTR] = 3; |
| tty.c_cc[VQUIT] = 28; |
| tty.c_cc[VERASE] = 127; |
| tty.c_cc[VKILL] = 24; |
| tty.c_cc[VEOF] = 4; |
| tty.c_cc[VTIME] = 0; |
| tty.c_cc[VMIN] = 1; |
| tty.c_cc[VSTART] = 17; |
| tty.c_cc[VSTOP] = 19; |
| tty.c_cc[VSUSP] = 26; |
| |
| tcsetattr(0, TCSANOW, &tty); |
| } |
| #endif |
| |
| |
| /* |
| * Called at timeout. |
| */ |
| void alrm_handler() |
| { |
| } |
| |
| #ifndef __INTERIX |
| /* |
| * See if an encrypted password is valid. The encrypted |
| * password is checked for traditional-style DES and |
| * FreeBSD-style MD5 encryption. |
| */ |
| int valid(const char *pass) |
| { |
| const char *s; |
| int len; |
| |
| if (pass[0] == 0) return 1; |
| #if CHECK_MD5 |
| /* |
| * 3 bytes for the signature $1$ |
| * up to 8 bytes for the salt |
| * $ |
| * the MD5 hash (128 bits or 16 bytes) encoded in base64 = 22 bytes |
| */ |
| if (strncmp(pass, "$1$", 3) == 0) { |
| for(s = pass + 3; *s && *s != '$'; s++) |
| ; |
| if (*s++ != '$') return 0; |
| len = strlen(s); |
| if (len < 22 || len > 24) return 0; |
| |
| return 1; |
| } |
| #endif |
| #if CHECK_DES |
| if (strlen(pass) != 13) return 0; |
| for (s = pass; *s; s++) { |
| if ((*s < '0' || *s > '9') && |
| (*s < 'a' || *s > 'z') && |
| (*s < 'A' || *s > 'Z') && |
| *s != '.' && *s != '/') return 0; |
| } |
| #endif |
| return 1; |
| } |
| |
| /* |
| * Set a variable if the value is not NULL. |
| */ |
| void set(char **var, char *val) |
| { |
| if (val) *var = val; |
| } |
| #endif |
| |
| /* |
| * Get the root password entry. |
| */ |
| struct passwd *getrootpwent(int try_manually) |
| { |
| static struct passwd pwd; |
| struct passwd *pw; |
| #ifndef __INTERIX |
| FILE *fp; |
| static char line[256]; |
| char *p; |
| #endif |
| #ifndef _NO_SHADOW |
| struct spwd *spw; |
| static char sline[256]; |
| #endif |
| #if defined __CYGWIN__ || defined __INTERIX |
| uid_t myeuid = geteuid(); |
| #endif |
| |
| /* |
| * First, we try to get the password the standard |
| * way using normal library calls. |
| */ |
| #ifdef _NO_SHADOW |
| #ifdef __CYGWIN__ |
| pw = getpwuid(myeuid); |
| //pw = getpwnam("SYSTEM"); |
| assert(pw != NULL); |
| if(strcmp(pw->pw_name, "SYSTEM")) { |
| const char *sid = strrchr(pw->pw_gecos, ','); |
| assert(sid != NULL); |
| sid++; |
| assert(strcmp(sid, "S-1-5-18") == 0); |
| fprintf(stderr, |
| "warning: the local system account (SID %s) is not named 'SYSTEM' in " F_PASSWD, |
| sid); |
| } |
| #elif defined __INTERIX |
| pw = getpwuid(myeuid); |
| if(!pw) pw = getpwuid(197108); |
| //if(!pw) pw = getpwnam("Administrator"); |
| #else |
| pw = getpwuid(0); |
| //pw = getpwnam("root"); |
| #endif |
| #else |
| if ((pw = getpwnam("root")) && strcmp(pw->pw_passwd, "x") == 0 |
| && (spw = getspnam("root")) |
| ) pw->pw_passwd = spw->sp_pwdp; |
| #endif |
| if (pw || !try_manually) return pw; |
| |
| /* |
| * If we come here, we could not retrieve the root |
| * password through library calls and we try to |
| * read the password and shadow files manually. |
| */ |
| #ifdef __CYGWIN__ |
| pwd.pw_name = "SYSTEM"; |
| #else |
| pwd.pw_name = "root"; |
| #endif |
| pwd.pw_passwd = ""; |
| pwd.pw_gecos = "Super User"; |
| pwd.pw_dir = "/"; |
| pwd.pw_shell = ""; |
| #ifdef __INTERIX |
| pwd.pw_uid = myeuid; |
| pwd.pw_gid = getegid(); |
| #else |
| pwd.pw_uid = 0; |
| pwd.pw_gid = 0; |
| #endif |
| |
| #ifndef __INTERIX |
| if ((fp = fopen(F_PASSWD, "r")) == NULL) { |
| perror(F_PASSWD); |
| return &pwd; |
| } |
| |
| /* |
| * Find root in the password file. |
| */ |
| while((p = fgets(line, 256, fp)) != NULL) { |
| #ifdef __CYGWIN__ |
| if (strncmp(line, "SYSTEM:", 7) != 0) continue; |
| p += 7; |
| #else |
| if (strncmp(line, "root:", 5) != 0) continue; |
| p += 5; |
| #endif |
| set(&pwd.pw_passwd, strsep(&p, ":")); |
| (void)strsep(&p, ":"); |
| (void)strsep(&p, ":"); |
| set(&pwd.pw_gecos, strsep(&p, ":")); |
| set(&pwd.pw_dir, strsep(&p, ":")); |
| set(&pwd.pw_shell, strsep(&p, "\n")); |
| p = line; |
| break; |
| } |
| fclose(fp); |
| |
| /* |
| * If the encrypted password is valid |
| * or not found, return. |
| */ |
| if (p == NULL) { |
| fprintf(stderr, "%s: no entry for " |
| #ifdef __CYGWIN__ |
| "SYSTEM" |
| #else |
| "root" |
| #endif |
| "\n", F_PASSWD); |
| return &pwd; |
| } |
| if (valid(pwd.pw_passwd)) return &pwd; |
| #ifdef _NO_SHADOW |
| fprintf(stderr, "%s: " |
| #ifdef __CYGWIN__ |
| "SYSTEM" |
| #else |
| "root" |
| #endif |
| " password garbled\n", F_PASSWD); |
| #else |
| /* |
| * The password is invalid. If there is a |
| * shadow password, try it. |
| */ |
| strcpy(pwd.pw_passwd, ""); |
| if ((fp = fopen(F_SHADOW, "r")) == NULL) { |
| fprintf(stderr, "%s: root password garbled\n", F_PASSWD); |
| return &pwd; |
| } |
| while((p = fgets(sline, 256, fp)) != NULL) { |
| if (strncmp(sline, "root:", 5) != 0) continue; |
| p += 5; |
| set(&pwd.pw_passwd, strsep(&p, ":")); |
| break; |
| } |
| fclose(fp); |
| |
| /* |
| * If the password is still invalid, |
| * NULL it, and return. |
| */ |
| if (p == NULL) { |
| fprintf(stderr, "%s: no entry for root\n", F_SHADOW); |
| strcpy(pwd.pw_passwd, ""); |
| } |
| if (!valid(pwd.pw_passwd)) { |
| fprintf(stderr, "%s: root password garbled\n", F_SHADOW); |
| strcpy(pwd.pw_passwd, ""); |
| } |
| #endif /* _NO_SHADOW */ |
| #endif /* !__INTERIX */ |
| return &pwd; |
| } |
| |
| /* |
| * Ask for the password. Note that there is no |
| * default timeout as we normally skip this during boot. |
| */ |
| char *getpasswd(const char *crypted) |
| { |
| struct sigaction sa; |
| struct termios old, tty; |
| static char pass[128]; |
| char *ret = pass; |
| |
| #ifndef __INTERIX |
| if (crypted[0]) { |
| #endif |
| printf("Give " |
| #ifdef __CYGWIN__ |
| "SYSTEM" |
| #elif defined __INTERIX |
| "Administrator" |
| #else |
| "root" |
| #endif |
| " password for maintenance\n"); |
| #ifndef __INTERIX |
| } else { |
| printf("Press enter for maintenance\n"); |
| } |
| #endif |
| printf("(or type Control-D to continue): "); |
| fflush(stdout); |
| |
| tcgetattr(0, &old); |
| tcgetattr(0, &tty); |
| tty.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY); |
| tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP); |
| tcsetattr(0, TCSANOW, &tty); |
| |
| pass[sizeof(pass) - 1] = 0; |
| |
| sa.sa_handler = alrm_handler; |
| sa.sa_flags = 0; |
| sigaction(SIGALRM, &sa, NULL); |
| if (timeout) alarm(timeout); |
| |
| if (read(0, pass, sizeof(pass) - 1) <= 0) { |
| ret = NULL; |
| } else { |
| unsigned int i; |
| for(i = 0; i < sizeof(pass) && pass[i]; i++) { |
| if (pass[i] == '\r' || pass[i] == '\n') { |
| pass[i] = 0; |
| break; |
| } |
| } |
| } |
| alarm(0); |
| tcsetattr(0, TCSANOW, &old); |
| printf("\n"); |
| |
| return ret; |
| } |
| |
| /* |
| * Password was OK, execute a shell. |
| */ |
| void sushell(const struct passwd *pwd) |
| { |
| char shell[128]; |
| char home[128]; |
| const char *p; |
| const char *sushell; |
| |
| /* |
| * Set directory and shell. |
| */ |
| (void)chdir(pwd->pw_dir); |
| if ((p = getenv("SUSHELL")) != NULL) |
| sushell = p; |
| else if ((p = getenv("sushell")) != NULL) |
| sushell = p; |
| else { |
| if (pwd->pw_shell[0]) |
| sushell = pwd->pw_shell; |
| else |
| sushell = BINSH; |
| } |
| if ((p = strrchr(sushell, '/')) == NULL) |
| p = sushell; |
| else |
| p++; |
| snprintf(shell, sizeof(shell), profile ? "-%s" : "%s", p); |
| |
| /* |
| * Set some important environment variables. |
| */ |
| getcwd(home, sizeof(home)); |
| setenv("HOME", home, 1); |
| setenv("LOGNAME", "root", 1); |
| setenv("USER", "root", 1); |
| if (!profile) |
| setenv("SHLVL","0",1); |
| |
| /* |
| * Try to execute a shell. |
| */ |
| setenv("SHELL", sushell, 1); |
| signal(SIGINT, SIG_DFL); |
| signal(SIGTSTP, SIG_DFL); |
| signal(SIGQUIT, SIG_DFL); |
| #ifdef WITH_SELINUX |
| if (is_selinux_enabled > 0) { |
| security_context_t scon=NULL; |
| char *seuser=NULL; |
| char *level=NULL; |
| if (getseuserbyname("root", &seuser, &level) == 0) |
| if (get_default_context_with_level(seuser, level, 0, &scon) > 0) { |
| if (setexeccon(scon) != 0) |
| fprintf(stderr, "setexeccon faile\n"); |
| freecon(scon); |
| } |
| free(seuser); |
| free(level); |
| } |
| #endif |
| execl(sushell, shell, NULL); |
| perror(sushell); |
| |
| setenv("SHELL", BINSH, 1); |
| execl(BINSH, profile ? "-sh" : "sh", NULL); |
| perror(BINSH); |
| |
| /* Fall back to staticly linked shell if both the users shell |
| and /bin/sh failed to execute. */ |
| setenv("SHELL", STATICSH, 1); |
| execl(STATICSH, STATICSH, NULL); |
| perror(STATICSH); |
| } |
| |
| #ifdef __CYGWIN__ |
| static int is_sid_equal(const char *pw_gecos, const char *sid) { |
| char *commas = strrchr(pw_gecos, ','); |
| if(!commas) return 0; |
| return strcmp(commas + 1, sid) == 0; |
| } |
| #endif |
| |
| static void usage(void) |
| { |
| fprintf(stderr, "Usage: sulogin [-e] [-p] [-t <timeout>] [<tty device>]\n"); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| char *tty = NULL; |
| char *p; |
| struct passwd *pwd; |
| int c, fd = -1; |
| int opt_e = 0; |
| pid_t pid, pgrp, ttypgrp; |
| #ifndef __INTERIX |
| pid_t ppgrp; |
| #endif |
| |
| uid_t myeuid = geteuid(); |
| #ifdef __CYGWIN__ |
| struct passwd *mypwd = getpwuid(myeuid); |
| #elif defined __INTERIX |
| struct passwd *administrator_pwd = getpwuid(197108); |
| #endif |
| |
| /* |
| * See if we have a timeout flag. |
| */ |
| opterr = 0; |
| while((c = getopt(argc, argv, "ept:")) != EOF) switch(c) { |
| case 't': |
| timeout = atoi(optarg); |
| break; |
| case 'p': |
| profile = 1; |
| break; |
| case 'e': |
| opt_e = 1; |
| break; |
| default: |
| usage(); |
| /* Do not exit! */ |
| break; |
| } |
| |
| #ifdef __CYGWIN__ |
| if (!mypwd || !is_sid_equal(mypwd->pw_gecos, "S-1-5-18")) { |
| #elif defined __INTERIX |
| if (myeuid != 66834 && myeuid != 197108 && myeuid != 1049076) { |
| #else |
| if (myeuid != 0) { |
| #endif |
| fprintf(stderr, "sulogin: only " |
| #if defined __CYGWIN__ || defined __INTERIX |
| #ifdef __INTERIX |
| "Administrator or " |
| #endif |
| "SYSTEM" |
| #else |
| "root" |
| #endif |
| " can run sulogin.\n"); |
| exit(1); |
| } |
| |
| #ifdef __INTERIX |
| if(!administrator_pwd) { |
| fprintf(stderr, "sulogin: Administrator user not found.\n"); |
| return 1; |
| } |
| #endif |
| |
| /* |
| * See if we need to open an other tty device. |
| */ |
| signal(SIGINT, SIG_IGN); |
| signal(SIGQUIT, SIG_IGN); |
| signal(SIGTSTP, SIG_IGN); |
| if (optind < argc) tty = argv[optind]; |
| if (tty) { |
| if ((fd = open(tty, O_RDWR)) < 0) { |
| perror(tty); |
| } else if (!isatty(fd)) { |
| fprintf(stderr, "%s: not a tty\n", tty); |
| close(fd); |
| } else { |
| |
| /* |
| * Only go through this trouble if the new |
| * tty doesn't fall in this process group. |
| */ |
| pid = getpid(); |
| pgrp = getpgrp(); |
| #ifndef __INTERIX |
| ppgrp = getpgid(getppid()); |
| #endif |
| ioctl(fd, TIOCGPGRP, &ttypgrp); |
| |
| if (pgrp != ttypgrp |
| #ifndef __INTERIX |
| && ppgrp != ttypgrp |
| #endif |
| ) { |
| #ifndef __INTERIX |
| if (pid != getsid(0)) { |
| if (pid == getpgrp()) |
| setpgid(0, getpgid(getppid())); |
| setsid(); |
| } |
| #endif |
| #ifdef TIOCNOTTY |
| signal(SIGHUP, SIG_IGN); |
| ioctl(0, TIOCNOTTY, (char *)1); |
| signal(SIGHUP, SIG_DFL); |
| #endif |
| close(0); |
| close(1); |
| close(2); |
| close(fd); |
| fd = open(tty, O_RDWR); |
| ioctl(0, TIOCSCTTY, (char *)1); |
| dup(fd); |
| dup(fd); |
| } else |
| close(fd); |
| } |
| #if !defined __CYGWIN__ && !defined __INTERIX |
| } else if (getpid() == 1) { |
| /* We are init. We hence need to set a session anyway */ |
| setsid(); |
| if (ioctl(0, TIOCSCTTY, (char *)1)) |
| perror("ioctl(TIOCSCTTY)"); |
| #endif |
| } |
| |
| /* |
| * Get the root password. |
| */ |
| if ((pwd = getrootpwent(opt_e)) == NULL) { |
| fprintf(stderr, "sulogin: cannot open password database!\n"); |
| sleep(2); |
| } |
| |
| /* |
| * Ask for the password. |
| */ |
| while(pwd) { |
| if ((p = getpasswd(pwd->pw_passwd)) == NULL) break; |
| #ifdef __INTERIX |
| //if(setuser("Administrator", p, SU_CHECK) == 0) sushell(pwd); |
| if(setuser(administrator_pwd->pw_name, p, SU_CHECK) == 0) { |
| printf("Starting shell for %s ...\n", pwd->pw_name); |
| sushell(pwd); |
| } |
| #else |
| if (pwd->pw_passwd[0] == 0 || |
| strcmp(crypt(p, pwd->pw_passwd), pwd->pw_passwd) == 0) |
| sushell(pwd); |
| #endif |
| printf("Login incorrect.\n"); |
| } |
| |
| /* |
| * User pressed Control-D. |
| */ |
| return 0; |
| } |
| |