pam_pwhistory.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /*
  2. * pam_pwhistory module
  3. *
  4. * Copyright (c) 2008, 2012 Thorsten Kukuk
  5. * Author: Thorsten Kukuk <kukuk@thkukuk.de>
  6. * Copyright (c) 2013 Red Hat, Inc.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, and the entire permission notice in its entirety,
  13. * including the disclaimer of warranties.
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in the
  16. * documentation and/or other materials provided with the distribution.
  17. * 3. The name of the author may not be used to endorse or promote
  18. * products derived from this software without specific prior
  19. * written permission.
  20. *
  21. * ALTERNATIVELY, this product may be distributed under the terms of
  22. * the GNU Public License, in which case the provisions of the GPL are
  23. * required INSTEAD OF the above restrictions. (This clause is
  24. * necessary due to a potential bad interaction between the GPL and
  25. * the restrictions contained in a BSD-style copyright.)
  26. *
  27. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
  28. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  29. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  30. * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
  31. * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  32. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  33. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  34. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  35. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  36. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  37. * OF THE POSSIBILITY OF SUCH DAMAGE.
  38. */
  39. #if defined(HAVE_CONFIG_H)
  40. #include <config.h>
  41. #endif
  42. #include <pwd.h>
  43. #include <errno.h>
  44. #include <stdio.h>
  45. #include <stdlib.h>
  46. #include <string.h>
  47. #include <unistd.h>
  48. #include <syslog.h>
  49. #include <sys/types.h>
  50. #include <sys/stat.h>
  51. #include <sys/time.h>
  52. #include <sys/resource.h>
  53. #include <sys/wait.h>
  54. #include <signal.h>
  55. #include <fcntl.h>
  56. #include <security/pam_modules.h>
  57. #include <security/pam_modutil.h>
  58. #include <security/pam_ext.h>
  59. #include <security/_pam_macros.h>
  60. #include "opasswd.h"
  61. #include "pam_inline.h"
  62. struct options_t {
  63. int debug;
  64. int enforce_for_root;
  65. int remember;
  66. int tries;
  67. };
  68. typedef struct options_t options_t;
  69. static void
  70. parse_option (pam_handle_t *pamh, const char *argv, options_t *options)
  71. {
  72. const char *str;
  73. if (strcasecmp (argv, "try_first_pass") == 0)
  74. /* ignore */;
  75. else if (strcasecmp (argv, "use_first_pass") == 0)
  76. /* ignore */;
  77. else if (strcasecmp (argv, "use_authtok") == 0)
  78. /* ignore, handled by pam_get_authtok */;
  79. else if (strcasecmp (argv, "debug") == 0)
  80. options->debug = 1;
  81. else if ((str = pam_str_skip_icase_prefix(argv, "remember=")) != NULL)
  82. {
  83. options->remember = strtol(str, NULL, 10);
  84. if (options->remember < 0)
  85. options->remember = 0;
  86. if (options->remember > 400)
  87. options->remember = 400;
  88. }
  89. else if ((str = pam_str_skip_icase_prefix(argv, "retry=")) != NULL)
  90. {
  91. options->tries = strtol(str, NULL, 10);
  92. if (options->tries < 0)
  93. options->tries = 1;
  94. }
  95. else if (strcasecmp (argv, "enforce_for_root") == 0)
  96. options->enforce_for_root = 1;
  97. else if (pam_str_skip_icase_prefix(argv, "authtok_type=") != NULL)
  98. { /* ignore, for pam_get_authtok */; }
  99. else
  100. pam_syslog (pamh, LOG_ERR, "pam_pwhistory: unknown option: %s", argv);
  101. }
  102. static int
  103. run_save_helper(pam_handle_t *pamh, const char *user,
  104. int howmany, int debug)
  105. {
  106. int retval, child;
  107. struct sigaction newsa, oldsa;
  108. memset(&newsa, '\0', sizeof(newsa));
  109. newsa.sa_handler = SIG_DFL;
  110. sigaction(SIGCHLD, &newsa, &oldsa);
  111. child = fork();
  112. if (child == 0)
  113. {
  114. static char *envp[] = { NULL };
  115. char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL };
  116. if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_PIPE_FD,
  117. PAM_MODUTIL_PIPE_FD,
  118. PAM_MODUTIL_PIPE_FD) < 0)
  119. {
  120. _exit(PAM_SYSTEM_ERR);
  121. }
  122. /* exec binary helper */
  123. DIAG_PUSH_IGNORE_CAST_QUAL;
  124. args[0] = (char *)PWHISTORY_HELPER;
  125. args[1] = (char *)"save";
  126. args[2] = (char *)user;
  127. DIAG_POP_IGNORE_CAST_QUAL;
  128. if (asprintf(&args[3], "%d", howmany) < 0 ||
  129. asprintf(&args[4], "%d", debug) < 0)
  130. {
  131. pam_syslog(pamh, LOG_ERR, "asprintf: %m");
  132. _exit(PAM_SYSTEM_ERR);
  133. }
  134. execve(args[0], args, envp);
  135. pam_syslog(pamh, LOG_ERR, "helper binary execve failed: %s: %m", args[0]);
  136. _exit(PAM_SYSTEM_ERR);
  137. }
  138. else if (child > 0)
  139. {
  140. /* wait for child */
  141. int rc = 0;
  142. while ((rc = waitpid (child, &retval, 0)) == -1 &&
  143. errno == EINTR);
  144. if (rc < 0)
  145. {
  146. pam_syslog(pamh, LOG_ERR, "pwhistory_helper save: waitpid: %m");
  147. retval = PAM_SYSTEM_ERR;
  148. }
  149. else if (!WIFEXITED(retval))
  150. {
  151. pam_syslog(pamh, LOG_ERR, "pwhistory_helper save abnormal exit: %d", retval);
  152. retval = PAM_SYSTEM_ERR;
  153. }
  154. else
  155. {
  156. retval = WEXITSTATUS(retval);
  157. }
  158. }
  159. else
  160. {
  161. pam_syslog(pamh, LOG_ERR, "fork failed: %m");
  162. retval = PAM_SYSTEM_ERR;
  163. }
  164. sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */
  165. return retval;
  166. }
  167. static int
  168. run_check_helper(pam_handle_t *pamh, const char *user,
  169. const char *newpass, int debug)
  170. {
  171. int retval, child, fds[2];
  172. struct sigaction newsa, oldsa;
  173. /* create a pipe for the password */
  174. if (pipe(fds) != 0)
  175. return PAM_SYSTEM_ERR;
  176. memset(&newsa, '\0', sizeof(newsa));
  177. newsa.sa_handler = SIG_DFL;
  178. sigaction(SIGCHLD, &newsa, &oldsa);
  179. child = fork();
  180. if (child == 0)
  181. {
  182. static char *envp[] = { NULL };
  183. char *args[] = { NULL, NULL, NULL, NULL, NULL };
  184. /* reopen stdin as pipe */
  185. if (dup2(fds[0], STDIN_FILENO) != STDIN_FILENO)
  186. {
  187. pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", "stdin");
  188. _exit(PAM_SYSTEM_ERR);
  189. }
  190. if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_IGNORE_FD,
  191. PAM_MODUTIL_PIPE_FD,
  192. PAM_MODUTIL_PIPE_FD) < 0)
  193. {
  194. _exit(PAM_SYSTEM_ERR);
  195. }
  196. /* exec binary helper */
  197. DIAG_PUSH_IGNORE_CAST_QUAL;
  198. args[0] = (char *)PWHISTORY_HELPER;
  199. args[1] = (char *)"check";
  200. args[2] = (char *)user;
  201. DIAG_POP_IGNORE_CAST_QUAL;
  202. if (asprintf(&args[3], "%d", debug) < 0)
  203. {
  204. pam_syslog(pamh, LOG_ERR, "asprintf: %m");
  205. _exit(PAM_SYSTEM_ERR);
  206. }
  207. execve(args[0], args, envp);
  208. pam_syslog(pamh, LOG_ERR, "helper binary execve failed: %s: %m", args[0]);
  209. _exit(PAM_SYSTEM_ERR);
  210. }
  211. else if (child > 0)
  212. {
  213. /* wait for child */
  214. int rc = 0;
  215. if (newpass == NULL)
  216. newpass = "";
  217. /* send the password to the child */
  218. if (write(fds[1], newpass, strlen(newpass)+1) == -1)
  219. {
  220. pam_syslog(pamh, LOG_ERR, "Cannot send password to helper: %m");
  221. retval = PAM_SYSTEM_ERR;
  222. }
  223. newpass = NULL;
  224. close(fds[0]); /* close here to avoid possible SIGPIPE above */
  225. close(fds[1]);
  226. while ((rc = waitpid (child, &retval, 0)) == -1 &&
  227. errno == EINTR);
  228. if (rc < 0)
  229. {
  230. pam_syslog(pamh, LOG_ERR, "pwhistory_helper check: waitpid: %m");
  231. retval = PAM_SYSTEM_ERR;
  232. }
  233. else if (!WIFEXITED(retval))
  234. {
  235. pam_syslog(pamh, LOG_ERR, "pwhistory_helper check abnormal exit: %d", retval);
  236. retval = PAM_SYSTEM_ERR;
  237. }
  238. else
  239. {
  240. retval = WEXITSTATUS(retval);
  241. }
  242. }
  243. else
  244. {
  245. pam_syslog(pamh, LOG_ERR, "fork failed: %m");
  246. close(fds[0]);
  247. close(fds[1]);
  248. retval = PAM_SYSTEM_ERR;
  249. }
  250. sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */
  251. return retval;
  252. }
  253. /* This module saves the current hashed password in /etc/security/opasswd
  254. and then compares the new password with all entries in this file. */
  255. int
  256. pam_sm_chauthtok (pam_handle_t *pamh, int flags, int argc, const char **argv)
  257. {
  258. const char *newpass;
  259. const char *user;
  260. int retval, tries;
  261. options_t options;
  262. memset (&options, 0, sizeof (options));
  263. /* Set some default values, which could be overwritten later. */
  264. options.remember = 10;
  265. options.tries = 1;
  266. /* Parse parameters for module */
  267. for ( ; argc-- > 0; argv++)
  268. parse_option (pamh, *argv, &options);
  269. if (options.debug)
  270. pam_syslog (pamh, LOG_DEBUG, "pam_sm_chauthtok entered");
  271. if (options.remember == 0)
  272. return PAM_IGNORE;
  273. retval = pam_get_user (pamh, &user, NULL);
  274. if (retval != PAM_SUCCESS)
  275. return retval;
  276. if (flags & PAM_PRELIM_CHECK)
  277. {
  278. if (options.debug)
  279. pam_syslog (pamh, LOG_DEBUG,
  280. "pam_sm_chauthtok(PAM_PRELIM_CHECK)");
  281. return PAM_SUCCESS;
  282. }
  283. retval = save_old_pass (pamh, user, options.remember, options.debug);
  284. if (retval == PAM_PWHISTORY_RUN_HELPER)
  285. retval = run_save_helper(pamh, user, options.remember, options.debug);
  286. if (retval != PAM_SUCCESS)
  287. return retval;
  288. newpass = NULL;
  289. tries = 0;
  290. while ((newpass == NULL) && (tries < options.tries))
  291. {
  292. retval = pam_get_authtok (pamh, PAM_AUTHTOK, &newpass, NULL);
  293. if (retval != PAM_SUCCESS && retval != PAM_TRY_AGAIN)
  294. {
  295. if (retval == PAM_CONV_AGAIN)
  296. retval = PAM_INCOMPLETE;
  297. return retval;
  298. }
  299. tries++;
  300. if (options.debug)
  301. {
  302. if (newpass)
  303. pam_syslog (pamh, LOG_DEBUG, "got new auth token");
  304. else
  305. pam_syslog (pamh, LOG_DEBUG, "got no auth token");
  306. }
  307. if (newpass == NULL || retval == PAM_TRY_AGAIN)
  308. continue;
  309. if (options.debug)
  310. pam_syslog (pamh, LOG_DEBUG, "check against old password file");
  311. retval = check_old_pass (pamh, user, newpass, options.debug);
  312. if (retval == PAM_PWHISTORY_RUN_HELPER)
  313. retval = run_check_helper(pamh, user, newpass, options.debug);
  314. if (retval != PAM_SUCCESS)
  315. {
  316. if (getuid() || options.enforce_for_root ||
  317. (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
  318. {
  319. pam_error (pamh,
  320. _("Password has been already used. Choose another."));
  321. newpass = NULL;
  322. /* Remove password item, else following module will use it */
  323. pam_set_item (pamh, PAM_AUTHTOK, (void *) NULL);
  324. }
  325. else
  326. pam_info (pamh,
  327. _("Password has been already used."));
  328. }
  329. }
  330. if (newpass == NULL && tries >= options.tries)
  331. {
  332. if (options.debug)
  333. pam_syslog (pamh, LOG_DEBUG, "Aborted, too many tries");
  334. return PAM_MAXTRIES;
  335. }
  336. return PAM_SUCCESS;
  337. }