]> granicus.if.org Git - linux-pam/blob - modules/pam_mail/pam_mail.c
0022f6d639656735ff149c0ca36d055ee1e8ee52
[linux-pam] / modules / pam_mail / pam_mail.c
1 /* pam_mail module */
2
3 /*
4  * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/3/11
5  * $HOME additions by David Kinchlea <kinch@kinch.ark.com> 1997/1/7
6  * mailhash additions by Chris Adams <cadams@ro.com> 1998/7/11
7  */
8
9 #include "config.h"
10
11 #include <ctype.h>
12 #include <pwd.h>
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <syslog.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
20 #include <unistd.h>
21 #include <dirent.h>
22 #include <errno.h>
23
24 #ifdef HAVE_PATHS_H
25 #include <paths.h>
26 #endif
27
28 #define DEFAULT_MAIL_DIRECTORY    PAM_PATH_MAILDIR
29 #define MAIL_FILE_FORMAT          "%s%s/%s"
30 #define MAIL_ENV_NAME             "MAIL"
31 #define MAIL_ENV_FORMAT           MAIL_ENV_NAME "=%s"
32
33 /*
34  * here, we make a definition for the externally accessible function
35  * in this file (this definition is required for static a module
36  * but strongly encouraged generally) it is used to instruct the
37  * modules include file to define the function prototypes.
38  */
39
40 #define PAM_SM_SESSION
41 #define PAM_SM_AUTH
42
43 #include <security/pam_modules.h>
44 #include <security/_pam_macros.h>
45 #include <security/pam_modutil.h>
46 #include <security/pam_ext.h>
47
48 /* argument parsing */
49
50 #define PAM_DEBUG_ARG           0x0001
51 #define PAM_NO_LOGIN            0x0002
52 #define PAM_LOGOUT_TOO          0x0004
53 #define PAM_NEW_MAIL_DIR        0x0010
54 #define PAM_MAIL_SILENT         0x0020
55 #define PAM_NO_ENV              0x0040
56 #define PAM_HOME_MAIL           0x0100
57 #define PAM_EMPTY_TOO           0x0200
58 #define PAM_STANDARD_MAIL       0x0400
59 #define PAM_QUIET_MAIL          0x1000
60
61 #define HAVE_NEW_MAIL           0x1
62 #define HAVE_OLD_MAIL           0x2
63 #define HAVE_NO_MAIL            0x3
64 #define HAVE_MAIL               0x4
65
66 static int
67 _pam_parse (const pam_handle_t *pamh, int flags, int argc,
68             const char **argv, const char **maildir, size_t *hashcount)
69 {
70     int ctrl=0;
71
72     if (flags & PAM_SILENT) {
73         ctrl |= PAM_MAIL_SILENT;
74     }
75
76     *hashcount = 0;
77
78     /* step through arguments */
79     for (; argc-- > 0; ++argv) {
80
81         /* generic options */
82
83         if (!strcmp(*argv,"debug"))
84             ctrl |= PAM_DEBUG_ARG;
85         else if (!strcmp(*argv,"quiet"))
86             ctrl |= PAM_QUIET_MAIL;
87         else if (!strcmp(*argv,"standard"))
88             ctrl |= PAM_STANDARD_MAIL | PAM_EMPTY_TOO;
89         else if (!strncmp(*argv,"dir=",4)) {
90             *maildir = 4 + *argv;
91             if (**maildir != '\0') {
92                 D(("new mail directory: %s", *maildir));
93                 ctrl |= PAM_NEW_MAIL_DIR;
94             } else {
95                 pam_syslog(pamh, LOG_ERR,
96                            "dir= specification missing argument - ignored");
97             }
98         } else if (!strncmp(*argv,"hash=",5)) {
99             char *ep = NULL;
100             *hashcount = strtoul(*argv+5,&ep,10);
101             if (!ep) {
102                 *hashcount = 0;
103             }
104         } else if (!strcmp(*argv,"close")) {
105             ctrl |= PAM_LOGOUT_TOO;
106         } else if (!strcmp(*argv,"nopen")) {
107             ctrl |= PAM_NO_LOGIN;
108         } else if (!strcmp(*argv,"noenv")) {
109             ctrl |= PAM_NO_ENV;
110         } else if (!strcmp(*argv,"empty")) {
111             ctrl |= PAM_EMPTY_TOO;
112         } else {
113             pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
114         }
115     }
116
117     if ((*hashcount != 0) && !(ctrl & PAM_NEW_MAIL_DIR)) {
118         *maildir = DEFAULT_MAIL_DIRECTORY;
119         ctrl |= PAM_NEW_MAIL_DIR;
120     }
121
122     return ctrl;
123 }
124
125 static int
126 get_folder(pam_handle_t *pamh, int ctrl,
127            const char *path_mail, char **folder_p, size_t hashcount,
128            const struct passwd *pwd)
129 {
130     int retval;
131     const char *path;
132     char *folder = NULL;
133
134     if (ctrl & PAM_NEW_MAIL_DIR) {
135         path = path_mail;
136         if (*path == '~') {     /* support for $HOME delivery */
137             /*
138              * "~/xxx" and "~xxx" are treated as same
139              */
140             if (!*++path || (*path == '/' && !*++path)) {
141                 pam_syslog(pamh, LOG_ERR,
142                            "badly formed mail path [%s]", path_mail);
143                 retval = PAM_SERVICE_ERR;
144                 goto get_folder_cleanup;
145             }
146             ctrl |= PAM_HOME_MAIL;
147             if (hashcount != 0) {
148                 pam_syslog(pamh, LOG_ERR,
149                            "cannot do hash= and home directory mail");
150             }
151         }
152     } else {
153         path = DEFAULT_MAIL_DIRECTORY;
154     }
155
156     /* put folder together */
157
158     hashcount = hashcount < strlen(pwd->pw_name) ?
159       hashcount : strlen(pwd->pw_name);
160
161     retval = PAM_BUF_ERR;
162     if (ctrl & PAM_HOME_MAIL) {
163         if (asprintf(&folder, MAIL_FILE_FORMAT, pwd->pw_dir, "", path) < 0)
164             goto get_folder_cleanup;
165     } else {
166         int rc;
167         size_t i;
168         char *hash;
169
170         if ((hash = malloc(2 * hashcount + 1)) == NULL)
171             goto get_folder_cleanup;
172
173         for (i = 0; i < hashcount; i++) {
174             hash[2 * i] = '/';
175             hash[2 * i + 1] = pwd->pw_name[i];
176         }
177         hash[2 * i] = '\0';
178
179         rc = asprintf(&folder, MAIL_FILE_FORMAT, path, hash, pwd->pw_name);
180         _pam_overwrite(hash);
181         _pam_drop(hash);
182         if (rc < 0)
183             goto get_folder_cleanup;
184     }
185     D(("folder=[%s]", folder));
186     retval = PAM_SUCCESS;
187
188     /* tidy up */
189
190   get_folder_cleanup:
191     path = NULL;
192
193     *folder_p = folder;
194     folder = NULL;
195
196     if (retval == PAM_BUF_ERR)
197         pam_syslog(pamh, LOG_CRIT, "out of memory for mail folder");
198
199     return retval;
200 }
201
202 static int
203 get_mail_status(pam_handle_t *pamh, int ctrl, const char *folder)
204 {
205     int type = 0;
206     struct stat mail_st;
207
208     if (stat(folder, &mail_st) < 0)
209         return 0;
210
211     if (S_ISDIR(mail_st.st_mode)) {     /* Assume Maildir format */
212         int i, save_errno;
213         char *dir;
214         struct dirent **namelist;
215
216         if (asprintf(&dir, "%s/new", folder) < 0) {
217             pam_syslog(pamh, LOG_CRIT, "out of memory");
218             goto get_mail_status_cleanup;
219         }
220         i = scandir(dir, &namelist, 0, alphasort);
221         save_errno = errno;
222         _pam_overwrite(dir);
223         _pam_drop(dir);
224         if (i < 0) {
225             type = 0;
226             namelist = NULL;
227             if (save_errno == ENOMEM) {
228                 pam_syslog(pamh, LOG_CRIT, "out of memory");
229                 goto get_mail_status_cleanup;
230             }
231         }
232         type = (i > 2) ? HAVE_NEW_MAIL : 0;
233         while (--i >= 0)
234             _pam_drop(namelist[i]);
235         _pam_drop(namelist);
236         if (type == 0) {
237             if (asprintf(&dir, "%s/cur", folder) < 0) {
238                 pam_syslog(pamh, LOG_CRIT, "out of memory");
239                 goto get_mail_status_cleanup;
240             }
241             i = scandir(dir, &namelist, 0, alphasort);
242             save_errno = errno;
243             _pam_overwrite(dir);
244             _pam_drop(dir);
245             if (i < 0) {
246                 type = 0;
247                 namelist = NULL;
248                 if (save_errno == ENOMEM) {
249                     pam_syslog(pamh, LOG_CRIT, "out of memory");
250                     goto get_mail_status_cleanup;
251                 }
252             }
253             if (i > 2)
254                 type = HAVE_OLD_MAIL;
255             else
256                 type = (ctrl & PAM_EMPTY_TOO) ? HAVE_NO_MAIL : 0;
257             while (--i >= 0)
258                 _pam_drop(namelist[i]);
259             _pam_drop(namelist);
260         }
261     } else {
262         if (mail_st.st_size > 0) {
263             if (mail_st.st_atime < mail_st.st_mtime)    /* new */
264                 type = HAVE_NEW_MAIL;
265             else                /* old */
266                 type = (ctrl & PAM_STANDARD_MAIL) ? HAVE_MAIL : HAVE_OLD_MAIL;
267         } else if (ctrl & PAM_EMPTY_TOO) {
268             type = HAVE_NO_MAIL;
269         } else {
270             type = 0;
271         }
272     }
273
274   get_mail_status_cleanup:
275     memset(&mail_st, 0, sizeof(mail_st));
276     D(("user has %d mail in %s folder", type, folder));
277     return type;
278 }
279
280 static int
281 report_mail(pam_handle_t *pamh, int ctrl, int type, const char *folder)
282 {
283     int retval;
284
285     if ((ctrl & PAM_MAIL_SILENT) ||
286         ((ctrl & PAM_QUIET_MAIL) && type != HAVE_NEW_MAIL))
287       {
288         D(("keeping quiet"));
289         retval = PAM_SUCCESS;
290       }
291     else
292       {
293         if (ctrl & PAM_STANDARD_MAIL)
294           switch (type)
295             {
296             case HAVE_NO_MAIL:
297               retval = pam_info (pamh, "%s", _("No mail."));
298               break;
299             case HAVE_NEW_MAIL:
300               retval = pam_info (pamh, "%s", _("You have new mail."));
301               break;
302             case HAVE_OLD_MAIL:
303               retval = pam_info (pamh, "%s", _("You have old mail."));
304               break;
305             case HAVE_MAIL:
306             default:
307               retval = pam_info (pamh, "%s", _("You have mail."));
308               break;
309             }
310         else
311           switch (type)
312             {
313             case HAVE_NO_MAIL:
314               retval = pam_info (pamh, _("You have no mail in folder %s."),
315                                  folder);
316               break;
317             case HAVE_NEW_MAIL:
318               retval = pam_info (pamh, _("You have new mail in folder %s."),
319                                  folder);
320               break;
321             case HAVE_OLD_MAIL:
322               retval = pam_info (pamh, _("You have old mail in folder %s."),
323                                  folder);
324               break;
325             case HAVE_MAIL:
326             default:
327               retval = pam_info (pamh, _("You have mail in folder %s."),
328                                  folder);
329               break;
330             }
331       }
332
333     D(("returning %s", pam_strerror(pamh, retval)));
334     return retval;
335 }
336
337 static int _do_mail(pam_handle_t *, int, int, const char **, int);
338
339 /* --- authentication functions --- */
340
341 int
342 pam_sm_authenticate (pam_handle_t *pamh UNUSED, int flags UNUSED,
343                      int argc UNUSED, const char **argv UNUSED)
344 {
345     return PAM_IGNORE;
346 }
347
348 /* Checking mail as part of authentication */
349 int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
350     const char **argv)
351 {
352     if (!(flags & (PAM_ESTABLISH_CRED|PAM_DELETE_CRED)))
353       return PAM_IGNORE;
354     return _do_mail(pamh,flags,argc,argv,(flags & PAM_ESTABLISH_CRED));
355 }
356
357 /* --- session management functions --- */
358
359 int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc
360                          ,const char **argv)
361 {
362     return _do_mail(pamh,flags,argc,argv,0);
363 }
364
365 /* Checking mail as part of the session management */
366 int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
367     const char **argv)
368 {
369     return _do_mail(pamh,flags,argc,argv,1);
370 }
371
372
373 /* --- The Beaf (Tm) --- */
374
375 static int _do_mail(pam_handle_t *pamh, int flags, int argc,
376     const char **argv, int est)
377 {
378     int retval, ctrl, type;
379     size_t hashcount;
380     char *folder = NULL;
381     const char *user;
382     const char *path_mail = NULL;
383     const struct passwd *pwd = NULL;
384
385     /*
386      * this module (un)sets the MAIL environment variable, and checks if
387      * the user has any new mail.
388      */
389
390     ctrl = _pam_parse(pamh, flags, argc, argv, &path_mail, &hashcount);
391
392     retval = pam_get_user(pamh, &user, NULL);
393     if (retval != PAM_SUCCESS || user == NULL) {
394         pam_syslog(pamh, LOG_ERR, "cannot determine username");
395         return PAM_USER_UNKNOWN;
396     }
397
398     pwd = pam_modutil_getpwnam (pamh, user);
399     if (pwd == NULL) {
400         pam_syslog(pamh, LOG_ERR, "user unknown");
401         return PAM_USER_UNKNOWN;
402     }
403
404     /* which folder? */
405
406     retval = get_folder(pamh, ctrl, path_mail, &folder, hashcount, pwd);
407     if (retval != PAM_SUCCESS) {
408         D(("failed to find folder"));
409         return retval;
410     }
411
412     /* set the MAIL variable? */
413
414     if (!(ctrl & PAM_NO_ENV) && est) {
415         char *tmp;
416
417         if (asprintf(&tmp, MAIL_ENV_FORMAT, folder) < 0) {
418             pam_syslog(pamh, LOG_CRIT,
419                        "no memory for " MAIL_ENV_NAME " variable");
420             retval = PAM_BUF_ERR;
421             goto do_mail_cleanup;
422         }
423         D(("setting env: %s", tmp));
424         retval = pam_putenv(pamh, tmp);
425         _pam_overwrite(tmp);
426         _pam_drop(tmp);
427         if (retval != PAM_SUCCESS) {
428             pam_syslog(pamh, LOG_CRIT,
429                        "unable to set " MAIL_ENV_NAME " variable");
430             retval = PAM_BUF_ERR;
431             goto do_mail_cleanup;
432         }
433     } else {
434         D(("not setting " MAIL_ENV_NAME " variable"));
435     }
436
437     /*
438      * OK. we've got the mail folder... what about its status?
439      */
440
441     if ((est && !(ctrl & PAM_NO_LOGIN))
442         || (!est && (ctrl & PAM_LOGOUT_TOO))) {
443         PAM_MODUTIL_DEF_PRIVS(privs);
444
445         if (pam_modutil_drop_priv(pamh, &privs, pwd)) {
446           retval = PAM_SESSION_ERR;
447           goto do_mail_cleanup;
448         } else {
449           type = get_mail_status(pamh, ctrl, folder);
450           if (pam_modutil_regain_priv(pamh, &privs)) {
451             retval = PAM_SESSION_ERR;
452             goto do_mail_cleanup;
453           }
454         }
455
456         if (type != 0) {
457             retval = report_mail(pamh, ctrl, type, folder);
458             type = 0;
459         }
460     }
461
462     /* Delete environment variable? */
463     if ( ! est && ! (ctrl & PAM_NO_ENV) )
464         (void) pam_putenv(pamh, MAIL_ENV_NAME);
465
466   do_mail_cleanup:
467     _pam_overwrite(folder);
468     _pam_drop(folder);
469
470     /* indicate success or failure */
471
472     return retval;
473 }
474
475 /* end of module definition */