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 */