pam_time.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. /*
  2. * pam_time module
  3. *
  4. * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/6/22
  5. * (File syntax and much other inspiration from the shadow package
  6. * shadow-960129)
  7. * Field parsing rewritten by Tomas Mraz <tm@t8m.info>
  8. */
  9. #include "config.h"
  10. #include <sys/file.h>
  11. #include <stdio.h>
  12. #include <stdlib.h>
  13. #include <ctype.h>
  14. #include <unistd.h>
  15. #include <stdarg.h>
  16. #include <time.h>
  17. #include <syslog.h>
  18. #include <string.h>
  19. #include <sys/types.h>
  20. #include <sys/stat.h>
  21. #include <fcntl.h>
  22. #include <netdb.h>
  23. #include <security/_pam_macros.h>
  24. #include <security/pam_modules.h>
  25. #include <security/pam_ext.h>
  26. #include <security/pam_modutil.h>
  27. #include "pam_inline.h"
  28. #ifdef HAVE_LIBAUDIT
  29. #include <libaudit.h>
  30. #endif
  31. #define PAM_TIME_BUFLEN 1000
  32. #define FIELD_SEPARATOR ';' /* this is new as of .02 */
  33. #define PAM_DEBUG_ARG 0x0001
  34. #define PAM_NO_AUDIT 0x0002
  35. #ifndef TRUE
  36. # define TRUE 1
  37. #endif
  38. #ifndef FALSE
  39. # define FALSE 0
  40. #endif
  41. typedef enum { AND, OR } operator;
  42. static int
  43. _pam_parse (const pam_handle_t *pamh, int argc, const char **argv, const char **conffile)
  44. {
  45. int ctrl = 0;
  46. *conffile = PAM_TIME_CONF;
  47. /* step through arguments */
  48. for (; argc-- > 0; ++argv) {
  49. const char *str;
  50. /* generic options */
  51. if (!strcmp(*argv, "debug")) {
  52. ctrl |= PAM_DEBUG_ARG;
  53. } else if (!strcmp(*argv, "noaudit")) {
  54. ctrl |= PAM_NO_AUDIT;
  55. } else if ((str = pam_str_skip_prefix(*argv, "conffile=")) != NULL) {
  56. if (str[0] == '\0') {
  57. pam_syslog(pamh, LOG_ERR,
  58. "conffile= specification missing argument - ignored");
  59. } else {
  60. *conffile = str;
  61. D(("new Configuration File: %s", *conffile));
  62. }
  63. } else {
  64. pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
  65. }
  66. }
  67. return ctrl;
  68. }
  69. /* --- static functions for checking whether the user should be let in --- */
  70. static char *
  71. shift_buf(char *mem, int from)
  72. {
  73. char *start = mem;
  74. while ((*mem = mem[from]) != '\0')
  75. ++mem;
  76. memset(mem, '\0', PAM_TIME_BUFLEN - (mem - start));
  77. return mem;
  78. }
  79. static void
  80. trim_spaces(char *buf, char *from)
  81. {
  82. while (from > buf) {
  83. --from;
  84. if (*from == ' ')
  85. *from = '\0';
  86. else
  87. break;
  88. }
  89. }
  90. #define STATE_NL 0 /* new line starting */
  91. #define STATE_COMMENT 1 /* inside comment */
  92. #define STATE_FIELD 2 /* field following */
  93. #define STATE_EOF 3 /* end of file or error */
  94. static int
  95. read_field(const pam_handle_t *pamh, int fd, char **buf, int *from, int *state, const char *file)
  96. {
  97. char *to;
  98. char *src;
  99. int i;
  100. char c;
  101. int onspace;
  102. /* is buf set ? */
  103. if (! *buf) {
  104. *buf = (char *) calloc(1, PAM_TIME_BUFLEN+1);
  105. if (! *buf) {
  106. pam_syslog(pamh, LOG_CRIT, "out of memory");
  107. D(("no memory"));
  108. *state = STATE_EOF;
  109. return -1;
  110. }
  111. *from = 0;
  112. *state = STATE_NL;
  113. fd = open(file, O_RDONLY);
  114. if (fd < 0) {
  115. pam_syslog(pamh, LOG_ERR, "error opening %s: %m", file);
  116. _pam_drop(*buf);
  117. *state = STATE_EOF;
  118. return -1;
  119. }
  120. }
  121. if (*from > 0)
  122. to = shift_buf(*buf, *from);
  123. else
  124. to = *buf;
  125. while (fd != -1 && to - *buf < PAM_TIME_BUFLEN) {
  126. i = pam_modutil_read(fd, to, PAM_TIME_BUFLEN - (to - *buf));
  127. if (i < 0) {
  128. pam_syslog(pamh, LOG_ERR, "error reading %s: %m", file);
  129. close(fd);
  130. memset(*buf, 0, PAM_TIME_BUFLEN);
  131. _pam_drop(*buf);
  132. *state = STATE_EOF;
  133. return -1;
  134. } else if (!i) {
  135. close(fd);
  136. fd = -1; /* end of file reached */
  137. }
  138. to += i;
  139. }
  140. if (to == *buf) {
  141. /* nothing previously in buf, nothing read */
  142. _pam_drop(*buf);
  143. *state = STATE_EOF;
  144. return -1;
  145. }
  146. memset(to, '\0', PAM_TIME_BUFLEN - (to - *buf));
  147. to = *buf;
  148. onspace = 1; /* delete any leading spaces */
  149. for (src = to; (c=*src) != '\0'; ++src) {
  150. if (*state == STATE_COMMENT && c != '\n') {
  151. continue;
  152. }
  153. switch (c) {
  154. case '\n':
  155. *state = STATE_NL;
  156. *to = '\0';
  157. *from = (src - *buf) + 1;
  158. trim_spaces(*buf, to);
  159. return fd;
  160. case '\t':
  161. case ' ':
  162. if (!onspace) {
  163. onspace = 1;
  164. *to++ = ' ';
  165. }
  166. break;
  167. case '!':
  168. onspace = 1; /* ignore following spaces */
  169. *to++ = '!';
  170. break;
  171. case '#':
  172. *state = STATE_COMMENT;
  173. break;
  174. case FIELD_SEPARATOR:
  175. *state = STATE_FIELD;
  176. *to = '\0';
  177. *from = (src - *buf) + 1;
  178. trim_spaces(*buf, to);
  179. return fd;
  180. case '\\':
  181. if (src[1] == '\n') {
  182. ++src; /* skip it */
  183. break;
  184. }
  185. /* fallthrough */
  186. default:
  187. *to++ = c;
  188. onspace = 0;
  189. }
  190. if (src > to)
  191. *src = '\0'; /* clearing */
  192. }
  193. if (*state != STATE_COMMENT) {
  194. *state = STATE_COMMENT;
  195. pam_syslog(pamh, LOG_ERR, "field too long - ignored");
  196. **buf = '\0';
  197. } else {
  198. *to = '\0';
  199. trim_spaces(*buf, to);
  200. }
  201. *from = 0;
  202. return fd;
  203. }
  204. /* read a member from a field */
  205. static int
  206. logic_member(const char *string, int *at)
  207. {
  208. int c,to;
  209. int done=0;
  210. int token=0;
  211. to=*at;
  212. do {
  213. c = string[to++];
  214. switch (c) {
  215. case '\0':
  216. --to;
  217. done = 1;
  218. break;
  219. case '&':
  220. case '|':
  221. case '!':
  222. if (token) {
  223. --to;
  224. }
  225. done = 1;
  226. break;
  227. default:
  228. if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
  229. || c == '-' || c == '.' || c == '/' || c == ':') {
  230. token = 1;
  231. } else if (token) {
  232. --to;
  233. done = 1;
  234. } else {
  235. ++*at;
  236. }
  237. }
  238. } while (!done);
  239. return to - *at;
  240. }
  241. typedef enum { VAL, OP } expect;
  242. static int
  243. logic_field(pam_handle_t *pamh, const void *me, const char *x, int rule,
  244. int (*agrees)(pam_handle_t *pamh,
  245. const void *, const char *, int, int))
  246. {
  247. int left=FALSE, right, not=FALSE;
  248. operator oper=OR;
  249. int at=0, l;
  250. expect next=VAL;
  251. while ((l = logic_member(x,&at))) {
  252. int c = x[at];
  253. if (next == VAL) {
  254. if (c == '!')
  255. not = !not;
  256. else if (isalpha(c) || c == '*' || isdigit(c) || c == '_'
  257. || c == '-' || c == '.' || c == '/' || c == ':') {
  258. right = not ^ agrees(pamh, me, x+at, l, rule);
  259. if (oper == AND)
  260. left &= right;
  261. else
  262. left |= right;
  263. next = OP;
  264. } else {
  265. pam_syslog(pamh, LOG_ERR,
  266. "garbled syntax; expected name (rule #%d)",
  267. rule);
  268. return FALSE;
  269. }
  270. } else { /* OP */
  271. switch (c) {
  272. case '&':
  273. oper = AND;
  274. break;
  275. case '|':
  276. oper = OR;
  277. break;
  278. default:
  279. pam_syslog(pamh, LOG_ERR,
  280. "garbled syntax; expected & or | (rule #%d)",
  281. rule);
  282. D(("%c at %d",c,at));
  283. return FALSE;
  284. }
  285. next = VAL;
  286. not = FALSE;
  287. }
  288. at += l;
  289. }
  290. return left;
  291. }
  292. static int
  293. is_same(pam_handle_t *pamh UNUSED, const void *A, const char *b,
  294. int len, int rule UNUSED)
  295. {
  296. int i;
  297. const char *a;
  298. a = A;
  299. for (i=0; len > 0; ++i, --len) {
  300. if (b[i] != a[i]) {
  301. if (b[i++] == '*') {
  302. return (!--len || !strncmp(b+i,a+strlen(a)-len,len));
  303. } else
  304. return FALSE;
  305. }
  306. }
  307. /* Ok, we know that b is a substring from A and does not contain
  308. wildcards, but now the length of both strings must be the same,
  309. too. In this case it means, a[i] has to be the end of the string. */
  310. if (a[i] != '\0')
  311. return FALSE;
  312. return ( !len );
  313. }
  314. typedef struct {
  315. int day; /* array of 7 bits, one set for today */
  316. int minute; /* integer, hour*100+minute for now */
  317. } TIME;
  318. static struct day {
  319. const char *d;
  320. int bit;
  321. } const days[11] = {
  322. { "su", 01 },
  323. { "mo", 02 },
  324. { "tu", 04 },
  325. { "we", 010 },
  326. { "th", 020 },
  327. { "fr", 040 },
  328. { "sa", 0100 },
  329. { "wk", 076 },
  330. { "wd", 0101 },
  331. { "al", 0177 },
  332. { NULL, 0 }
  333. };
  334. static TIME
  335. time_now(void)
  336. {
  337. struct tm *local;
  338. time_t the_time;
  339. TIME this;
  340. the_time = time((time_t *)0); /* get the current time */
  341. local = localtime(&the_time);
  342. this.day = days[local->tm_wday].bit;
  343. this.minute = local->tm_hour*100 + local->tm_min;
  344. D(("day: 0%o, time: %.4d", this.day, this.minute));
  345. return this;
  346. }
  347. /* take the current date and see if the range "date" passes it */
  348. static int
  349. check_time(pam_handle_t *pamh, const void *AT, const char *times,
  350. int len, int rule)
  351. {
  352. int not,pass;
  353. int marked_day, time_start, time_end;
  354. const TIME *at;
  355. int i,j=0;
  356. at = AT;
  357. D(("chcking: 0%o/%.4d vs. %s", at->day, at->minute, times));
  358. if (times == NULL) {
  359. /* this should not happen */
  360. pam_syslog(pamh, LOG_CRIT,
  361. "internal error in file %s at line %d",
  362. __FILE__, __LINE__);
  363. return FALSE;
  364. }
  365. if (times[j] == '!') {
  366. ++j;
  367. not = TRUE;
  368. } else {
  369. not = FALSE;
  370. }
  371. for (marked_day = 0; len > 0 && isalpha(times[j]); --len) {
  372. int this_day=-1;
  373. D(("%c%c ?", times[j], times[j+1]));
  374. for (i=0; days[i].d != NULL; ++i) {
  375. if (tolower(times[j]) == days[i].d[0]
  376. && tolower(times[j+1]) == days[i].d[1] ) {
  377. this_day = days[i].bit;
  378. break;
  379. }
  380. }
  381. j += 2;
  382. if (this_day == -1) {
  383. pam_syslog(pamh, LOG_ERR, "bad day specified (rule #%d)", rule);
  384. return FALSE;
  385. }
  386. marked_day ^= this_day;
  387. }
  388. if (marked_day == 0) {
  389. pam_syslog(pamh, LOG_ERR, "no day specified");
  390. return FALSE;
  391. }
  392. D(("day range = 0%o", marked_day));
  393. time_start = 0;
  394. for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) {
  395. time_start *= 10;
  396. time_start += times[i+j]-'0'; /* is this portable? */
  397. }
  398. j += i;
  399. if (times[j] == '-') {
  400. time_end = 0;
  401. for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) {
  402. time_end *= 10;
  403. time_end += times[i+j]-'0'; /* is this portable */
  404. }
  405. j += i;
  406. } else
  407. time_end = -1;
  408. D(("i=%d, time_end=%d, times[j]='%c'", i, time_end, times[j]));
  409. if (i != 5 || time_end == -1) {
  410. pam_syslog(pamh, LOG_ERR, "no/bad times specified (rule #%d)", rule);
  411. return TRUE;
  412. }
  413. D(("times(%d to %d)", time_start,time_end));
  414. D(("marked_day = 0%o", marked_day));
  415. /* compare with the actual time now */
  416. pass = FALSE;
  417. if (time_start < time_end) { /* start < end ? --> same day */
  418. if ((at->day & marked_day) && (at->minute >= time_start)
  419. && (at->minute < time_end)) {
  420. D(("time is listed"));
  421. pass = TRUE;
  422. }
  423. } else { /* spans two days */
  424. if ((at->day & marked_day) && (at->minute >= time_start)) {
  425. D(("caught on first day"));
  426. pass = TRUE;
  427. } else {
  428. marked_day <<= 1;
  429. marked_day |= (marked_day & 0200) ? 1:0;
  430. D(("next day = 0%o", marked_day));
  431. if ((at->day & marked_day) && (at->minute <= time_end)) {
  432. D(("caught on second day"));
  433. pass = TRUE;
  434. }
  435. }
  436. }
  437. return (not ^ pass);
  438. }
  439. static int
  440. check_account(pam_handle_t *pamh, const char *service,
  441. const char *tty, const char *user, const char *file)
  442. {
  443. int from=0, state=STATE_NL, fd=-1;
  444. char *buffer=NULL;
  445. int count=0;
  446. TIME here_and_now;
  447. int retval=PAM_SUCCESS;
  448. here_and_now = time_now(); /* find current time */
  449. do {
  450. int good=TRUE,intime;
  451. /* here we get the service name field */
  452. fd = read_field(pamh, fd, &buffer, &from, &state, file);
  453. if (!buffer || !buffer[0]) {
  454. /* empty line .. ? */
  455. continue;
  456. }
  457. ++count;
  458. if (state != STATE_FIELD) {
  459. pam_syslog(pamh, LOG_ERR,
  460. "%s: malformed rule #%d", file, count);
  461. continue;
  462. }
  463. good = logic_field(pamh, service, buffer, count, is_same);
  464. D(("with service: %s", good ? "passes":"fails" ));
  465. /* here we get the terminal name field */
  466. fd = read_field(pamh, fd, &buffer, &from, &state, file);
  467. if (state != STATE_FIELD) {
  468. pam_syslog(pamh, LOG_ERR,
  469. "%s: malformed rule #%d", file, count);
  470. continue;
  471. }
  472. good &= logic_field(pamh, tty, buffer, count, is_same);
  473. D(("with tty: %s", good ? "passes":"fails" ));
  474. /* here we get the username field */
  475. fd = read_field(pamh, fd, &buffer, &from, &state, file);
  476. if (state != STATE_FIELD) {
  477. pam_syslog(pamh, LOG_ERR,
  478. "%s: malformed rule #%d", file, count);
  479. continue;
  480. }
  481. /* If buffer starts with @, we are using netgroups */
  482. if (buffer[0] == '@')
  483. #ifdef HAVE_INNETGR
  484. good &= innetgr (&buffer[1], NULL, user, NULL);
  485. #else
  486. pam_syslog (pamh, LOG_ERR, "pam_time does not have netgroup support");
  487. #endif
  488. else
  489. good &= logic_field(pamh, user, buffer, count, is_same);
  490. D(("with user: %s", good ? "passes":"fails" ));
  491. /* here we get the time field */
  492. fd = read_field(pamh, fd, &buffer, &from, &state, file);
  493. if (state == STATE_FIELD) {
  494. pam_syslog(pamh, LOG_ERR,
  495. "%s: poorly terminated rule #%d", file, count);
  496. continue;
  497. }
  498. intime = logic_field(pamh, &here_and_now, buffer, count, check_time);
  499. D(("with time: %s", intime ? "passes":"fails" ));
  500. if (good && !intime) {
  501. /*
  502. * for security parse whole file.. also need to ensure
  503. * that the buffer is free()'d and the file is closed.
  504. */
  505. retval = PAM_PERM_DENIED;
  506. } else {
  507. D(("rule passed"));
  508. }
  509. } while (state != STATE_EOF);
  510. return retval;
  511. }
  512. /* --- public account management functions --- */
  513. int
  514. pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
  515. int argc, const char **argv)
  516. {
  517. const void *service=NULL, *void_tty=NULL;
  518. const char *tty;
  519. const char *user=NULL;
  520. const char *conf_file = NULL;
  521. int ctrl;
  522. int rv;
  523. ctrl = _pam_parse(pamh, argc, argv, &conf_file);
  524. if (ctrl & PAM_DEBUG_ARG) {
  525. pam_syslog(pamh, LOG_DEBUG, "conffile=%s", conf_file);
  526. }
  527. /* set service name */
  528. if (pam_get_item(pamh, PAM_SERVICE, &service)
  529. != PAM_SUCCESS || service == NULL) {
  530. pam_syslog(pamh, LOG_ERR, "cannot find the current service name");
  531. return PAM_ABORT;
  532. }
  533. /* set username */
  534. if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || *user == '\0') {
  535. pam_syslog(pamh, LOG_NOTICE, "cannot determine user name");
  536. return PAM_USER_UNKNOWN;
  537. }
  538. /* set tty name */
  539. if (pam_get_item(pamh, PAM_TTY, &void_tty) != PAM_SUCCESS
  540. || void_tty == NULL) {
  541. D(("PAM_TTY not set, probing stdin"));
  542. tty = ttyname(STDIN_FILENO);
  543. if (tty == NULL) {
  544. tty = "";
  545. }
  546. if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
  547. pam_syslog(pamh, LOG_ERR, "couldn't set tty name");
  548. return PAM_ABORT;
  549. }
  550. }
  551. else
  552. tty = void_tty;
  553. if (tty[0] == '/') { /* full path */
  554. const char *t;
  555. tty++;
  556. if ((t = strchr(tty, '/')) != NULL) {
  557. tty = t + 1;
  558. }
  559. }
  560. /* good, now we have the service name, the user and the terminal name */
  561. D(("service=%s", service));
  562. D(("user=%s", user));
  563. D(("tty=%s", tty));
  564. rv = check_account(pamh, service, tty, user, conf_file);
  565. if (rv != PAM_SUCCESS) {
  566. #ifdef HAVE_LIBAUDIT
  567. if (!(ctrl & PAM_NO_AUDIT)) {
  568. pam_modutil_audit_write(pamh, AUDIT_ANOM_LOGIN_TIME,
  569. "pam_time", rv); /* ignore return value as we fail anyway */
  570. }
  571. #endif
  572. if (ctrl & PAM_DEBUG_ARG) {
  573. pam_syslog(pamh, LOG_DEBUG, "user %s rejected", user);
  574. }
  575. }
  576. return rv;
  577. }
  578. /* end of module definition */