opasswd.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. /*
  2. * Copyright (c) 2008 Thorsten Kukuk <kukuk@suse.de>
  3. * Copyright (c) 2013 Red Hat, Inc.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions
  7. * are met:
  8. * 1. Redistributions of source code must retain the above copyright
  9. * notice, and the entire permission notice in its entirety,
  10. * including the disclaimer of warranties.
  11. * 2. Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * 3. The name of the author may not be used to endorse or promote
  15. * products derived from this software without specific prior
  16. * written permission.
  17. *
  18. * ALTERNATIVELY, this product may be distributed under the terms of
  19. * the GNU Public License, in which case the provisions of the GPL are
  20. * required INSTEAD OF the above restrictions. (This clause is
  21. * necessary due to a potential bad interaction between the GPL and
  22. * the restrictions contained in a BSD-style copyright.)
  23. *
  24. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
  25. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  26. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  27. * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
  28. * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  29. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  30. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  31. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  32. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  33. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  34. * OF THE POSSIBILITY OF SUCH DAMAGE.
  35. */
  36. #if defined(HAVE_CONFIG_H)
  37. #include <config.h>
  38. #endif
  39. #include <pwd.h>
  40. #include <shadow.h>
  41. #include <time.h>
  42. #include <ctype.h>
  43. #include <errno.h>
  44. #include <fcntl.h>
  45. #include <stdio.h>
  46. #include <unistd.h>
  47. #include <string.h>
  48. #include <stdlib.h>
  49. #include <syslog.h>
  50. #ifdef HELPER_COMPILE
  51. #include <stdarg.h>
  52. #endif
  53. #include <sys/stat.h>
  54. #ifdef HAVE_CRYPT_H
  55. #include <crypt.h>
  56. #endif
  57. #ifdef HELPER_COMPILE
  58. #define pam_modutil_getpwnam(h,n) getpwnam(n)
  59. #define pam_modutil_getspnam(h,n) getspnam(n)
  60. #define pam_syslog(h,a,...) helper_log_err(a,__VA_ARGS__)
  61. #else
  62. #include <security/pam_modutil.h>
  63. #include <security/pam_ext.h>
  64. #endif
  65. #include <security/pam_modules.h>
  66. #include "opasswd.h"
  67. #ifndef RANDOM_DEVICE
  68. #define RANDOM_DEVICE "/dev/urandom"
  69. #endif
  70. #define OLD_PASSWORDS_FILE "/etc/security/opasswd"
  71. #define TMP_PASSWORDS_FILE OLD_PASSWORDS_FILE".tmpXXXXXX"
  72. #define DEFAULT_BUFLEN 4096
  73. typedef struct {
  74. char *user;
  75. char *uid;
  76. int count;
  77. char *old_passwords;
  78. } opwd;
  79. #ifdef HELPER_COMPILE
  80. PAM_FORMAT((printf, 2, 3))
  81. void
  82. helper_log_err(int err, const char *format, ...)
  83. {
  84. va_list args;
  85. va_start(args, format);
  86. openlog(HELPER_COMPILE, LOG_CONS | LOG_PID, LOG_AUTHPRIV);
  87. vsyslog(err, format, args);
  88. va_end(args);
  89. closelog();
  90. }
  91. #endif
  92. static int
  93. parse_entry (char *line, opwd *data)
  94. {
  95. const char delimiters[] = ":";
  96. char *endptr;
  97. char *count;
  98. data->user = strsep (&line, delimiters);
  99. data->uid = strsep (&line, delimiters);
  100. count = strsep (&line, delimiters);
  101. if (count == NULL)
  102. return 1;
  103. data->count = strtol (count, &endptr, 10);
  104. if (endptr != NULL && *endptr != '\0')
  105. return 1;
  106. data->old_passwords = strsep (&line, delimiters);
  107. return 0;
  108. }
  109. static int
  110. compare_password(const char *newpass, const char *oldpass)
  111. {
  112. char *outval;
  113. #ifdef HAVE_CRYPT_R
  114. struct crypt_data output;
  115. output.initialized = 0;
  116. outval = crypt_r (newpass, oldpass, &output);
  117. #else
  118. outval = crypt (newpass, oldpass);
  119. #endif
  120. return outval != NULL && strcmp(outval, oldpass) == 0;
  121. }
  122. /* Check, if the new password is already in the opasswd file. */
  123. PAMH_ARG_DECL(int
  124. check_old_pass, const char *user, const char *newpass, int debug)
  125. {
  126. int retval = PAM_SUCCESS;
  127. FILE *oldpf;
  128. char *buf = NULL;
  129. size_t buflen = 0;
  130. opwd entry;
  131. int found = 0;
  132. #ifndef HELPER_COMPILE
  133. if (SELINUX_ENABLED)
  134. return PAM_PWHISTORY_RUN_HELPER;
  135. #endif
  136. if ((oldpf = fopen (OLD_PASSWORDS_FILE, "r")) == NULL)
  137. {
  138. if (errno != ENOENT)
  139. pam_syslog (pamh, LOG_ERR, "Cannot open %s: %m", OLD_PASSWORDS_FILE);
  140. return PAM_SUCCESS;
  141. }
  142. while (!feof (oldpf))
  143. {
  144. char *cp, *tmp;
  145. #if defined(HAVE_GETLINE)
  146. ssize_t n = getline (&buf, &buflen, oldpf);
  147. #elif defined (HAVE_GETDELIM)
  148. ssize_t n = getdelim (&buf, &buflen, '\n', oldpf);
  149. #else
  150. ssize_t n;
  151. if (buf == NULL)
  152. {
  153. buflen = DEFAULT_BUFLEN;
  154. buf = malloc (buflen);
  155. if (buf == NULL)
  156. return PAM_BUF_ERR;
  157. }
  158. buf[0] = '\0';
  159. fgets (buf, buflen - 1, oldpf);
  160. n = strlen (buf);
  161. #endif /* HAVE_GETLINE / HAVE_GETDELIM */
  162. cp = buf;
  163. if (n < 1)
  164. break;
  165. tmp = strchr (cp, '#'); /* remove comments */
  166. if (tmp)
  167. *tmp = '\0';
  168. while (isspace ((int)*cp)) /* remove spaces and tabs */
  169. ++cp;
  170. if (*cp == '\0') /* ignore empty lines */
  171. continue;
  172. if (cp[strlen (cp) - 1] == '\n')
  173. cp[strlen (cp) - 1] = '\0';
  174. if (strncmp (cp, user, strlen (user)) == 0 &&
  175. cp[strlen (user)] == ':')
  176. {
  177. /* We found the line we needed */
  178. if (parse_entry (cp, &entry) == 0)
  179. {
  180. found = 1;
  181. break;
  182. }
  183. }
  184. }
  185. fclose (oldpf);
  186. if (found && entry.old_passwords)
  187. {
  188. const char delimiters[] = ",";
  189. char *running;
  190. char *oldpass;
  191. running = entry.old_passwords;
  192. do {
  193. oldpass = strsep (&running, delimiters);
  194. if (oldpass && strlen (oldpass) > 0 &&
  195. compare_password(newpass, oldpass) )
  196. {
  197. if (debug)
  198. pam_syslog (pamh, LOG_DEBUG, "New password already used");
  199. retval = PAM_AUTHTOK_ERR;
  200. break;
  201. }
  202. } while (oldpass != NULL);
  203. }
  204. if (buf)
  205. free (buf);
  206. return retval;
  207. }
  208. PAMH_ARG_DECL(int
  209. save_old_pass, const char *user, int howmany, int debug UNUSED)
  210. {
  211. char opasswd_tmp[] = TMP_PASSWORDS_FILE;
  212. struct stat opasswd_stat;
  213. FILE *oldpf, *newpf;
  214. int newpf_fd;
  215. int do_create = 0;
  216. int retval = PAM_SUCCESS;
  217. char *buf = NULL;
  218. size_t buflen = 0;
  219. int found = 0;
  220. struct passwd *pwd;
  221. const char *oldpass;
  222. pwd = pam_modutil_getpwnam (pamh, user);
  223. if (pwd == NULL)
  224. return PAM_USER_UNKNOWN;
  225. if (howmany <= 0)
  226. return PAM_SUCCESS;
  227. #ifndef HELPER_COMPILE
  228. if (SELINUX_ENABLED)
  229. return PAM_PWHISTORY_RUN_HELPER;
  230. #endif
  231. if ((strcmp(pwd->pw_passwd, "x") == 0) ||
  232. ((pwd->pw_passwd[0] == '#') &&
  233. (pwd->pw_passwd[1] == '#') &&
  234. (strcmp(pwd->pw_name, pwd->pw_passwd + 2) == 0)))
  235. {
  236. struct spwd *spw = pam_modutil_getspnam (pamh, user);
  237. if (spw == NULL)
  238. return PAM_USER_UNKNOWN;
  239. oldpass = spw->sp_pwdp;
  240. }
  241. else
  242. oldpass = pwd->pw_passwd;
  243. if (oldpass == NULL || *oldpass == '\0')
  244. return PAM_SUCCESS;
  245. if ((oldpf = fopen (OLD_PASSWORDS_FILE, "r")) == NULL)
  246. {
  247. if (errno == ENOENT)
  248. {
  249. pam_syslog (pamh, LOG_NOTICE, "Creating %s",
  250. OLD_PASSWORDS_FILE);
  251. do_create = 1;
  252. }
  253. else
  254. {
  255. pam_syslog (pamh, LOG_ERR, "Cannot open %s: %m",
  256. OLD_PASSWORDS_FILE);
  257. return PAM_AUTHTOK_ERR;
  258. }
  259. }
  260. else if (fstat (fileno (oldpf), &opasswd_stat) < 0)
  261. {
  262. pam_syslog (pamh, LOG_ERR, "Cannot stat %s: %m", OLD_PASSWORDS_FILE);
  263. fclose (oldpf);
  264. return PAM_AUTHTOK_ERR;
  265. }
  266. /* Open a temp passwd file */
  267. newpf_fd = mkstemp (opasswd_tmp);
  268. if (newpf_fd == -1)
  269. {
  270. pam_syslog (pamh, LOG_ERR, "Cannot create %s temp file: %m",
  271. OLD_PASSWORDS_FILE);
  272. if (oldpf)
  273. fclose (oldpf);
  274. return PAM_AUTHTOK_ERR;
  275. }
  276. if (do_create)
  277. {
  278. if (fchmod (newpf_fd, S_IRUSR|S_IWUSR) != 0)
  279. pam_syslog (pamh, LOG_ERR,
  280. "Cannot set permissions of %s temp file: %m",
  281. OLD_PASSWORDS_FILE);
  282. if (fchown (newpf_fd, 0, 0) != 0)
  283. pam_syslog (pamh, LOG_ERR,
  284. "Cannot set owner/group of %s temp file: %m",
  285. OLD_PASSWORDS_FILE);
  286. }
  287. else
  288. {
  289. if (fchmod (newpf_fd, opasswd_stat.st_mode) != 0)
  290. pam_syslog (pamh, LOG_ERR,
  291. "Cannot set permissions of %s temp file: %m",
  292. OLD_PASSWORDS_FILE);
  293. if (fchown (newpf_fd, opasswd_stat.st_uid, opasswd_stat.st_gid) != 0)
  294. pam_syslog (pamh, LOG_ERR,
  295. "Cannot set owner/group of %s temp file: %m",
  296. OLD_PASSWORDS_FILE);
  297. }
  298. newpf = fdopen (newpf_fd, "w+");
  299. if (newpf == NULL)
  300. {
  301. pam_syslog (pamh, LOG_ERR, "Cannot fdopen %s: %m", opasswd_tmp);
  302. if (oldpf)
  303. fclose (oldpf);
  304. close (newpf_fd);
  305. retval = PAM_AUTHTOK_ERR;
  306. goto error_opasswd;
  307. }
  308. if (!do_create)
  309. while (!feof (oldpf))
  310. {
  311. char *cp, *tmp, *save;
  312. #if defined(HAVE_GETLINE)
  313. ssize_t n = getline (&buf, &buflen, oldpf);
  314. #elif defined (HAVE_GETDELIM)
  315. ssize_t n = getdelim (&buf, &buflen, '\n', oldpf);
  316. #else
  317. ssize_t n;
  318. if (buf == NULL)
  319. {
  320. buflen = DEFAULT_BUFLEN;
  321. buf = malloc (buflen);
  322. if (buf == NULL)
  323. {
  324. fclose (oldpf);
  325. fclose (newpf);
  326. retval = PAM_BUF_ERR;
  327. goto error_opasswd;
  328. }
  329. }
  330. buf[0] = '\0';
  331. fgets (buf, buflen - 1, oldpf);
  332. n = strlen (buf);
  333. #endif /* HAVE_GETLINE / HAVE_GETDELIM */
  334. if (n < 1)
  335. break;
  336. cp = buf;
  337. save = strdup (buf); /* Copy to write the original data back. */
  338. if (save == NULL)
  339. {
  340. fclose (oldpf);
  341. fclose (newpf);
  342. retval = PAM_BUF_ERR;
  343. goto error_opasswd;
  344. }
  345. tmp = strchr (cp, '#'); /* remove comments */
  346. if (tmp)
  347. *tmp = '\0';
  348. while (isspace ((int)*cp)) /* remove spaces and tabs */
  349. ++cp;
  350. if (*cp == '\0') /* ignore empty lines */
  351. goto write_old_data;
  352. if (cp[strlen (cp) - 1] == '\n')
  353. cp[strlen (cp) - 1] = '\0';
  354. if (strncmp (cp, user, strlen (user)) == 0 &&
  355. cp[strlen (user)] == ':')
  356. {
  357. /* We found the line we needed */
  358. opwd entry;
  359. if (parse_entry (cp, &entry) == 0)
  360. {
  361. char *out = NULL;
  362. found = 1;
  363. /* Don't save the current password twice */
  364. if (entry.old_passwords && entry.old_passwords[0] != '\0')
  365. {
  366. char *last = entry.old_passwords;
  367. cp = entry.old_passwords;
  368. entry.count = 1; /* Don't believe the count */
  369. while ((cp = strchr (cp, ',')) != NULL)
  370. {
  371. entry.count++;
  372. last = ++cp;
  373. }
  374. /* compare the last password */
  375. if (strcmp (last, oldpass) == 0)
  376. goto write_old_data;
  377. }
  378. else
  379. entry.count = 0;
  380. /* increase count. */
  381. entry.count++;
  382. /* check that we don't remember to many passwords. */
  383. while (entry.count > howmany && entry.count > 1)
  384. {
  385. char *p = strpbrk (entry.old_passwords, ",");
  386. if (p != NULL)
  387. entry.old_passwords = ++p;
  388. entry.count--;
  389. }
  390. if (entry.count == 1)
  391. {
  392. if (asprintf (&out, "%s:%s:%d:%s\n",
  393. entry.user, entry.uid, entry.count,
  394. oldpass) < 0)
  395. {
  396. free (save);
  397. retval = PAM_AUTHTOK_ERR;
  398. fclose (oldpf);
  399. fclose (newpf);
  400. goto error_opasswd;
  401. }
  402. }
  403. else
  404. {
  405. if (asprintf (&out, "%s:%s:%d:%s,%s\n",
  406. entry.user, entry.uid, entry.count,
  407. entry.old_passwords, oldpass) < 0)
  408. {
  409. free (save);
  410. retval = PAM_AUTHTOK_ERR;
  411. fclose (oldpf);
  412. fclose (newpf);
  413. goto error_opasswd;
  414. }
  415. }
  416. if (fputs (out, newpf) < 0)
  417. {
  418. free (out);
  419. free (save);
  420. retval = PAM_AUTHTOK_ERR;
  421. fclose (oldpf);
  422. fclose (newpf);
  423. goto error_opasswd;
  424. }
  425. free (out);
  426. }
  427. }
  428. else
  429. {
  430. write_old_data:
  431. if (fputs (save, newpf) < 0)
  432. {
  433. free (save);
  434. retval = PAM_AUTHTOK_ERR;
  435. fclose (oldpf);
  436. fclose (newpf);
  437. goto error_opasswd;
  438. }
  439. }
  440. free (save);
  441. }
  442. if (!found)
  443. {
  444. char *out;
  445. if (asprintf (&out, "%s:%d:1:%s\n", user, pwd->pw_uid, oldpass) < 0)
  446. {
  447. retval = PAM_AUTHTOK_ERR;
  448. if (oldpf)
  449. fclose (oldpf);
  450. fclose (newpf);
  451. goto error_opasswd;
  452. }
  453. if (fputs (out, newpf) < 0)
  454. {
  455. free (out);
  456. retval = PAM_AUTHTOK_ERR;
  457. if (oldpf)
  458. fclose (oldpf);
  459. fclose (newpf);
  460. goto error_opasswd;
  461. }
  462. free (out);
  463. }
  464. if (oldpf)
  465. if (fclose (oldpf) != 0)
  466. {
  467. pam_syslog (pamh, LOG_ERR, "Error while closing old opasswd file: %m");
  468. retval = PAM_AUTHTOK_ERR;
  469. fclose (newpf);
  470. goto error_opasswd;
  471. }
  472. if (fflush (newpf) != 0 || fsync (fileno (newpf)) != 0)
  473. {
  474. pam_syslog (pamh, LOG_ERR,
  475. "Error while syncing temporary opasswd file: %m");
  476. retval = PAM_AUTHTOK_ERR;
  477. fclose (newpf);
  478. goto error_opasswd;
  479. }
  480. if (fclose (newpf) != 0)
  481. {
  482. pam_syslog (pamh, LOG_ERR,
  483. "Error while closing temporary opasswd file: %m");
  484. retval = PAM_AUTHTOK_ERR;
  485. goto error_opasswd;
  486. }
  487. unlink (OLD_PASSWORDS_FILE".old");
  488. if (link (OLD_PASSWORDS_FILE, OLD_PASSWORDS_FILE".old") != 0 &&
  489. errno != ENOENT)
  490. pam_syslog (pamh, LOG_ERR, "Cannot create backup file of %s: %m",
  491. OLD_PASSWORDS_FILE);
  492. rename (opasswd_tmp, OLD_PASSWORDS_FILE);
  493. error_opasswd:
  494. unlink (opasswd_tmp);
  495. free (buf);
  496. return retval;
  497. }