(root)/
Linux-PAM-1.5.3/
modules/
pam_motd/
pam_motd.c
       1  /*
       2   * pam_motd module
       3   *
       4   * Modified for pam_motd by Ben Collins <bcollins@debian.org>
       5   * Written by Michael K. Johnson <johnsonm@redhat.com> 1996/10/24
       6   */
       7  
       8  #include "config.h"
       9  
      10  #include <stdio.h>
      11  #include <string.h>
      12  #include <stdlib.h>
      13  #include <unistd.h>
      14  #include <fcntl.h>
      15  #include <dirent.h>
      16  #include <sys/types.h>
      17  #include <sys/stat.h>
      18  #include <pwd.h>
      19  #include <syslog.h>
      20  #include <errno.h>
      21  
      22  #include <security/_pam_macros.h>
      23  #include <security/pam_ext.h>
      24  #include <security/pam_modules.h>
      25  #include <security/pam_modutil.h>
      26  #include "pam_inline.h"
      27  
      28  #define DEFAULT_MOTD	"/etc/motd:/run/motd:/usr/lib/motd"
      29  #define DEFAULT_MOTD_D	"/etc/motd.d:/run/motd.d:/usr/lib/motd.d"
      30  
      31  /* --- session management functions (only) --- */
      32  
      33  int
      34  pam_sm_close_session (pam_handle_t *pamh UNUSED, int flags UNUSED,
      35  		      int argc UNUSED, const char **argv UNUSED)
      36  {
      37       return PAM_IGNORE;
      38  }
      39  
      40  static const char default_motd[] = DEFAULT_MOTD;
      41  static const char default_motd_dir[] = DEFAULT_MOTD_D;
      42  
      43  static void try_to_display_fd(pam_handle_t *pamh, int fd)
      44  {
      45      struct stat st;
      46      char *mtmp = NULL;
      47  
      48      /* fill in message buffer with contents of motd */
      49      if ((fstat(fd, &st) < 0) || !st.st_size || st.st_size > 0x10000)
      50  	return;
      51  
      52      if (!(mtmp = malloc(st.st_size+1)))
      53  	return;
      54  
      55      if (pam_modutil_read(fd, mtmp, st.st_size) == st.st_size) {
      56  	if (mtmp[st.st_size-1] == '\n')
      57  	    mtmp[st.st_size-1] = '\0';
      58  	else
      59  	    mtmp[st.st_size] = '\0';
      60  
      61  	pam_info (pamh, "%s", mtmp);
      62      }
      63  
      64      _pam_drop(mtmp);
      65  }
      66  
      67  /*
      68   * Split a DELIM-separated string ARG into an array.
      69   * Outputs a newly allocated array of strings OUT_ARG_SPLIT
      70   * and the number of strings OUT_NUM_STRS.
      71   * Returns 0 in case of error, 1 in case of success.
      72   */
      73  static int pam_split_string(const pam_handle_t *pamh, char *arg, char delim,
      74  			    char ***out_arg_split, unsigned int *out_num_strs)
      75  {
      76      char *arg_extracted = NULL;
      77      const char *arg_ptr = arg;
      78      char **arg_split = NULL;
      79      char delim_str[2];
      80      unsigned int i = 0;
      81      unsigned int num_strs = 0;
      82      int retval = 0;
      83  
      84      delim_str[0] = delim;
      85      delim_str[1] = '\0';
      86  
      87      if (arg == NULL) {
      88  	goto out;
      89      }
      90  
      91      while (arg_ptr != NULL) {
      92  	num_strs++;
      93  	arg_ptr = strchr(arg_ptr + sizeof(const char), delim);
      94      }
      95  
      96      arg_split = calloc(num_strs, sizeof(*arg_split));
      97      if (arg_split == NULL) {
      98  	pam_syslog(pamh, LOG_CRIT, "failed to allocate string array");
      99  	goto out;
     100      }
     101  
     102      arg_extracted = strtok_r(arg, delim_str, &arg);
     103      while (arg_extracted != NULL && i < num_strs) {
     104  	arg_split[i++] = arg_extracted;
     105  	arg_extracted = strtok_r(NULL, delim_str, &arg);
     106      }
     107  
     108      retval = 1;
     109  
     110    out:
     111      *out_num_strs = num_strs;
     112      *out_arg_split = arg_split;
     113  
     114      return retval;
     115  }
     116  
     117  /* Join A_STR and B_STR, inserting a "/" between them if one is not already trailing
     118   * in A_STR or beginning B_STR. A pointer to a newly allocated string holding the
     119   * joined string is returned in STRP_OUT.
     120   * Returns -1 in case of error, or the number of bytes in the joined string in
     121   * case of success. */
     122  static int join_dir_strings(char **strp_out, const char *a_str, const char *b_str)
     123  {
     124      int has_sep = 0;
     125      int retval = -1;
     126      char *join_strp = NULL;
     127  
     128      if (strp_out == NULL || a_str == NULL || b_str == NULL) {
     129  	goto out;
     130      }
     131      if (strlen(a_str) == 0) {
     132  	goto out;
     133      }
     134  
     135      has_sep = (a_str[strlen(a_str) - 1] == '/') || (b_str[0] == '/');
     136  
     137      retval = asprintf(&join_strp, "%s%s%s", a_str,
     138  	(has_sep == 1) ? "" : "/", b_str);
     139  
     140      if (retval < 0) {
     141  	goto out;
     142      }
     143  
     144      *strp_out = join_strp;
     145  
     146    out:
     147      return retval;
     148  }
     149  
     150  static int compare_strings(const void *a, const void *b)
     151  {
     152      const char *a_str = *(const char * const *)a;
     153      const char *b_str = *(const char * const *)b;
     154  
     155      if (a_str == NULL && b_str == NULL) {
     156          return 0;
     157      }
     158      else if (a_str == NULL) {
     159  	return -1;
     160      }
     161      else if (b_str == NULL) {
     162  	return 1;
     163      }
     164      else {
     165  	return strcmp(a_str, b_str);
     166      }
     167  }
     168  
     169  static void try_to_display_directories_with_overrides(pam_handle_t *pamh,
     170  	char **motd_dir_path_split, unsigned int num_motd_dirs, int report_missing)
     171  {
     172      struct dirent ***dirscans = NULL;
     173      unsigned int *dirscans_sizes = NULL;
     174      unsigned int dirscans_size_total = 0;
     175      char **dirnames_all = NULL;
     176      unsigned int i;
     177      int i_dirnames = 0;
     178  
     179      if (pamh == NULL || motd_dir_path_split == NULL) {
     180  	goto out;
     181      }
     182      if (num_motd_dirs < 1) {
     183  	goto out;
     184      }
     185  
     186      if ((dirscans = calloc(num_motd_dirs, sizeof(*dirscans))) == NULL) {
     187  	pam_syslog(pamh, LOG_CRIT, "failed to allocate dirent arrays");
     188  	goto out;
     189      }
     190      if ((dirscans_sizes = calloc(num_motd_dirs, sizeof(*dirscans_sizes))) == NULL) {
     191  	pam_syslog(pamh, LOG_CRIT, "failed to allocate dirent array sizes");
     192  	goto out;
     193      }
     194  
     195      for (i = 0; i < num_motd_dirs; i++) {
     196  	int rv;
     197  	rv = scandir(motd_dir_path_split[i], &(dirscans[i]), NULL, NULL);
     198  	if (rv < 0) {
     199  	    if (errno != ENOENT || report_missing) {
     200  		pam_syslog(pamh, LOG_ERR, "error scanning directory %s: %m",
     201  		    motd_dir_path_split[i]);
     202  	    }
     203  	} else {
     204  	    dirscans_sizes[i] = rv;
     205  	}
     206  	dirscans_size_total += dirscans_sizes[i];
     207      }
     208  
     209      if (dirscans_size_total == 0)
     210          goto out;
     211  
     212      /* filter out unwanted names, directories, and complement data with lstat() */
     213      for (i = 0; i < num_motd_dirs; i++) {
     214  	struct dirent **d = dirscans[i];
     215  	for (unsigned int j = 0; j < dirscans_sizes[i]; j++) {
     216  	    int rc;
     217  	    char *fullpath;
     218  	    struct stat s;
     219  
     220  	    switch(d[j]->d_type) {    /* the filetype determines how to proceed */
     221  	    case DT_REG:              /* regular files and     */
     222  	    case DT_LNK:              /* symlinks              */
     223  		continue;             /* are good.             */
     224  	    case DT_UNKNOWN:   /* for file systems that do not provide */
     225  			       /* a filetype, we use lstat()           */
     226  		if (join_dir_strings(&fullpath, motd_dir_path_split[i],
     227  				     d[j]->d_name) <= 0)
     228  		    break;
     229  		rc = lstat(fullpath, &s);
     230  		_pam_drop(fullpath);  /* free the memory alloc'ed by join_dir_strings */
     231  		if (rc != 0)          /* if the lstat() somehow failed */
     232  		    break;
     233  
     234  		if (S_ISREG(s.st_mode) ||          /* regular files and  */
     235  		    S_ISLNK(s.st_mode)) continue;  /* symlinks are good  */
     236  		break;
     237  	    case DT_DIR:          /* We don't want directories     */
     238  	    default:              /* nor anything else             */
     239  		break;
     240  	    }
     241  	    _pam_drop(d[j]);  /* free memory                   */
     242  	    d[j] = NULL;      /* indicate this one was dropped */
     243  	    dirscans_size_total--;
     244  	}
     245      }
     246  
     247      /* Allocate space for all file names found in the directories, including duplicates. */
     248      if ((dirnames_all = calloc(dirscans_size_total, sizeof(*dirnames_all))) == NULL) {
     249  	pam_syslog(pamh, LOG_CRIT, "failed to allocate dirname array");
     250  	goto out;
     251      }
     252  
     253      for (i = 0; i < num_motd_dirs; i++) {
     254  	unsigned int j;
     255  
     256  	for (j = 0; j < dirscans_sizes[i]; j++) {
     257  	    if (NULL != dirscans[i][j]) {
     258  	        dirnames_all[i_dirnames] = dirscans[i][j]->d_name;
     259  	        i_dirnames++;
     260  	    }
     261  	}
     262      }
     263  
     264      qsort(dirnames_all, dirscans_size_total,
     265  	    sizeof(const char *), compare_strings);
     266  
     267      for (i = 0; i < dirscans_size_total; i++) {
     268  	unsigned int j;
     269  
     270  	if (dirnames_all[i] == NULL) {
     271  	    continue;
     272  	}
     273  
     274  	/* Skip duplicate file names. */
     275  	if (i > 0 && strcmp(dirnames_all[i], dirnames_all[i - 1]) == 0) {
     276  	    continue;
     277  	}
     278  
     279  	for (j = 0; j < num_motd_dirs; j++) {
     280  	    char *abs_path = NULL;
     281  	    int fd;
     282  
     283  	    if (join_dir_strings(&abs_path, motd_dir_path_split[j],
     284  		    dirnames_all[i]) < 0 || abs_path == NULL) {
     285  		continue;
     286  	    }
     287  
     288  	    fd = open(abs_path, O_RDONLY, 0);
     289  	    _pam_drop(abs_path);
     290  
     291  	    if (fd >= 0) {
     292  		try_to_display_fd(pamh, fd);
     293  		close(fd);
     294  
     295  		/* We displayed a file, skip to the next file name. */
     296  		break;
     297  	    }
     298  	}
     299      }
     300  
     301    out:
     302      _pam_drop(dirnames_all);
     303      if (dirscans_sizes != NULL) {
     304  	for (i = 0; i < num_motd_dirs; i++) {
     305  	    unsigned int j;
     306  
     307  	    for (j = 0; j < dirscans_sizes[i]; j++)
     308  		_pam_drop(dirscans[i][j]);
     309  	    _pam_drop(dirscans[i]);
     310  	}
     311  	_pam_drop(dirscans_sizes);
     312      }
     313      _pam_drop(dirscans);
     314  }
     315  
     316  static int drop_privileges(pam_handle_t *pamh, struct pam_modutil_privs *privs)
     317  {
     318      struct passwd *pw;
     319      const char *username;
     320      int retval;
     321  
     322      retval = pam_get_user(pamh, &username, NULL);
     323  
     324      if (retval == PAM_SUCCESS) {
     325          pw = pam_modutil_getpwnam (pamh, username);
     326      } else {
     327          return PAM_SESSION_ERR;
     328      }
     329  
     330      if (pw == NULL || pam_modutil_drop_priv(pamh, privs, pw)) {
     331          return PAM_SESSION_ERR;
     332      }
     333  
     334      return PAM_SUCCESS;
     335  }
     336  
     337  static int try_to_display(pam_handle_t *pamh, char **motd_path_split,
     338                            unsigned int num_motd_paths,
     339                            char **motd_dir_path_split,
     340                            unsigned int num_motd_dir_paths, int report_missing)
     341  {
     342      PAM_MODUTIL_DEF_PRIVS(privs);
     343  
     344      if (drop_privileges(pamh, &privs) != PAM_SUCCESS) {
     345          pam_syslog(pamh, LOG_ERR, "Unable to drop privileges");
     346          return PAM_SESSION_ERR;
     347      }
     348  
     349      if (motd_path_split != NULL) {
     350          unsigned int i;
     351  
     352          for (i = 0; i < num_motd_paths; i++) {
     353              int fd = open(motd_path_split[i], O_RDONLY, 0);
     354  
     355              if (fd >= 0) {
     356                  try_to_display_fd(pamh, fd);
     357                  close(fd);
     358  
     359                  /* We found and displayed a file,
     360                      * move onto next filename.
     361                      */
     362                  break;
     363              }
     364          }
     365      }
     366  
     367      if (motd_dir_path_split != NULL) {
     368          try_to_display_directories_with_overrides(pamh,
     369                                                      motd_dir_path_split,
     370                                                      num_motd_dir_paths,
     371                                                      report_missing);
     372      }
     373  
     374      if (pam_modutil_regain_priv(pamh, &privs)) {
     375          pam_syslog(pamh, LOG_ERR, "Unable to regain privileges");
     376          return PAM_SESSION_ERR;
     377      }
     378  
     379      return PAM_SUCCESS;
     380  }
     381  
     382  int pam_sm_open_session(pam_handle_t *pamh, int flags,
     383  			int argc, const char **argv)
     384  {
     385      int retval = PAM_IGNORE;
     386      const char *motd_path = NULL;
     387      char *motd_path_copy = NULL;
     388      unsigned int num_motd_paths = 0;
     389      char **motd_path_split = NULL;
     390      const char *motd_dir_path = NULL;
     391      char *motd_dir_path_copy = NULL;
     392      unsigned int num_motd_dir_paths = 0;
     393      char **motd_dir_path_split = NULL;
     394      int report_missing;
     395  
     396      if (flags & PAM_SILENT) {
     397  	return retval;
     398      }
     399  
     400      for (; argc-- > 0; ++argv) {
     401  	const char *str;
     402  	if ((str = pam_str_skip_prefix(*argv, "motd=")) != NULL) {
     403  
     404              motd_path = str;
     405              if (*motd_path != '\0') {
     406                  D(("set motd path: %s", motd_path));
     407  	    } else {
     408  		motd_path = NULL;
     409  		pam_syslog(pamh, LOG_ERR,
     410  			   "motd= specification missing argument - ignored");
     411  	    }
     412  	}
     413  	else if ((str = pam_str_skip_prefix(*argv, "motd_dir=")) != NULL) {
     414  
     415              motd_dir_path = str;
     416              if (*motd_dir_path != '\0') {
     417                  D(("set motd.d path: %s", motd_dir_path));
     418  	    } else {
     419  		motd_dir_path = NULL;
     420  		pam_syslog(pamh, LOG_ERR,
     421  			   "motd_dir= specification missing argument - ignored");
     422  	    }
     423  	}
     424  	else
     425  	    pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
     426      }
     427  
     428      if (motd_path == NULL && motd_dir_path == NULL) {
     429  	motd_path = default_motd;
     430  	motd_dir_path = default_motd_dir;
     431  	report_missing = 0;
     432      } else {
     433  	report_missing = 1;
     434      }
     435  
     436      if (motd_path != NULL) {
     437  	motd_path_copy = strdup(motd_path);
     438      }
     439  
     440      if (motd_path_copy != NULL) {
     441  	if (pam_split_string(pamh, motd_path_copy, ':',
     442  		&motd_path_split, &num_motd_paths) == 0) {
     443  	    goto out;
     444  	}
     445      }
     446  
     447      if (motd_dir_path != NULL) {
     448  	motd_dir_path_copy = strdup(motd_dir_path);
     449      }
     450  
     451      if (motd_dir_path_copy != NULL) {
     452  	if (pam_split_string(pamh, motd_dir_path_copy, ':',
     453  		&motd_dir_path_split, &num_motd_dir_paths) == 0) {
     454  	    goto out;
     455  	}
     456      }
     457  
     458      retval = try_to_display(pamh, motd_path_split, num_motd_paths,
     459                              motd_dir_path_split, num_motd_dir_paths,
     460                              report_missing);
     461  
     462    out:
     463      _pam_drop(motd_path_copy);
     464      _pam_drop(motd_path_split);
     465      _pam_drop(motd_dir_path_copy);
     466      _pam_drop(motd_dir_path_split);
     467  
     468      if (retval == PAM_SUCCESS) {
     469          retval = pam_putenv(pamh, "MOTD_SHOWN=pam");
     470          return retval == PAM_SUCCESS ? PAM_IGNORE : retval;
     471      } else {
     472          return retval;
     473      }
     474  }
     475  
     476  /* end of module definition */