pam_exec.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. /*
  2. * Copyright (c) 2006, 2008 Thorsten Kukuk <kukuk@thkukuk.de>
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions
  6. * are met:
  7. * 1. Redistributions of source code must retain the above copyright
  8. * notice, and the entire permission notice in its entirety,
  9. * including the disclaimer of warranties.
  10. * 2. Redistributions in binary form must reproduce the above copyright
  11. * notice, this list of conditions and the following disclaimer in the
  12. * documentation and/or other materials provided with the distribution.
  13. * 3. The name of the author may not be used to endorse or promote
  14. * products derived from this software without specific prior
  15. * written permission.
  16. *
  17. * ALTERNATIVELY, this product may be distributed under the terms of
  18. * the GNU Public License, in which case the provisions of the GPL are
  19. * required INSTEAD OF the above restrictions. (This clause is
  20. * necessary due to a potential bad interaction between the GPL and
  21. * the restrictions contained in a BSD-style copyright.)
  22. *
  23. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
  24. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  25. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  26. * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
  27. * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  28. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  29. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  30. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  31. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  32. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  33. * OF THE POSSIBILITY OF SUCH DAMAGE.
  34. */
  35. #if defined(HAVE_CONFIG_H)
  36. #include "config.h"
  37. #endif
  38. #include <time.h>
  39. #include <errno.h>
  40. #include <fcntl.h>
  41. #include <stdio.h>
  42. #include <string.h>
  43. #include <syslog.h>
  44. #include <unistd.h>
  45. #include <stdlib.h>
  46. #include <sys/wait.h>
  47. #include <sys/stat.h>
  48. #include <sys/types.h>
  49. #include <security/pam_modules.h>
  50. #include <security/pam_modutil.h>
  51. #include <security/pam_ext.h>
  52. #include <security/_pam_macros.h>
  53. #include "pam_inline.h"
  54. #define ENV_ITEM(n) { (n), #n }
  55. static struct {
  56. int item;
  57. const char *name;
  58. } env_items[] = {
  59. ENV_ITEM(PAM_SERVICE),
  60. ENV_ITEM(PAM_USER),
  61. ENV_ITEM(PAM_TTY),
  62. ENV_ITEM(PAM_RHOST),
  63. ENV_ITEM(PAM_RUSER),
  64. };
  65. /* move_fd_to_non_stdio copies the given file descriptor to something other
  66. * than stdin, stdout, or stderr. Assumes that the caller will close all
  67. * unwanted fds after calling. */
  68. static int
  69. move_fd_to_non_stdio (pam_handle_t *pamh, int fd)
  70. {
  71. while (fd < 3)
  72. {
  73. fd = dup(fd);
  74. if (fd == -1)
  75. {
  76. int err = errno;
  77. pam_syslog (pamh, LOG_ERR, "dup failed: %m");
  78. _exit (err);
  79. }
  80. }
  81. return fd;
  82. }
  83. static int
  84. call_exec (const char *pam_type, pam_handle_t *pamh,
  85. int argc, const char **argv)
  86. {
  87. int debug = 0;
  88. int call_setuid = 0;
  89. int quiet = 0;
  90. int quiet_log = 0;
  91. int expose_authtok = 0;
  92. int use_stdout = 0;
  93. int optargc;
  94. const char *logfile = NULL;
  95. char authtok[PAM_MAX_RESP_SIZE] = {};
  96. pid_t pid;
  97. int fds[2];
  98. int stdout_fds[2];
  99. FILE *stdout_file = NULL;
  100. int retval;
  101. const char *name;
  102. if (argc < 1) {
  103. pam_syslog (pamh, LOG_ERR,
  104. "This module needs at least one argument");
  105. return PAM_SERVICE_ERR;
  106. }
  107. for (optargc = 0; optargc < argc; optargc++)
  108. {
  109. const char *str;
  110. if (argv[optargc][0] == '/') /* paths starts with / */
  111. break;
  112. if (strcasecmp (argv[optargc], "debug") == 0)
  113. debug = 1;
  114. else if (strcasecmp (argv[optargc], "stdout") == 0)
  115. use_stdout = 1;
  116. else if ((str = pam_str_skip_icase_prefix (argv[optargc], "log=")) != NULL)
  117. logfile = str;
  118. else if ((str = pam_str_skip_icase_prefix (argv[optargc], "type=")) != NULL)
  119. {
  120. if (strcmp (pam_type, str) != 0)
  121. return PAM_IGNORE;
  122. }
  123. else if (strcasecmp (argv[optargc], "seteuid") == 0)
  124. call_setuid = 1;
  125. else if (strcasecmp (argv[optargc], "quiet") == 0)
  126. quiet = 1;
  127. else if (strcasecmp (argv[optargc], "quiet_log") == 0)
  128. quiet_log = 1;
  129. else if (strcasecmp (argv[optargc], "expose_authtok") == 0)
  130. expose_authtok = 1;
  131. else
  132. break; /* Unknown option, assume program to execute. */
  133. }
  134. /* Request user name to be available. */
  135. retval = pam_get_user(pamh, &name, NULL);
  136. if (retval != PAM_SUCCESS)
  137. {
  138. if (retval == PAM_CONV_AGAIN)
  139. retval = PAM_INCOMPLETE;
  140. return retval;
  141. }
  142. if (expose_authtok == 1)
  143. {
  144. if (strcmp (pam_type, "auth") != 0)
  145. {
  146. pam_syslog (pamh, LOG_ERR,
  147. "expose_authtok not supported for type %s", pam_type);
  148. expose_authtok = 0;
  149. }
  150. else
  151. {
  152. const void *void_pass;
  153. retval = pam_get_item (pamh, PAM_AUTHTOK, &void_pass);
  154. if (retval != PAM_SUCCESS)
  155. {
  156. if (debug)
  157. pam_syslog (pamh, LOG_DEBUG,
  158. "pam_get_item (PAM_AUTHTOK) failed, return %d",
  159. retval);
  160. return retval;
  161. }
  162. else if (void_pass == NULL)
  163. {
  164. char *resp = NULL;
  165. retval = pam_prompt (pamh, PAM_PROMPT_ECHO_OFF,
  166. &resp, _("Password: "));
  167. if (retval != PAM_SUCCESS)
  168. {
  169. _pam_drop (resp);
  170. if (retval == PAM_CONV_AGAIN)
  171. retval = PAM_INCOMPLETE;
  172. return retval;
  173. }
  174. if (resp)
  175. {
  176. pam_set_item (pamh, PAM_AUTHTOK, resp);
  177. strncpy (authtok, resp, sizeof(authtok) - 1);
  178. _pam_drop (resp);
  179. }
  180. }
  181. else
  182. strncpy (authtok, void_pass, sizeof(authtok) - 1);
  183. if (pipe(fds) != 0)
  184. {
  185. pam_syslog (pamh, LOG_ERR, "Could not create pipe: %m");
  186. return PAM_SYSTEM_ERR;
  187. }
  188. }
  189. }
  190. if (use_stdout)
  191. {
  192. if (pipe(stdout_fds) != 0)
  193. {
  194. pam_syslog (pamh, LOG_ERR, "Could not create pipe: %m");
  195. return PAM_SYSTEM_ERR;
  196. }
  197. stdout_file = fdopen(stdout_fds[0], "r");
  198. if (!stdout_file)
  199. {
  200. pam_syslog (pamh, LOG_ERR, "Could not fdopen pipe: %m");
  201. return PAM_SYSTEM_ERR;
  202. }
  203. }
  204. if (optargc >= argc) {
  205. pam_syslog (pamh, LOG_ERR, "No path given as argument");
  206. return PAM_SERVICE_ERR;
  207. }
  208. pid = fork();
  209. if (pid == -1)
  210. return PAM_SYSTEM_ERR;
  211. if (pid > 0) /* parent */
  212. {
  213. int status = 0;
  214. pid_t rc;
  215. if (expose_authtok) /* send the password to the child */
  216. {
  217. if (debug)
  218. pam_syslog (pamh, LOG_DEBUG, "send password to child");
  219. if (write(fds[1], authtok, strlen(authtok)) == -1)
  220. pam_syslog (pamh, LOG_ERR,
  221. "sending password to child failed: %m");
  222. close(fds[0]); /* close here to avoid possible SIGPIPE above */
  223. close(fds[1]);
  224. }
  225. if (use_stdout)
  226. {
  227. char buf[4096];
  228. close(stdout_fds[1]);
  229. while (fgets(buf, sizeof(buf), stdout_file) != NULL)
  230. {
  231. size_t len;
  232. len = strlen(buf);
  233. if (buf[len-1] == '\n')
  234. buf[len-1] = '\0';
  235. pam_info(pamh, "%s", buf);
  236. }
  237. fclose(stdout_file);
  238. }
  239. while ((rc = waitpid (pid, &status, 0)) == -1 &&
  240. errno == EINTR);
  241. if (rc == (pid_t)-1)
  242. {
  243. pam_syslog (pamh, LOG_ERR, "waitpid returns with -1: %m");
  244. return PAM_SYSTEM_ERR;
  245. }
  246. else if (status != 0)
  247. {
  248. if (WIFEXITED(status))
  249. {
  250. if (!quiet_log)
  251. pam_syslog (pamh, LOG_ERR, "%s failed: exit code %d",
  252. argv[optargc], WEXITSTATUS(status));
  253. if (!quiet)
  254. pam_error (pamh, _("%s failed: exit code %d"),
  255. argv[optargc], WEXITSTATUS(status));
  256. }
  257. else if (WIFSIGNALED(status))
  258. {
  259. if (!quiet_log)
  260. pam_syslog (pamh, LOG_ERR, "%s failed: caught signal %d%s",
  261. argv[optargc], WTERMSIG(status),
  262. WCOREDUMP(status) ? " (core dumped)" : "");
  263. if (!quiet)
  264. pam_error (pamh, _("%s failed: caught signal %d%s"),
  265. argv[optargc], WTERMSIG(status),
  266. WCOREDUMP(status) ? " (core dumped)" : "");
  267. }
  268. else
  269. {
  270. if (!quiet_log)
  271. pam_syslog (pamh, LOG_ERR, "%s failed: unknown status 0x%x",
  272. argv[optargc], status);
  273. if (!quiet)
  274. pam_error (pamh, _("%s failed: unknown status 0x%x"),
  275. argv[optargc], status);
  276. }
  277. return PAM_SYSTEM_ERR;
  278. }
  279. return PAM_SUCCESS;
  280. }
  281. else /* child */
  282. {
  283. char **arggv;
  284. int i;
  285. char **envlist, **tmp;
  286. int envlen, nitems;
  287. char *envstr;
  288. enum pam_modutil_redirect_fd redirect_stdin =
  289. expose_authtok ? PAM_MODUTIL_IGNORE_FD : PAM_MODUTIL_PIPE_FD;
  290. enum pam_modutil_redirect_fd redirect_stdout =
  291. (use_stdout || logfile) ? PAM_MODUTIL_IGNORE_FD : PAM_MODUTIL_NULL_FD;
  292. /* First, move all the pipes off of stdin, stdout, and stderr, to ensure
  293. * that calls to dup2 won't close them. */
  294. if (expose_authtok)
  295. {
  296. fds[0] = move_fd_to_non_stdio(pamh, fds[0]);
  297. close(fds[1]);
  298. }
  299. if (use_stdout)
  300. {
  301. stdout_fds[1] = move_fd_to_non_stdio(pamh, stdout_fds[1]);
  302. close(stdout_fds[0]);
  303. }
  304. /* Set up stdin. */
  305. if (expose_authtok)
  306. {
  307. /* reopen stdin as pipe */
  308. if (dup2(fds[0], STDIN_FILENO) == -1)
  309. {
  310. int err = errno;
  311. pam_syslog (pamh, LOG_ERR, "dup2 of STDIN failed: %m");
  312. _exit (err);
  313. }
  314. }
  315. /* Set up stdout. */
  316. if (use_stdout)
  317. {
  318. if (dup2(stdout_fds[1], STDOUT_FILENO) == -1)
  319. {
  320. int err = errno;
  321. pam_syslog (pamh, LOG_ERR, "dup2 to stdout failed: %m");
  322. _exit (err);
  323. }
  324. }
  325. else if (logfile)
  326. {
  327. time_t tm = time (NULL);
  328. char *buffer = NULL;
  329. close (STDOUT_FILENO);
  330. if ((i = open (logfile, O_CREAT|O_APPEND|O_WRONLY,
  331. S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1)
  332. {
  333. int err = errno;
  334. pam_syslog (pamh, LOG_ERR, "open of %s failed: %m",
  335. logfile);
  336. _exit (err);
  337. }
  338. if (i != STDOUT_FILENO)
  339. {
  340. if (dup2 (i, STDOUT_FILENO) == -1)
  341. {
  342. int err = errno;
  343. pam_syslog (pamh, LOG_ERR, "dup2 failed: %m");
  344. _exit (err);
  345. }
  346. close (i);
  347. }
  348. if (asprintf (&buffer, "*** %s", ctime (&tm)) > 0)
  349. {
  350. pam_modutil_write (STDOUT_FILENO, buffer, strlen (buffer));
  351. free (buffer);
  352. }
  353. }
  354. if ((use_stdout || logfile) &&
  355. dup2 (STDOUT_FILENO, STDERR_FILENO) == -1)
  356. {
  357. int err = errno;
  358. pam_syslog (pamh, LOG_ERR, "dup2 failed: %m");
  359. _exit (err);
  360. }
  361. if (pam_modutil_sanitize_helper_fds(pamh, redirect_stdin,
  362. redirect_stdout, redirect_stdout) < 0)
  363. _exit(1);
  364. if (call_setuid)
  365. if (setuid (geteuid ()) == -1)
  366. {
  367. int err = errno;
  368. pam_syslog (pamh, LOG_ERR, "setuid(%lu) failed: %m",
  369. (unsigned long) geteuid ());
  370. _exit (err);
  371. }
  372. if (setsid () == -1)
  373. {
  374. int err = errno;
  375. pam_syslog (pamh, LOG_ERR, "setsid failed: %m");
  376. _exit (err);
  377. }
  378. arggv = calloc (argc + 4, sizeof (char *));
  379. if (arggv == NULL)
  380. _exit (ENOMEM);
  381. for (i = 0; i < (argc - optargc); i++)
  382. arggv[i] = strdup(argv[i+optargc]);
  383. arggv[i] = NULL;
  384. /*
  385. * Set up the child's environment list. It consists of the PAM
  386. * environment, plus a few hand-picked PAM items.
  387. */
  388. envlist = pam_getenvlist(pamh);
  389. for (envlen = 0; envlist[envlen] != NULL; ++envlen)
  390. /* nothing */ ;
  391. nitems = PAM_ARRAY_SIZE(env_items);
  392. /* + 2 because of PAM_TYPE and NULL entry */
  393. tmp = realloc(envlist, (envlen + nitems + 2) * sizeof(*envlist));
  394. if (tmp == NULL)
  395. {
  396. free(envlist);
  397. pam_syslog (pamh, LOG_CRIT, "realloc environment failed: %m");
  398. _exit (ENOMEM);
  399. }
  400. envlist = tmp;
  401. for (i = 0; i < nitems; ++i)
  402. {
  403. const void *item;
  404. if (pam_get_item(pamh, env_items[i].item, &item) != PAM_SUCCESS || item == NULL)
  405. continue;
  406. if (asprintf(&envstr, "%s=%s", env_items[i].name, (const char *)item) < 0)
  407. {
  408. free(envlist);
  409. pam_syslog (pamh, LOG_CRIT, "prepare environment failed: %m");
  410. _exit (ENOMEM);
  411. }
  412. envlist[envlen++] = envstr;
  413. envlist[envlen] = NULL;
  414. }
  415. if (asprintf(&envstr, "PAM_TYPE=%s", pam_type) < 0)
  416. {
  417. free(envlist);
  418. pam_syslog (pamh, LOG_CRIT, "prepare environment failed: %m");
  419. _exit (ENOMEM);
  420. }
  421. envlist[envlen++] = envstr;
  422. envlist[envlen] = NULL;
  423. if (debug)
  424. pam_syslog (pamh, LOG_DEBUG, "Calling %s ...", arggv[0]);
  425. execve (arggv[0], arggv, envlist);
  426. i = errno;
  427. pam_syslog (pamh, LOG_ERR, "execve(%s,...) failed: %m", arggv[0]);
  428. free(envlist);
  429. _exit (i);
  430. }
  431. return PAM_SYSTEM_ERR; /* will never be reached. */
  432. }
  433. int
  434. pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED,
  435. int argc, const char **argv)
  436. {
  437. return call_exec ("auth", pamh, argc, argv);
  438. }
  439. int
  440. pam_sm_setcred (pam_handle_t *pamh UNUSED, int flags UNUSED,
  441. int argc UNUSED, const char **argv UNUSED)
  442. {
  443. return PAM_IGNORE;
  444. }
  445. /* password updating functions */
  446. int
  447. pam_sm_chauthtok(pam_handle_t *pamh, int flags,
  448. int argc, const char **argv)
  449. {
  450. if (flags & PAM_PRELIM_CHECK)
  451. return PAM_SUCCESS;
  452. return call_exec ("password", pamh, argc, argv);
  453. }
  454. int
  455. pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
  456. int argc, const char **argv)
  457. {
  458. return call_exec ("account", pamh, argc, argv);
  459. }
  460. int
  461. pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED,
  462. int argc, const char **argv)
  463. {
  464. return call_exec ("open_session", pamh, argc, argv);
  465. }
  466. int
  467. pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED,
  468. int argc, const char **argv)
  469. {
  470. return call_exec ("close_session", pamh, argc, argv);
  471. }