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