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