diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a16a160 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +CFLAGS += -std=c99 -I. + +login: login.c timingsafe_memcmp.c + $(CC) $(CFLAGS) -o login login.c timingsafe_memcmp.c $(LDFLAGS) $(LIBS) + +.PHONY: clean install + +clean: + rm -f *.o login + +install: + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp login $(DESTDIR)$(PREFIX)/bin/login diff --git a/login.c b/login.c new file mode 100644 index 0000000..66cc6f6 --- /dev/null +++ b/login.c @@ -0,0 +1,202 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __midipix__ +#include + +#define IFLAG 1 +#define WFLAG 1 +#else +#define IFLAG 0 +#define WFLAG 0 +#endif + +#include "login.h" + + +static char* get_win32_username(void) +{ +#ifdef __midipix__ + size_t i; + char usernam[257]; + unsigned int usernam_siz = sizeof(usernam); + if(GetUserNameA(usernam, &usernam_siz) == 0) + return ""; + for(i = 0; i < 257; i++) + usernam[i] = tolower(usernam[i]); + return strdup(usernam); +#else + return ""; +#endif +} + +/* force memset */ +static void __attribute__((optimize("O0"))) memset_noopt(void* mem, int c, size_t memsiz) +{ + memset(mem, c, memsiz); +} + +static bool switch_user_context(struct passwd* pw, const char* username) +{ + /* temporary */ + #ifdef INSECURE + if(initgroups(username, pw->pw_gid) == -1) { + printf("initgroups failed: %s", strerror(errno)); + exit(1); + } + #endif + + #ifdef INSECURE + if((setuid(pw->pw_uid) != -1) && (setgid(pw->pw_gid) != -1)) + return true; + return false; + #else + gid_t oldgid = getegid(); + uid_t olduid = geteuid(); + + if((setegid(pw->pw_gid) != -1) && (setgid(pw->pw_gid) != -1) && (seteuid(pw->pw_uid) != -1) && (setuid(pw->pw_uid) != -1)) { + gid_t newgid = getgid(); + uid_t newuid = getuid(); + #ifdef debug + printf("old gid %d\n", oldgid); + printf("old uid %d\n", olduid); + printf("new gid %d\n", newgid); + printf("new uid %d\n", newuid); + #endif + if((newgid == oldgid) && (newuid == olduid)) + return true; + } + return false; + #endif +} + +int main(int argc, char **argv) +{ + /* we don't want ^D etc to mess up the logic */ + (void)signal(SIGTSTP, SIG_IGN); + (void)signal(SIGQUIT, SIG_IGN); + (void)signal(SIGINT, SIG_IGN); + + struct passwd *pwd; + char* username = NULL; + int c; + bool authenticated = false; + int pflag, fflag, iflag, wflag = 0; + + while((c = getopt(argc, argv, "pfh:iw")) != -1) + switch(c) { + case 'p': + pflag = 1; break; + case 'f': + fflag = 1; break; /* unused, pointless currently */ + case 'h': + if(getuid()) { + exit(1); + } + break; + case 'i': + iflag = IFLAG; break; + case 'w': + wflag = WFLAG; break; + default: + case '?': + usage(); + break; + } + argv += optind; + + if(wflag) + username = get_win32_username(); + else if(*argv) + username = *argv; + else { + /* TODO: hostname, emulate shadow's login(1) prompt */ + printf("login: "); + fflush(0); + (void)scanf("%ms", &username); + } + pwd = getpwnam(username); + + if(!iflag) + { + char* pw = getpass("Password: "); + if(pwd) { + /* if hash == 0 then authenticated = true */ + if(*pwd->pw_passwd == '\0') { + authenticated = true; + } + else if(pw) { + char* pw_encrypted = crypt(pw, pwd->pw_passwd); + if(!timingsafe_memcmp(pw_encrypted, pwd->pw_passwd, strlen(pw_encrypted))) + authenticated = true; + } + else { + puts("not authenticated"); + authenticated = false; + } + } else { + authenticated = false; + } + memset_noopt(pw, 0, strlen(pw)); + } else + authenticated = true; /* iflag */ + + endpwent(); + + if(!authenticated) { + puts("Login incorrect."); + exit(1); + } + + /* authenticated, attempt to set user context and spawn user shell */ + if(switch_user_context(pwd, username)) { + if(*pwd->pw_shell == '\0') + pwd->pw_shell = "/bin/sh"; /* if /bin/sh doesn't exist, I do not care. blow up. */ + + if(chdir(pwd->pw_dir) < 0) { + printf("no home directory %s!\n", pwd->pw_dir); // handle -EPERM, -ENOMEM, -ESYMLNK + if(chdir("/") == -1) { + printf("chdir failed with %s", strerror(errno)); + exit(1); /* no-one can save you now */ + } + pwd->pw_dir = "/"; + } + + if(!pflag) + (void)clearenv(); + + (void)setenv("HOME", pwd->pw_dir, 1); + (void)setenv("SHELL", pwd->pw_shell, 1); + (void)setenv("TERM", "xterm", 0); /* rrrr. needs researching */ + (void)setenv("LOGNAME", pwd->pw_name, 1); + (void)setenv("USER", pwd->pw_name, 1); +#if 0 + (void)setenv("PS1", "$ ", 0); +#endif + (void)setenv("PATH", "/local/sbin:/local/bin:/sbin:/bin", 0); + + execlp(pwd->pw_shell, pwd->pw_shell, (const char*)NULL); + printf("login failed with error: %s", strerror(errno)); + exit(1); + } + puts("could not switch to specified user."); + + +} + +void usage(void) { + puts("login -i (skip authentication, only works on midipix, temporary)"); + puts("login -w (acquire username through Win32)"); + puts("login -p (preserve environment)"); + puts("login -f (no secondary authentication, unused)"); + puts("login -h (pass remote server name to login, unused)"); + exit(0); +} diff --git a/login.h b/login.h new file mode 100644 index 0000000..ad0c720 --- /dev/null +++ b/login.h @@ -0,0 +1,2 @@ +void usage(void); +int timingsafe_memcmp(const void *b1, const void *b2, size_t len); diff --git a/timingsafe_memcmp.c b/timingsafe_memcmp.c new file mode 100644 index 0000000..bb210a3 --- /dev/null +++ b/timingsafe_memcmp.c @@ -0,0 +1,46 @@ +/* $OpenBSD: timingsafe_memcmp.c,v 1.2 2015/08/31 02:53:57 guenther Exp $ */ +/* + * Copyright (c) 2014 Google Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +int +timingsafe_memcmp(const void *b1, const void *b2, size_t len) +{ + const unsigned char *p1 = b1, *p2 = b2; + size_t i; + int res = 0, done = 0; + + for (i = 0; i < len; i++) { + /* lt is -1 if p1[i] < p2[i]; else 0. */ + int lt = (p1[i] - p2[i]) >> CHAR_BIT; + + /* gt is -1 if p1[i] > p2[i]; else 0. */ + int gt = (p2[i] - p1[i]) >> CHAR_BIT; + + /* cmp is 1 if p1[i] > p2[i]; -1 if p1[i] < p2[i]; else 0. */ + int cmp = lt - gt; + + /* set res = cmp if !done. */ + res |= cmp & ~done; + + /* set done if p1[i] != p2[i]. */ + done |= lt | gt; + } + + return (res); +}