logo

skeud

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

su.c (4768B)


  1. // SPDX-FileCopyrightText: 2022-2023 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> // 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 <unistd.h> // getuid, getopt, opt*, chdir, setuid, setgid
  20. extern char **environ;
  21. char *envclear[] = {NULL};
  22. int
  23. main(int argc, char *argv[])
  24. {
  25. bool opt_l = false;
  26. bool opt_p = false;
  27. int c = EOF;
  28. char *username = "root";
  29. struct passwd *pwent = NULL;
  30. char *shell = NULL;
  31. char *opt_cmd = NULL;
  32. if(geteuid() != 0)
  33. {
  34. fprintf(stderr, "su: Not effectively super-user. Missing setuid?\n");
  35. return 1;
  36. }
  37. /* flawfinder: ignore CWE-120, CWE-20 */
  38. while((c = getopt(argc, argv, ":c:ls:p")) != EOF)
  39. {
  40. switch(c)
  41. {
  42. case 'c': // opt_cmd
  43. opt_cmd = optarg;
  44. break;
  45. case 'l': // login-mode
  46. opt_l = true;
  47. break;
  48. case 's': // shell
  49. if(getuid() != 0)
  50. {
  51. fprintf(stderr, "su: Only the super-user can override the target shell\n");
  52. return 1;
  53. }
  54. shell = optarg;
  55. break;
  56. case 'p': // preserve environment
  57. if(getuid() != 0)
  58. {
  59. fprintf(stderr, "su: Only the super-user can preserve the environment\n");
  60. return 1;
  61. }
  62. opt_p = true;
  63. break;
  64. case ':':
  65. fprintf(stderr, "su: Option -%c requires an operand\n", optopt);
  66. return 1;
  67. case '?':
  68. fprintf(stderr, "su: Unrecognized option: '-%c'\n", optopt);
  69. return 1;
  70. default:
  71. fprintf(stderr, "su: Unknown getopt state, aborting\n");
  72. abort();
  73. }
  74. }
  75. argc -= optind;
  76. argv += optind;
  77. if(argv[0] && strcmp(argv[0], "-") == 0)
  78. {
  79. opt_l = true;
  80. argc -= 1;
  81. argv += 1;
  82. }
  83. if(argc > 1)
  84. {
  85. fprintf(stderr, "su: Too many arguments given.\n");
  86. return 1;
  87. }
  88. if(argc == 1)
  89. {
  90. username = argv[0];
  91. }
  92. errno = 0;
  93. pwent = getpwnam(username);
  94. if(pwent == NULL)
  95. {
  96. if(errno != 0)
  97. {
  98. perror("su: getpwnam");
  99. }
  100. else
  101. {
  102. fprintf(stderr, "su: getpwnam: No entry found for user %s\n", username);
  103. }
  104. return 1;
  105. }
  106. if(shell == NULL)
  107. {
  108. if(pwent->pw_shell)
  109. {
  110. shell = pwent->pw_shell;
  111. }
  112. else
  113. {
  114. fprintf(stderr, "su: No shell entry for user %s\n", username);
  115. }
  116. }
  117. fprintf(stderr, "su: Authenticating as %s\n", username);
  118. if(getuid() != 0)
  119. {
  120. char *pw_hash = NULL;
  121. if(pwent->pw_passwd)
  122. {
  123. pw_hash = pwent->pw_passwd;
  124. }
  125. #ifdef __linux__
  126. // Always fetched to avoid potentially leaking passwd contents
  127. errno = 0;
  128. struct spwd *swent = getspnam(username);
  129. if(errno != 0)
  130. {
  131. perror("su: getspnam");
  132. }
  133. else
  134. {
  135. if(pw_hash && pw_hash[0] == 'x' && pw_hash[1] == 0)
  136. {
  137. pw_hash = swent->sp_pwdp;
  138. }
  139. explicit_bzero(swent, sizeof(swent));
  140. swent = NULL;
  141. }
  142. #endif /* __linux__ */
  143. char *password = NULL;
  144. ssize_t got = skeud_getpass(&password);
  145. if(got < 0)
  146. {
  147. free(password);
  148. return 1;
  149. }
  150. bool valid_p = skeud_crypt_check(pw_hash, password);
  151. explicit_bzero(password, got);
  152. free(password);
  153. if(pw_hash) explicit_bzero(pw_hash, sizeof(pw_hash));
  154. if(!valid_p)
  155. {
  156. sleep(2);
  157. fprintf(stderr, "su: Invalid username or password\n");
  158. return 1;
  159. }
  160. }
  161. if(!opt_p)
  162. {
  163. char *term = getenv("TERM");
  164. environ = envclear;
  165. if(term)
  166. {
  167. setenv("TERM", term, 1);
  168. }
  169. }
  170. if(setgid(pwent->pw_gid) < 0)
  171. {
  172. perror("su: setgid");
  173. }
  174. if(initgroups(username, pwent->pw_gid) < 0)
  175. {
  176. perror("su: initgroups");
  177. }
  178. if(setuid(pwent->pw_uid) < 0)
  179. {
  180. perror("su: setuid");
  181. }
  182. char *home = pwent->pw_dir ? pwent->pw_dir : "/";
  183. setenv("HOME", home, 1);
  184. if(opt_l)
  185. {
  186. if(chdir(pwent->pw_dir) != 0) perror("su: chdir");
  187. }
  188. explicit_bzero(pwent, sizeof(pwent));
  189. pwent = NULL;
  190. setenv("USER", username, 1);
  191. setenv("LOGNAME", username, 1);
  192. setenv("SHELL", shell, 1);
  193. setenv("IFS", " \t\n", 1);
  194. int cmd_argc = 1;
  195. char *cmd_argv[2 + 1 + 2] = {shell, NULL};
  196. if(opt_l)
  197. {
  198. cmd_argv[cmd_argc++] = "-l";
  199. }
  200. if(opt_cmd != NULL)
  201. {
  202. cmd_argv[cmd_argc++] = "-c";
  203. cmd_argv[cmd_argc++] = opt_cmd;
  204. }
  205. /* Just to be sure it's null-terminated */
  206. cmd_argv[cmd_argc + 1] = NULL;
  207. errno = 0;
  208. /* flawfinder: ignore CWE-78 */
  209. int ret = execvp(shell, cmd_argv);
  210. if(ret < 0)
  211. {
  212. if(errno == ENOENT)
  213. {
  214. perror("su: execve");
  215. return 127;
  216. }
  217. else
  218. {
  219. perror("su: execve");
  220. return 126;
  221. }
  222. }
  223. }