logo

skeud

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

login.c (5797B)


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