PageRenderTime 42ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/liboath/usersfile.c

https://gitlab.com/ssbarnea/oath-toolkit
C | 495 lines | 362 code | 66 blank | 67 comment | 161 complexity | c0e5b9199f194f6e30a8afb20beeadf4 MD5 | raw file
  1. /*
  2. * usersfile.c - implementation of UsersFile based HOTP validation
  3. * Copyright (C) 2009-2016 Simon Josefsson
  4. *
  5. * This library is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Lesser General Public License
  7. * as published by the Free Software Foundation; either version 2.1 of
  8. * the License, or (at your option) any later version.
  9. *
  10. * This library is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. * Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public
  16. * License along with this library; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  18. * 02110-1301 USA
  19. *
  20. */
  21. #include <config.h>
  22. #undef GNULIB_POSIXCHECK /* too many complaints for now */
  23. #include "oath.h"
  24. #include <stdio.h> /* For snprintf, getline. */
  25. #include <stdlib.h> /* For free. */
  26. #include <unistd.h> /* For ssize_t. */
  27. #include <fcntl.h> /* For fcntl. */
  28. #include <errno.h> /* For errno. */
  29. #include <sys/stat.h> /* For S_IRUSR, S_IWUSR. */
  30. static int
  31. parse_type (const char *str, unsigned *digits, unsigned *totpstepsize)
  32. {
  33. *totpstepsize = 0;
  34. if (strcmp (str, "HOTP/E/6") == 0
  35. || strcmp (str, "HOTP/E") == 0 || strcmp (str, "HOTP") == 0)
  36. *digits = 6;
  37. else if (strcmp (str, "HOTP/E/7") == 0)
  38. *digits = 7;
  39. else if (strcmp (str, "HOTP/E/8") == 0)
  40. *digits = 8;
  41. else if (strncmp (str, "HOTP/T30", 8) == 0)
  42. {
  43. *totpstepsize = 30;
  44. if (strcmp (str, "HOTP/T30") == 0 || strcmp (str, "HOTP/T30/6") == 0)
  45. *digits = 6;
  46. else if (strcmp (str, "HOTP/T30/7") == 0)
  47. *digits = 7;
  48. else if (strcmp (str, "HOTP/T30/8") == 0)
  49. *digits = 8;
  50. else
  51. return -1;
  52. }
  53. else if (strncmp (str, "HOTP/T60", 8) == 0)
  54. {
  55. *totpstepsize = 60;
  56. if (strcmp (str, "HOTP/T60") == 0 || strcmp (str, "HOTP/T60/6") == 0)
  57. *digits = 6;
  58. else if (strcmp (str, "HOTP/T60/7") == 0)
  59. *digits = 7;
  60. else if (strcmp (str, "HOTP/T60/8") == 0)
  61. *digits = 8;
  62. else
  63. return -1;
  64. }
  65. else
  66. return -1;
  67. return 0;
  68. }
  69. static const char *whitespace = " \t\r\n";
  70. #define TIME_FORMAT_STRING "%Y-%m-%dT%H:%M:%SL"
  71. static int
  72. parse_usersfile (const char *username,
  73. const char *otp,
  74. size_t window,
  75. const char *passwd,
  76. time_t * last_otp,
  77. FILE * infh,
  78. char **lineptr, size_t * n, uint64_t * new_moving_factor,
  79. size_t * skipped_users)
  80. {
  81. int bad_password = 0;
  82. *skipped_users = 0;
  83. while (getline (lineptr, n, infh) != -1)
  84. {
  85. char *saveptr;
  86. char *p = strtok_r (*lineptr, whitespace, &saveptr);
  87. unsigned digits, totpstepsize;
  88. char secret[32];
  89. size_t secret_length = sizeof (secret);
  90. uint64_t start_moving_factor = 0;
  91. int rc = 0;
  92. char *prev_otp = NULL;
  93. if (p == NULL)
  94. continue;
  95. /* Read token type */
  96. if (parse_type (p, &digits, &totpstepsize) != 0)
  97. continue;
  98. /* Read username */
  99. p = strtok_r (NULL, whitespace, &saveptr);
  100. if (p == NULL || strcmp (p, username) != 0)
  101. continue;
  102. /* Read password. */
  103. p = strtok_r (NULL, whitespace, &saveptr);
  104. if (passwd)
  105. {
  106. if (p == NULL)
  107. continue;
  108. if (strcmp (p, "-") == 0)
  109. {
  110. if (*passwd != '\0')
  111. {
  112. bad_password = 1;
  113. rc = OATH_BAD_PASSWORD;
  114. }
  115. }
  116. else if (strcmp (p, "+") == 0)
  117. {
  118. /* Externally verified. */
  119. }
  120. else if (strcmp (p, passwd) != 0)
  121. {
  122. bad_password = 1;
  123. rc = OATH_BAD_PASSWORD;
  124. }
  125. if (rc == OATH_BAD_PASSWORD)
  126. {
  127. (*skipped_users)++;
  128. continue;
  129. }
  130. bad_password = 0;
  131. }
  132. /* Read key. */
  133. p = strtok_r (NULL, whitespace, &saveptr);
  134. if (p == NULL)
  135. continue;
  136. rc = oath_hex2bin (p, secret, &secret_length);
  137. if (rc != OATH_OK)
  138. return rc;
  139. /* Read (optional) moving factor. */
  140. p = strtok_r (NULL, whitespace, &saveptr);
  141. if (p && *p)
  142. {
  143. char *endptr;
  144. unsigned long long int ull = strtoull (p, &endptr, 10);
  145. if (endptr && *endptr != '\0')
  146. return OATH_INVALID_COUNTER;
  147. start_moving_factor = ull;
  148. }
  149. /* Read (optional) last OTP */
  150. prev_otp = strtok_r (NULL, whitespace, &saveptr);
  151. /* Read (optional) last_otp */
  152. p = strtok_r (NULL, whitespace, &saveptr);
  153. if (p)
  154. {
  155. struct tm tm;
  156. char *ts;
  157. ts = strptime (p, TIME_FORMAT_STRING, &tm);
  158. if (ts == NULL || *ts != '\0')
  159. return OATH_INVALID_TIMESTAMP;
  160. tm.tm_isdst = -1;
  161. if (last_otp)
  162. {
  163. *last_otp = mktime (&tm);
  164. if (*last_otp == (time_t) - 1)
  165. return OATH_INVALID_TIMESTAMP;
  166. }
  167. }
  168. if (prev_otp && strcmp (prev_otp, otp) == 0)
  169. return OATH_REPLAYED_OTP;
  170. if (totpstepsize == 0)
  171. rc = oath_hotp_validate (secret, secret_length,
  172. start_moving_factor, window, otp);
  173. else if (prev_otp)
  174. {
  175. int prev_otp_pos, this_otp_pos, tmprc;
  176. rc = oath_totp_validate2 (secret, secret_length,
  177. time (NULL), totpstepsize, 0, window,
  178. &this_otp_pos, otp);
  179. if (rc == OATH_INVALID_OTP)
  180. {
  181. (*skipped_users)++;
  182. continue;
  183. }
  184. if (rc < 0)
  185. return rc;
  186. tmprc = oath_totp_validate2 (secret, secret_length,
  187. time (NULL), totpstepsize, 0, window,
  188. &prev_otp_pos, prev_otp);
  189. if (tmprc >= 0 && prev_otp_pos >= this_otp_pos)
  190. return OATH_REPLAYED_OTP;
  191. }
  192. else
  193. rc = oath_totp_validate (secret, secret_length,
  194. time (NULL), totpstepsize, 0, window, otp);
  195. if (rc == OATH_INVALID_OTP)
  196. {
  197. (*skipped_users)++;
  198. continue;
  199. }
  200. if (rc < 0)
  201. return rc;
  202. *new_moving_factor = start_moving_factor + rc;
  203. return OATH_OK;
  204. }
  205. if (*skipped_users)
  206. {
  207. if (bad_password)
  208. return OATH_BAD_PASSWORD;
  209. else
  210. return OATH_INVALID_OTP;
  211. }
  212. return OATH_UNKNOWN_USER;
  213. }
  214. static int
  215. update_usersfile2 (const char *username,
  216. const char *otp,
  217. FILE * infh,
  218. FILE * outfh,
  219. char **lineptr,
  220. size_t * n, char *timestamp, uint64_t new_moving_factor,
  221. size_t skipped_users)
  222. {
  223. size_t got_users = 0;
  224. while (getline (lineptr, n, infh) != -1)
  225. {
  226. char *saveptr;
  227. char *origline;
  228. const char *user, *type, *passwd, *secret;
  229. int r;
  230. unsigned digits, totpstepsize;
  231. origline = strdup (*lineptr);
  232. type = strtok_r (*lineptr, whitespace, &saveptr);
  233. if (type == NULL)
  234. goto skip_line;
  235. /* Read token type */
  236. if (parse_type (type, &digits, &totpstepsize) != 0)
  237. goto skip_line;
  238. /* Read username */
  239. user = strtok_r (NULL, whitespace, &saveptr);
  240. if (user == NULL || strcmp (user, username) != 0
  241. || got_users++ != skipped_users)
  242. goto skip_line;
  243. passwd = strtok_r (NULL, whitespace, &saveptr);
  244. if (passwd == NULL)
  245. passwd = "-";
  246. secret = strtok_r (NULL, whitespace, &saveptr);
  247. if (secret == NULL)
  248. secret = "-";
  249. r = fprintf (outfh, "%s\t%s\t%s\t%s\t%llu\t%s\t%s\n",
  250. type, username, passwd, secret,
  251. (unsigned long long) new_moving_factor, otp, timestamp);
  252. free (origline);
  253. if (r <= 0)
  254. return OATH_PRINTF_ERROR;
  255. continue;
  256. skip_line:
  257. r = fprintf (outfh, "%s", origline);
  258. free (origline);
  259. if (r <= 0)
  260. return OATH_PRINTF_ERROR;
  261. continue;
  262. }
  263. return OATH_OK;
  264. }
  265. static int
  266. update_usersfile (const char *usersfile,
  267. const char *username,
  268. const char *otp,
  269. FILE * infh,
  270. char **lineptr,
  271. size_t * n, char *timestamp, uint64_t new_moving_factor,
  272. size_t skipped_users)
  273. {
  274. FILE *outfh, *lockfh;
  275. int rc;
  276. char *newfilename, *lockfile;
  277. /* Rewind input file. */
  278. {
  279. int pos;
  280. pos = fseeko (infh, 0L, SEEK_SET);
  281. if (pos == -1)
  282. return OATH_FILE_SEEK_ERROR;
  283. clearerr (infh);
  284. }
  285. /* Open lockfile. */
  286. {
  287. int l;
  288. l = asprintf (&lockfile, "%s.lock", usersfile);
  289. if (lockfile == NULL || ((size_t) l) != strlen (usersfile) + 5)
  290. return OATH_PRINTF_ERROR;
  291. lockfh = fopen (lockfile, "w");
  292. if (!lockfh)
  293. {
  294. free (lockfile);
  295. return OATH_FILE_CREATE_ERROR;
  296. }
  297. }
  298. /* Lock the lockfile. */
  299. {
  300. struct flock l;
  301. memset (&l, 0, sizeof (l));
  302. l.l_whence = SEEK_SET;
  303. l.l_start = 0;
  304. l.l_len = 0;
  305. l.l_type = F_WRLCK;
  306. while ((rc = fcntl (fileno (lockfh), F_SETLKW, &l)) < 0 && errno == EINTR)
  307. continue;
  308. if (rc == -1)
  309. {
  310. fclose (lockfh);
  311. free (lockfile);
  312. return OATH_FILE_LOCK_ERROR;
  313. }
  314. }
  315. /* Open the "new" file. */
  316. {
  317. int l;
  318. l = asprintf (&newfilename, "%s.new", usersfile);
  319. if (newfilename == NULL || ((size_t) l) != strlen (usersfile) + 4)
  320. {
  321. fclose (lockfh);
  322. free (lockfile);
  323. return OATH_PRINTF_ERROR;
  324. }
  325. outfh = fopen (newfilename, "w");
  326. if (!outfh)
  327. {
  328. free (newfilename);
  329. fclose (lockfh);
  330. free (lockfile);
  331. return OATH_FILE_CREATE_ERROR;
  332. }
  333. }
  334. /* Create the new usersfile content. */
  335. rc = update_usersfile2 (username, otp, infh, outfh, lineptr, n,
  336. timestamp, new_moving_factor, skipped_users);
  337. /* On success, flush the buffers. */
  338. if (rc == OATH_OK && fflush (outfh) != 0)
  339. rc = OATH_FILE_FLUSH_ERROR;
  340. /* On success, sync the disks. */
  341. if (rc == OATH_OK && fsync (fileno (outfh)) != 0)
  342. rc = OATH_FILE_SYNC_ERROR;
  343. /* Close the file regardless of success. */
  344. if (fclose (outfh) != 0)
  345. rc = OATH_FILE_CLOSE_ERROR;
  346. /* On success, overwrite the usersfile with the new copy. */
  347. if (rc == OATH_OK && rename (newfilename, usersfile) != 0)
  348. rc = OATH_FILE_RENAME_ERROR;
  349. /* Something has failed, don't leave garbage lying around. */
  350. if (rc != OATH_OK)
  351. unlink (newfilename);
  352. free (newfilename);
  353. /* Complete, close the lockfile */
  354. if (fclose (lockfh) != 0)
  355. rc = OATH_FILE_CLOSE_ERROR;
  356. if (unlink (lockfile) != 0)
  357. rc = OATH_FILE_UNLINK_ERROR;
  358. free (lockfile);
  359. return rc;
  360. }
  361. /**
  362. * oath_authenticate_usersfile:
  363. * @usersfile: string with user credential filename, in UsersFile format
  364. * @username: string with name of user
  365. * @otp: string with one-time password to authenticate
  366. * @window: how many past/future OTPs to search
  367. * @passwd: string with password, or NULL to disable password checking
  368. * @last_otp: output variable holding last successful authentication
  369. *
  370. * Authenticate user named @username with the one-time password @otp
  371. * and (optional) password @passwd. Credentials are read (and
  372. * updated) from a text file named @usersfile.
  373. *
  374. * Note that for TOTP the usersfile will only record the last OTP and
  375. * use that to make sure more recent OTPs have not been seen yet when
  376. * validating a new OTP. That logics relies on using the same search
  377. * window for the same user.
  378. *
  379. * Returns: On successful validation, %OATH_OK is returned. If the
  380. * supplied @otp is the same as the last successfully authenticated
  381. * one-time password, %OATH_REPLAYED_OTP is returned and the
  382. * timestamp of the last authentication is returned in @last_otp.
  383. * If the one-time password is not found in the indicated search
  384. * window, %OATH_INVALID_OTP is returned. Otherwise, an error code
  385. * is returned.
  386. **/
  387. int
  388. oath_authenticate_usersfile (const char *usersfile,
  389. const char *username,
  390. const char *otp,
  391. size_t window,
  392. const char *passwd, time_t * last_otp)
  393. {
  394. FILE *infh;
  395. char *line = NULL;
  396. size_t n = 0;
  397. uint64_t new_moving_factor;
  398. int rc;
  399. size_t skipped_users;
  400. infh = fopen (usersfile, "r");
  401. if (!infh)
  402. return OATH_NO_SUCH_FILE;
  403. rc = parse_usersfile (username, otp, window, passwd, last_otp,
  404. infh, &line, &n, &new_moving_factor, &skipped_users);
  405. if (rc == OATH_OK)
  406. {
  407. char timestamp[30];
  408. size_t max = sizeof (timestamp);
  409. struct tm now;
  410. time_t t;
  411. size_t l;
  412. mode_t old_umask;
  413. if (time (&t) == (time_t) - 1)
  414. return OATH_TIME_ERROR;
  415. if (localtime_r (&t, &now) == NULL)
  416. return OATH_TIME_ERROR;
  417. l = strftime (timestamp, max, TIME_FORMAT_STRING, &now);
  418. if (l != 20)
  419. return OATH_TIME_ERROR;
  420. old_umask = umask (~(S_IRUSR | S_IWUSR));
  421. rc = update_usersfile (usersfile, username, otp, infh,
  422. &line, &n, timestamp, new_moving_factor,
  423. skipped_users);
  424. umask (old_umask);
  425. }
  426. free (line);
  427. fclose (infh);
  428. return rc;
  429. }