logo

skeud

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

su.c (4970B)


  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. const 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: error: 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: error: 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: error: 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: error: Option -%c requires an operand\n", optopt);
  66. return 1;
  67. case '?':
  68. fprintf(stderr, "su: error: Unrecognized option: '-%c'\n", optopt);
  69. return 1;
  70. default:
  71. fprintf(stderr, "su: error: 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: error: Got %d arguments, expected <= 1\n", argc);
  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: error: getpwnam");
  99. }
  100. else
  101. {
  102. fprintf(stderr, "su: error: 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: error: No shell entry for user %s\n", username);
  115. return 1;
  116. }
  117. }
  118. fprintf(stderr, "su: info: Authenticating as %s\n", username);
  119. if(getuid() != 0)
  120. {
  121. char *pw_hash = NULL;
  122. if(pwent->pw_passwd)
  123. {
  124. pw_hash = pwent->pw_passwd;
  125. }
  126. #ifdef __linux__
  127. // Always fetched to avoid potentially leaking passwd contents
  128. errno = 0;
  129. struct spwd *swent = getspnam(username);
  130. if(errno != 0)
  131. {
  132. perror("su: warning: getspnam");
  133. }
  134. else
  135. {
  136. if(pw_hash && pw_hash[0] == 'x' && pw_hash[1] == 0)
  137. {
  138. pw_hash = swent->sp_pwdp;
  139. }
  140. explicit_bzero(swent, sizeof(swent));
  141. swent = NULL;
  142. }
  143. #endif /* __linux__ */
  144. char *password = NULL;
  145. ssize_t got = skeud_getpass(&password);
  146. if(got < 0)
  147. {
  148. free(password);
  149. return 1;
  150. }
  151. bool valid_p = skeud_crypt_check(pw_hash, password);
  152. explicit_bzero(password, got);
  153. free(password);
  154. if(pw_hash) explicit_bzero(pw_hash, sizeof(pw_hash));
  155. if(!valid_p)
  156. {
  157. sleep(2);
  158. fprintf(stderr, "su: error: Invalid username or password\n");
  159. return 1;
  160. }
  161. }
  162. if(!opt_p)
  163. {
  164. char *term = getenv("TERM");
  165. environ = envclear;
  166. if(term)
  167. {
  168. setenv("TERM", term, 1);
  169. }
  170. }
  171. if(setgid(pwent->pw_gid) < 0)
  172. {
  173. perror("su: error: setgid");
  174. return 1;
  175. }
  176. if(initgroups(username, pwent->pw_gid) < 0)
  177. {
  178. perror("su: error: initgroups");
  179. return 1;
  180. }
  181. if(setuid(pwent->pw_uid) < 0)
  182. {
  183. perror("su: error: setuid");
  184. return 1;
  185. }
  186. const char *home = pwent->pw_dir ? pwent->pw_dir : "/";
  187. setenv("HOME", home, 1);
  188. if(opt_l)
  189. {
  190. if(chdir(pwent->pw_dir) != 0) perror("su: chdir");
  191. }
  192. explicit_bzero(pwent, sizeof(pwent));
  193. pwent = NULL;
  194. setenv("USER", username, 1);
  195. setenv("LOGNAME", username, 1);
  196. setenv("SHELL", shell, 1);
  197. setenv("IFS", " \t\n", 1);
  198. int cmd_argc = 1;
  199. const char *cmd_argv[2 + 1 + 2] = {shell, NULL};
  200. if(opt_l)
  201. {
  202. cmd_argv[cmd_argc++] = "-l";
  203. }
  204. if(opt_cmd != NULL)
  205. {
  206. cmd_argv[cmd_argc++] = "-c";
  207. cmd_argv[cmd_argc++] = opt_cmd;
  208. }
  209. /* Just to be sure it's null-terminated */
  210. cmd_argv[cmd_argc + 1] = NULL;
  211. errno = 0;
  212. /* flawfinder: ignore CWE-78 */
  213. int ret = execvp(shell, (char **)cmd_argv);
  214. if(ret < 0)
  215. {
  216. if(errno == ENOENT)
  217. {
  218. perror("su: execve");
  219. return 127;
  220. }
  221. else
  222. {
  223. perror("su: execve");
  224. return 126;
  225. }
  226. }
  227. }