login.c (7024B)
1 /* login.c - Start a session on the system. 2 * 3 * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com> 4 * 5 * No support for PAM/securetty/selinux/login script/issue/utmp 6 * Relies on libcrypt for hash calculation. 7 * 8 * TODO: this command predates "pending" but needs cleanup. It #defines 9 * random stuff, calls exit() form a signal handler... yeah. 10 11 USE_LOGIN(NEWTOY(login, ">1f:ph:", TOYFLAG_BIN|TOYFLAG_NEEDROOT)) 12 13 config LOGIN 14 bool "login" 15 default y 16 depends on TOYBOX_SHADOW 17 help 18 usage: login [-p] [-h host] [-f USERNAME] [USERNAME] 19 20 Log in as a user, prompting for username and password if necessary. 21 22 -p Preserve environment 23 -h The name of the remote host for this login 24 -f login as USERNAME without authentication 25 */ 26 27 #include <assert.h> 28 29 #define FOR_login 30 #include "toys.h" 31 32 #ifndef LOGINEXEC_PATH 33 #define LOGINEXEC_PATH "/etc/loginexec" 34 #endif 35 36 GLOBALS( 37 char *hostname; 38 char *username; 39 40 int login_timeout, login_fail_timeout; 41 ) 42 43 static void login_timeout_handler(int sig __attribute__((unused))) 44 { 45 printf("\nLogin timed out after %d seconds.\n", TT.login_timeout); 46 exit(0); 47 } 48 49 unsigned char login_hashcmp(const char *s1, const char *s2) 50 { 51 // constant time string comparison 52 unsigned char result = 0; 53 do { 54 result |= (*s1) ^ (*s2); 55 } while(*(s1++) && *(s2++)); 56 return result; 57 } 58 59 void login_main(void) 60 { 61 char *forbid[] = { 62 "BASH_ENV", "ENV", "HOME", "IFS", "LD_LIBRARY_PATH", "LD_PRELOAD", 63 "LD_TRACE_LOADED_OBJECTS", "LD_BIND_NOW", "LD_AOUT_LIBRARY_PATH", 64 "LD_AOUT_PRELOAD", "LD_NOWARN", "LD_KEEPDIR", "SHELL" 65 }; 66 int hh = toys.optflags&FLAG_h, count, tty; 67 int pipefd[2] = {-1, -1}; 68 int child_pid; 69 char uu[33], *username, *pass = 0, *ss; 70 struct passwd *pwd = 0; 71 char *loginexec = 0; 72 unsigned char use_loginexec = 0; 73 74 for (tty=0; tty<3; tty++) if (isatty(tty)) break; 75 if (tty == 3) error_exit("no tty"); 76 77 for (count = 0; count < ARRAY_LEN(forbid); count++) unsetenv(forbid[count]); 78 79 openlog("login", LOG_PID | LOG_CONS, LOG_AUTH); 80 xsignal(SIGALRM, login_timeout_handler); 81 82 if (TT.username) username = TT.username; 83 else username = *toys.optargs; 84 for (count = 0; count < 3; count++) { 85 alarm(TT.login_timeout = 60); 86 tcflush(0, TCIFLUSH); 87 88 if (!username) { 89 int i; 90 91 memset(username = uu, 0, sizeof(uu)); 92 gethostname(uu, sizeof(uu)-1); 93 printf("%s%slogin: ", *uu ? uu : "", *uu ? " " : ""); 94 fflush(stdout); 95 96 if(!fgets(uu, sizeof(uu)-1, stdin)) _exit(1); 97 98 // Remove trailing \n and so on 99 for (i = 0; i<sizeof(uu); i++) if (uu[i]<=' ' || uu[i]==':') uu[i]=0; 100 if (!*uu) { 101 username = 0; 102 continue; 103 } 104 } 105 106 // If user exists and isn't locked 107 pwd = getpwnam(username); 108 if (pwd && *pwd->pw_passwd != '!' && *pwd->pw_passwd != '*') { 109 110 // Pre-authenticated or passwordless 111 if (TT.username || !*pwd->pw_passwd) break; 112 113 // fetch shadow password if necessary 114 if (*(pass = pwd->pw_passwd) == 'x') { 115 struct spwd *spwd = getspnam (username); 116 117 if (spwd) pass = spwd->sp_pwdp; 118 } 119 120 loginexec = xmprintf("%s/loginexec", pwd->pw_dir); 121 use_loginexec = !access(loginexec, X_OK); 122 123 } else if (TT.username) error_exit("bad -f '%s'", TT.username); 124 125 126 // Verify password. (Prompt for password _before_ checking disable state.) 127 if (!read_password(toybuf, sizeof(toybuf), "Password: ")) { 128 int x = pass && (ss = crypt(toybuf, pass)) && !login_hashcmp(pass, ss); 129 130 if (x && use_loginexec) { 131 if(!pipe(pipefd)) { 132 if((child_pid = fork())) { 133 if(child_pid == -1) { 134 /* error */ 135 perror("fork"); 136 close(pipefd[0]); 137 close(pipefd[1]); 138 pipefd[0] = pipefd[1] = -1; 139 } else { 140 /* parent */ 141 close(pipefd[1]); 142 } 143 } else { 144 /* child */ 145 dup2(pipefd[1], 1); 146 xprintf("%s\n", toybuf); 147 exit(0); 148 } 149 } else { 150 /* creating pipe failed */ 151 perror("pipe"); 152 pipefd[0] = pipefd[1] = -1; 153 } 154 if (!access(LOGINEXEC_PATH, X_OK)) { 155 if (!(toys.optflags&FLAG_p)) { 156 char *term = getenv("TERM"); 157 158 clearenv(); 159 if (term) setenv("TERM", term, 1); 160 } 161 162 setenv("USER", pwd->pw_name, 1); 163 setenv("LOGNAME", pwd->pw_name, 1); 164 setenv("HOME", pwd->pw_dir, 1); 165 setenv("SHELL", pwd->pw_shell, 1); 166 if(pipefd[0] >= 0) { 167 setenv("LOGINPASS_FD", xmprintf("%d", pipefd[0]), 1); 168 } 169 /* TODO */ 170 /* set UID, GID and GIDLIST env variables */ 171 172 alarm(0); // remove pending SIGALRM for stale login 173 174 execl(LOGINEXEC_PATH, LOGINEXEC_PATH, loginexec, (char *)0); 175 } else { 176 syslog(LOG_WARNING, "login executable '%s' unavailable when attempting to log in as '%s' on %s %s%s", 177 LOGINEXEC_PATH, 178 pwd ? pwd->pw_name : "UNKNOWN", 179 ttyname(tty), 180 hh ? "from " : "", hh ? TT.hostname : ""); 181 182 puts("global loginexec unavailable, using user shell"); 183 use_loginexec = 0; 184 } 185 } 186 // password go bye-bye now. 187 memset(toybuf, 0, sizeof(toybuf)); 188 if (x) break; 189 } 190 191 syslog(LOG_WARNING, "invalid password for '%s' on %s %s%s", 192 pwd ? pwd->pw_name : "UNKNOWN", 193 ttyname(tty), hh ? "from " : "", hh ? TT.hostname : ""); 194 195 sleep(3); 196 puts("Login incorrect"); 197 198 username = 0; 199 pwd = 0; 200 } 201 202 alarm(0); 203 // This had password data in it, and we reuse for motd below 204 memset(toybuf, 0, sizeof(toybuf)); 205 206 if (!pwd) error_exit("max retries (3)"); 207 208 // Check twice because "this file exists" is a security test, and in 209 // theory filehandle exhaustion or other error could make open/read fail. 210 if (pwd->pw_uid && !access("/etc/nologin", R_OK)) { 211 ss = readfile("/etc/nologin", toybuf, sizeof(toybuf)); 212 puts ((ss && *ss) ? ss : "nologin"); 213 free(ss); 214 toys.exitval = 1; 215 216 return; 217 } 218 219 xsetuser(pwd); 220 221 if (chdir(pwd->pw_dir)) printf("bad $HOME: %s\n", pwd->pw_dir); 222 223 if (!(toys.optflags&FLAG_p)) { 224 char *term = getenv("TERM"); 225 226 clearenv(); 227 if (term) setenv("TERM", term, 1); 228 } 229 230 setenv("USER", pwd->pw_name, 1); 231 setenv("LOGNAME", pwd->pw_name, 1); 232 setenv("HOME", pwd->pw_dir, 1); 233 setenv("SHELL", pwd->pw_shell, 1); 234 if(pipefd[0] >= 0) { 235 setenv("LOGINPASS_FD", xmprintf("%d", pipefd[0]), 1); 236 } 237 238 // Message of the day 239 if ((ss = readfile("/etc/motd", 0, 0))) { 240 puts(ss); 241 free(ss); 242 } 243 244 syslog(LOG_INFO, "%s logged in on %s %s %s", pwd->pw_name, 245 ttyname(tty), hh ? "from" : "", hh ? TT.hostname : ""); 246 247 assert(!use_loginexec); 248 // not using xexec(), login calls absolute path from filesystem so must exec() 249 execl(pwd->pw_shell, xmprintf("-%s", pwd->pw_shell), (char *)0); 250 perror_exit("exec shell '%s'", pwd->pw_shell); 251 }