logo

skeud

Simple and portable utilities to deal with user accounts (su, login)git clone https://anongit.hacktivis.me/git/skeud.git/

login.c (5222B)


  1. // SPDX-FileCopyrightText: 2022 Haelwenn (lanodan) Monnier <contact+skeud@hacktivis.me>
  2. // SPDX-License-Identifier: MPL-2.0
  3. #define _POSIX_C_SOURCE 200809L
  4. // for explicit_bzero, initgroups
  5. #define _DEFAULT_SOURCE
  6. #ifdef __linux__
  7. // I love linux extensions (no)
  8. #include <shadow.h> /* getspnam */
  9. #endif
  10. #include "common.h" // skeud_getpass, skeud_crypt_check
  11. #include <errno.h> // errno
  12. #include <grp.h> // getgrnam, initgroups
  13. #include <pwd.h> // getpwnam
  14. #include <stdbool.h> // bool
  15. #include <stdio.h> // fprintf, perror
  16. #include <stdlib.h> // abort, setenv
  17. #include <string.h> // strcmp, explicit_bzero
  18. #include <sys/stat.h> // fchmod
  19. #include <unistd.h> // getuid, getopt, opt*, chdir, setuid, setgid, fchown
  20. #define TTY_GROUP "tty"
  21. #define TTY_PERMS 0600
  22. extern char **environ;
  23. char *envclear[] = {NULL};
  24. int
  25. main(int argc, char *argv[])
  26. {
  27. bool opt_f = false;
  28. bool opt_p = false;
  29. int c = EOF;
  30. char *username = NULL;
  31. struct passwd *pwent = NULL;
  32. const char *shell = "/bin/sh";
  33. if(getuid() != 0)
  34. {
  35. fprintf(stderr, "login: error: Not super-user\n");
  36. return 1;
  37. }
  38. /* flawfinder: ignore CWE-120, CWE-20 */
  39. while((c = getopt(argc, argv, ":f:p")) != EOF)
  40. {
  41. switch(c)
  42. {
  43. case 'f': // user already authenticated
  44. opt_f = true;
  45. username = optarg;
  46. break;
  47. case 'p': // preserve environment
  48. opt_p = true;
  49. break;
  50. case ':':
  51. fprintf(stderr, "login: error: Option -%c requires an operand\n", optopt);
  52. return 1;
  53. case '?':
  54. fprintf(stderr, "login: error: Unrecognized option: '-%c'\n", optopt);
  55. return 1;
  56. default:
  57. fprintf(stderr, "login: error: Unknown getopt state, aborting\n");
  58. abort();
  59. }
  60. }
  61. argc -= optind;
  62. argv += optind;
  63. if(!opt_f)
  64. {
  65. if(argc == 1)
  66. {
  67. username = argv[0];
  68. }
  69. else if(argc > 1)
  70. {
  71. fprintf(stderr, "login: error: Got %d arguments, expected <= 1\n", argc);
  72. return 1;
  73. }
  74. }
  75. else
  76. {
  77. if(argc > 0)
  78. {
  79. fprintf(stderr, "login: error: Got %d arguments, expected <= 0\n", argc);
  80. return 1;
  81. }
  82. }
  83. char *username_buf = NULL;
  84. if(username == NULL)
  85. {
  86. size_t len = 0;
  87. const char *prompt = "username: ";
  88. write(STDERR_FILENO, prompt, strlen(prompt));
  89. ssize_t got = getline(&username_buf, &len, stdin);
  90. if(got < 0)
  91. {
  92. if(errno != 0) perror("login: error: getline");
  93. free(username_buf);
  94. return 1;
  95. }
  96. username_buf[got] = 0;
  97. username_buf[got - 1] = 0;
  98. username = username_buf;
  99. }
  100. if(username == NULL) return 1;
  101. errno = 0;
  102. pwent = getpwnam(username);
  103. if(errno != 0)
  104. {
  105. perror("login: warning: getpwnam");
  106. }
  107. if(!opt_f)
  108. {
  109. char *pw_hash = NULL;
  110. if(pwent && pwent->pw_passwd)
  111. {
  112. pw_hash = pwent->pw_passwd;
  113. }
  114. #ifdef __linux__
  115. // Always fetched to avoid potentially leaking passwd contents
  116. errno = 0;
  117. struct spwd *swent = getspnam(username);
  118. if(errno != 0)
  119. {
  120. perror("login: getspnam");
  121. }
  122. else
  123. {
  124. if(pw_hash && strcmp(pw_hash, "x") == 0)
  125. {
  126. pw_hash = swent->sp_pwdp;
  127. }
  128. explicit_bzero(swent, sizeof(swent));
  129. swent = NULL;
  130. }
  131. #endif /* __linux__ */
  132. char *password = NULL;
  133. ssize_t got = skeud_getpass(&password);
  134. if(got < 0)
  135. {
  136. free(username_buf);
  137. free(password);
  138. return 1;
  139. }
  140. bool valid_p = skeud_crypt_check(pw_hash, password);
  141. explicit_bzero(password, got);
  142. free(password);
  143. if(pw_hash) explicit_bzero(pw_hash, sizeof(pw_hash));
  144. if(!valid_p)
  145. {
  146. free(username_buf);
  147. sleep(2);
  148. fprintf(stderr, "login: error: Invalid username or password\n");
  149. return 1;
  150. }
  151. }
  152. if(!opt_p)
  153. {
  154. char *term = getenv("TERM");
  155. environ = envclear;
  156. if(term)
  157. {
  158. setenv("TERM", term, 1);
  159. }
  160. }
  161. if(pwent != NULL)
  162. {
  163. int tty_gid = pwent->pw_gid;
  164. struct group *tty_group = getgrnam(TTY_GROUP);
  165. if(tty_group == NULL)
  166. {
  167. perror("login: warning: getgrnam");
  168. }
  169. else
  170. {
  171. tty_gid = tty_group->gr_gid;
  172. }
  173. /* considers that STDIN_FILENO is close enough to the current tty */
  174. if(fchown(STDIN_FILENO, pwent->pw_uid, tty_gid) < 0)
  175. {
  176. perror("login: error: fchown");
  177. return 1;
  178. }
  179. if(fchmod(STDIN_FILENO, TTY_PERMS))
  180. {
  181. perror("login: error: fchmod");
  182. return 1;
  183. }
  184. if(setgid(pwent->pw_gid) < 0)
  185. {
  186. perror("login: error: setgid");
  187. return 1;
  188. }
  189. if(initgroups(pwent->pw_name, pwent->pw_gid) < 0)
  190. {
  191. perror("login: error: initgroups");
  192. return 1;
  193. }
  194. if(setuid(pwent->pw_uid) < 0)
  195. {
  196. perror("login: error: setuid");
  197. return 1;
  198. }
  199. if(pwent->pw_shell != NULL)
  200. {
  201. shell = pwent->pw_shell;
  202. }
  203. if(pwent->pw_dir != NULL)
  204. {
  205. setenv("HOME", pwent->pw_dir, 1);
  206. if(chdir(pwent->pw_dir) != 0)
  207. {
  208. fprintf(
  209. stderr, "login: warning: Failed to change current directory to: %s\n", pwent->pw_dir);
  210. }
  211. }
  212. explicit_bzero(pwent, sizeof(pwent));
  213. pwent = NULL;
  214. }
  215. setenv("USER", username, 1);
  216. setenv("LOGNAME", username, 1);
  217. setenv("SHELL", shell, 1);
  218. setenv("IFS", " \t\n", 1);
  219. free(username_buf);
  220. errno = 0;
  221. /* flawfinder: ignore CWE-78 */
  222. if(execl(shell, shell, "-l", NULL) < 0)
  223. {
  224. if(errno == ENOENT)
  225. {
  226. perror("login: error: execve");
  227. return 127;
  228. }
  229. else
  230. {
  231. perror("login: error: execve");
  232. return 126;
  233. }
  234. }
  235. }