diff options
author | Robert Fairley <rfairley@users.noreply.github.com> | 2018-11-19 03:00:16 -0500 |
---|---|---|
committer | Tomáš Mráz <t8m@users.noreply.github.com> | 2018-11-19 09:00:16 +0100 |
commit | f9c9c72121eada731e010ab3620762bcf63db08f (patch) | |
tree | 0af482fd580b4794d977e7d8f584e78f522a7d59 /modules/pam_motd | |
parent | f7abb8c1ef3aa31e6c2564a8aaf69683a77c2016 (diff) | |
download | pam-f9c9c72121eada731e010ab3620762bcf63db08f.tar.gz pam-f9c9c72121eada731e010ab3620762bcf63db08f.tar.bz2 pam-f9c9c72121eada731e010ab3620762bcf63db08f.zip |
pam_motd: Support multiple motd paths specified, with filename overrides (#69)
Adds specifying multiple paths to motd files and motd.d
directories to be displayed. A colon-separated list of
paths is specified as arguments motd and motd_dir to the
pam_motd module.
This gives packages several options to install motd files to.
By default, the paths are, with highest priority first:
/etc/motd
/run/motd
/usr/lib/motd
/etc/motd.d/
/run/motd.d/
/usr/lib/motd.d/
Which is equivalent to the following arguments:
motd=/etc/motd:/run/motd:/usr/lib/motd
motd_dir=/etc/motd.d:/run/motd.d:/usr/lib/motd.d
Files with the same filename in a lower-priority directory,
as specified by the order in the colon-separated list, are
overridden, meaning PAM will not display them.
This allows a package to contain motd files under
/usr/lib instead of the host configuration in /etc.
A service may also write a dynamically generated motd in
/run/motd.d/ and have PAM display it without needing a
symlink from /etc/motd.d/ installed.
Closes #68
* modules/pam_motd/pam_motd.8.xml: update documentation
* modules/pam_motd/pam_motd.c: add specifying multiple motd paths
* xtests/.gitignore: add generated test script
* xtests/Makefile.am: add test source, scripts and config files
* xtests/tst-pam_motd.c: create
* xtests/tst-pam_motd.sh: create
* xtests/tst-pam_motd1.pamd: create
* xtests/tst-pam_motd1.sh: create
* xtests/tst-pam_motd2.pamd: create
* xtests/tst-pam_motd2.sh: create
* xtests/tst-pam_motd3.pamd: create
* xtests/tst-pam_motd3.sh: create
Diffstat (limited to 'modules/pam_motd')
-rw-r--r-- | modules/pam_motd/pam_motd.8.xml | 65 | ||||
-rw-r--r-- | modules/pam_motd/pam_motd.c | 271 |
2 files changed, 317 insertions, 19 deletions
diff --git a/modules/pam_motd/pam_motd.8.xml b/modules/pam_motd/pam_motd.8.xml index 906c4ed0..4e2110c1 100644 --- a/modules/pam_motd/pam_motd.8.xml +++ b/modules/pam_motd/pam_motd.8.xml @@ -21,6 +21,9 @@ <arg choice="opt"> motd=<replaceable>/path/filename</replaceable> </arg> + <arg choice="opt"> + motd_dir=<replaceable>/path/dirname.d</replaceable> + </arg> </cmdsynopsis> </refsynopsisdiv> @@ -31,10 +34,49 @@ <para> pam_motd is a PAM module that can be used to display arbitrary motd (message of the day) files after a successful - login. By default the <filename>/etc/motd</filename> file is - shown. The message size is limited to 64KB. + login. By default, pam_motd shows files in the + following locations: + </para> + <para> + <simplelist type='vert'> + <member><filename>/etc/motd</filename></member> + <member><filename>/run/motd</filename></member> + <member><filename>/usr/lib/motd</filename></member> + <member><filename>/etc/motd.d/</filename></member> + <member><filename>/run/motd.d/</filename></member> + <member><filename>/usr/lib/motd.d/</filename></member> + </simplelist> + </para> + <para> + Each message size is limited to 64KB. + </para> + <para> + If <filename>/etc/motd</filename> does not exist, + then <filename>/run/motd</filename> is shown. If + <filename>/run/motd</filename> does not exist, then + <filename>/usr/lib/motd</filename> is shown. + </para> + <para> + Similar overriding behavior applies to the directories. + Files in <filename>/etc/motd.d/</filename> override files + with the same name in <filename>/run/motd.d/</filename> and + <filename>/usr/lib/motd.d/</filename>. Files in <filename>/run/motd.d/</filename> + override files with the same name in <filename>/usr/lib/motd.d/</filename>. + </para> + <para> + Files the in the directories listed above are displayed in + lexicographic order by name. + </para> + <para> + To silence a message, + a symbolic link with target <filename>/dev/null</filename> + may be placed in <filename>/etc/motd.d</filename> with + the same filename as the message to be silenced. Example: + Creating a symbolic link as follows silences <filename>/usr/lib/motd.d/my_motd</filename>. + </para> + <para> + <command>ln -s /dev/null /etc/motd.d/my_motd</command> </para> - </refsect1> <refsect1 id="pam_motd-options"> @@ -47,8 +89,10 @@ </term> <listitem> <para> - The <filename>/path/filename</filename> file is displayed - as message of the day. + The <filename>/path/filename</filename> file is displayed + as message of the day. Multiple paths to try can be + specified as a colon-separated list. By default this option + is set to <filename>/etc/motd:/run/motd:/usr/lib/motd</filename>. </para> </listitem> </varlistentry> @@ -59,16 +103,17 @@ <listitem> <para> The <filename>/path/dirname.d</filename> directory is scanned - and each file contained inside of it is displayed. + and each file contained inside of it is displayed. Multiple + directories to scan can be specified as a colon-separated list. + By default this option is set to <filename>/etc/motd.d:/run/motd.d:/usr/lib/motd.d</filename>. </para> </listitem> </varlistentry> </variablelist> <para> - When no options are given, the default is to display both - <filename>/etc/motd</filename> and the contents of - <filename>/etc/motd.d</filename>. Specifying either option (or both) - will disable this default behavior. + When no options are given, the default behavior applies for both + options. Specifying either option (or both) will disable the + default behavior for both options. </para> </refsect1> diff --git a/modules/pam_motd/pam_motd.c b/modules/pam_motd/pam_motd.c index cc828d7e..1c1cfcfa 100644 --- a/modules/pam_motd/pam_motd.c +++ b/modules/pam_motd/pam_motd.c @@ -33,8 +33,8 @@ */ #define PAM_SM_SESSION -#define DEFAULT_MOTD "/etc/motd" -#define DEFAULT_MOTD_D "/etc/motd.d" +#define DEFAULT_MOTD "/etc/motd:/run/motd:/usr/lib/motd" +#define DEFAULT_MOTD_D "/etc/motd.d:/run/motd.d:/usr/lib/motd.d" #include <security/pam_modules.h> #include <security/pam_modutil.h> @@ -97,12 +97,235 @@ static void try_to_display_directory(pam_handle_t *pamh, const char *dirname) } } +/* + * Split a DELIM-separated string ARG into an array. + * Outputs a newly allocated array of strings OUT_ARG_SPLIT + * and the number of strings OUT_NUM_STRS. + * Returns 0 in case of error, 1 in case of success. + */ +static int pam_split_string(const pam_handle_t *pamh, char *arg, char delim, + char ***out_arg_split, uint *out_num_strs) +{ + char *arg_extracted = NULL; + const char *arg_ptr = arg; + char **arg_split = NULL; + char delim_str[2]; + int i = 0; + uint num_strs = 0; + int retval = 0; + + delim_str[0] = delim; + delim_str[1] = '\0'; + + if (arg == NULL) { + goto out; + } + + while (arg_ptr != NULL) { + num_strs++; + arg_ptr = strchr(arg_ptr + sizeof(const char), delim); + } + + arg_split = (char **)calloc(num_strs, sizeof(char *)); + if (arg_split == NULL) { + pam_syslog(pamh, LOG_CRIT, "pam_motd: failed to allocate string array"); + goto out; + } + + + arg_extracted = strtok_r(arg, delim_str, &arg); + while (arg_extracted != NULL && i < num_strs) { + arg_split[i++] = arg_extracted; + arg_extracted = strtok_r(NULL, delim_str, &arg); + } + + retval = 1; + + out: + *out_num_strs = num_strs; + *out_arg_split = arg_split; + + return retval; +} + +/* Join A_STR and B_STR, inserting a "/" between them if one is not already trailing + * in A_STR or beginning B_STR. A pointer to a newly allocated string holding the + * joined string is returned in STRP_OUT. + * Returns -1 in case of error, or the number of bytes in the joined string in + * case of success. */ +static int join_dir_strings(char **strp_out, const char *a_str, const char *b_str) +{ + int has_sep = 0; + int retval = -1; + char *join_strp = NULL; + + if (strp_out == NULL || a_str == NULL || b_str == NULL) { + goto out; + } + if (strlen(a_str) == 0) { + goto out; + } + + has_sep = (a_str[strlen(a_str) - 1] == '/') || (b_str[0] == '/'); + + retval = asprintf(&join_strp, "%s%s%s", a_str, + (has_sep == 1) ? "" : "/", b_str); + + if (retval < 0) { + goto out; + } + + *strp_out = join_strp; + + out: + return retval; +} + +static int compare_strings(const void * a, const void * b) +{ + const char *a_str = *(char **)a; + const char *b_str = *(char **)b; + + if (a_str == NULL && b_str == NULL) { + return 0; + } + else if (a_str == NULL) { + return -1; + } + else if (b_str == NULL) { + return 1; + } + else { + return strcmp(a_str, b_str); + } +} + +static int filter_dirents(const struct dirent *d) +{ + return (d->d_type == DT_REG || d->d_type == DT_LNK); +} + +static void try_to_display_directories_with_overrides(pam_handle_t *pamh, + char **motd_dir_path_split, int num_motd_dirs) +{ + struct dirent ***dirscans = NULL; + int *dirscans_sizes = NULL; + int dirscans_size_total = 0; + char **dirnames_all = NULL; + int i; + int i_dirnames = 0; + + if (pamh == NULL || motd_dir_path_split == NULL) { + goto out; + } + if (num_motd_dirs < 1) { + goto out; + } + + if ((dirscans = (struct dirent ***)calloc(num_motd_dirs, + sizeof(struct dirent **))) == NULL) { + pam_syslog(pamh, LOG_CRIT, "pam_motd: failed to allocate dirent arrays"); + goto out; + } + if ((dirscans_sizes = (int *)calloc(num_motd_dirs, sizeof(int))) == NULL) { + pam_syslog(pamh, LOG_CRIT, "pam_motd: failed to allocate dirent array sizes"); + goto out; + } + + for (i = 0; i < num_motd_dirs; i++) { + dirscans_sizes[i] = scandir(motd_dir_path_split[i], &(dirscans[i]), + filter_dirents, alphasort); + if (dirscans_sizes[i] < 0) { + pam_syslog(pamh, LOG_ERR, "pam_motd: error scanning directory %s", motd_dir_path_split[i]); + dirscans_sizes[i] = 0; + } + dirscans_size_total += dirscans_sizes[i]; + } + + /* Allocate space for all file names found in the directories, including duplicates. */ + if ((dirnames_all = (char **)calloc(dirscans_size_total, + sizeof(char *))) == NULL) { + pam_syslog(pamh, LOG_CRIT, "pam_motd: failed to allocate dirname array"); + goto out; + } + + for (i = 0; i < dirscans_size_total; i++) { + dirnames_all[i] = NULL; + } + + for (i = 0; i < num_motd_dirs; i++) { + int j; + + for (j = 0; j < dirscans_sizes[i]; j++) { + dirnames_all[i_dirnames] = dirscans[i][j]->d_name; + i_dirnames++; + } + } + + qsort(dirnames_all, dirscans_size_total, + sizeof(const char *), compare_strings); + + for (i = 0; i < dirscans_size_total; i++) { + int j; + + if (dirnames_all[i] == NULL) { + continue; + } + + /* Skip duplicate file names. */ + if (i > 0 && strcmp(dirnames_all[i], dirnames_all[i - 1]) == 0) { + continue; + } + + for (j = 0; j < num_motd_dirs; j++) { + char *abs_path = NULL; + + if (join_dir_strings(&abs_path, motd_dir_path_split[j], + dirnames_all[i]) < 0) { + continue; + } + + if (abs_path != NULL) { + int fd = open(abs_path, O_RDONLY, 0); + if (fd >= 0) { + try_to_display_fd(pamh, fd); + close(fd); + + /* We displayed a file, skip to the next file name. */ + break; + } + } + _pam_drop(abs_path); + } + } + + out: + _pam_drop(dirnames_all); + for (i = 0; i < num_motd_dirs; i++) { + int j; + for (j = 0; j < dirscans_sizes[i]; j++) { + _pam_drop(dirscans[i][j]); + } + _pam_drop(dirscans[i]); + } + _pam_drop(dirscans_sizes); + _pam_drop(dirscans); + + return; +} + int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { int retval = PAM_IGNORE; const char *motd_path = NULL; + char *motd_path_copy = NULL; + int num_motd_paths = 0; + char **motd_path_split = NULL; const char *motd_dir_path = NULL; + char *motd_dir_path_copy = NULL; + int num_motd_dir_paths = 0; + char **motd_dir_path_split = NULL; if (flags & PAM_SILENT) { return retval; @@ -140,17 +363,47 @@ int pam_sm_open_session(pam_handle_t *pamh, int flags, motd_dir_path = default_motd_dir; } - if (motd_path != NULL) { - int fd = open(motd_path, O_RDONLY, 0); + motd_path_copy = strdup(motd_path); + if (motd_path_copy != NULL) { + if (pam_split_string(pamh, motd_path_copy, ':', &motd_path_split, + &num_motd_paths) == 0) { + goto out; + } + } + + motd_dir_path_copy = strdup(motd_dir_path); + if (motd_dir_path_copy != NULL) { + if (pam_split_string(pamh, motd_dir_path_copy, ':', + &motd_dir_path_split, &num_motd_dir_paths) == 0) { + goto out; + } + } + + if (motd_path_split != NULL) { + int i; + + for (i = 0; i < num_motd_paths; i++) { + int fd = open(motd_path_split[i], O_RDONLY, 0); - if (fd >= 0) { - try_to_display_fd(pamh, fd); - close(fd); + if (fd >= 0) { + try_to_display_fd(pamh, fd); + close(fd); + + /* We found and displayed a file, move onto next filename. */ + break; + } } } - if (motd_dir_path != NULL) - try_to_display_directory(pamh, motd_dir_path); + if (motd_dir_path_split != NULL) + try_to_display_directories_with_overrides(pamh, motd_dir_path_split, + num_motd_dir_paths); + + out: + _pam_drop(motd_path_copy); + _pam_drop(motd_path_split); + _pam_drop(motd_dir_path_copy); + _pam_drop(motd_dir_path_split); return retval; } |