logo

skeud

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

su.c (4985B)


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