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