#include #include #include #include #include #include #include static char* echoing_fgets(char* buf, size_t size, FILE* stream) { char* s = buf; memset(buf, 0, size); size_t oldsize = size; while (size) { int c = fgetc(stream); if (c == EOF) { if (ferror(stream)) return NULL; if (feof(stream)) { if (s != buf) return s; else return NULL; }; } if ((char)c == '\b') { if (size != oldsize) { buf--; size++; putchar('\b'); } } else { size--; *buf = (char)c; buf++; putchar((char)c); if ((char)c == '\n') return s; } *buf = 0; } return s; } static void strip_newline(char* str) { size_t len = strlen(str); if (str[len - 1] == '\n') str[len - 1] = 0; } static char* collect_password() { static char buf[BUFSIZ]; printf("Password: "); fgets(buf, BUFSIZ, stdin); strip_newline(buf); putchar('\n'); char* copy = strdup( buf); // The password only stays in a caller-controlled heap-allocated buffer, where it can be freed at will. memset(buf, 0, BUFSIZ); return copy; } static void login_as(struct passwd* user) { pid_t child = fork(); if (child < 0) { perror("fork"); return; } if (child == 0) { setuid(user->pw_uid); setgid(user->pw_gid); char* argv[] = {user->pw_shell, NULL}; execv(argv[0], argv); perror("execv"); exit(EXIT_FAILURE); } wait(NULL); } static int login() { printf("Username: "); char username[BUFSIZ]; echoing_fgets(username, BUFSIZ, stdin); strip_newline(username); if (strcmp("exit", username) == 0) return 1; struct passwd* user = getpwnam(username); if (!user) { if (errno) perror("getpwnam"); else printf("Unknown user %s\n", username); return 0; } char* password = collect_password(); putchar('\n'); if (strcmp(user->pw_passwd, password) == 0) { free(password); login_as(user); puts("logout\n"); } else { free(password); puts("Invalid password.\n"); } return 0; } int main(int argc, char** argv) { (void)argc; if (getuid() != 0) { fprintf(stderr, "%s must be run as root.\nYou are probably looking for the 'su' command, which lets you switch users " "once logged in.\n", argv[0]); return EXIT_FAILURE; } for (;;) { if (login()) break; } endpwent(); return EXIT_SUCCESS; }