logo

skeud

Simple and portable utilities to deal with user accounts (su, login)

login.c (4812B)


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