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