1 /* mailcheck.c -- The check is in the mail... */
2 
3 /* Copyright (C) 1987-2020 Free Software Foundation, Inc.
4 
5    This file is part of GNU Bash, the Bourne Again SHell.
6 
7    Bash is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation, either version 3 of the License, or
10    (at your option) any later version.
11 
12    Bash is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with Bash.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include "config.h"
22 
23 #include <stdio.h>
24 #include "bashtypes.h"
25 #include "posixstat.h"
26 #if defined (HAVE_SYS_PARAM_H)
27 #  include <sys/param.h>
28 #endif
29 #if defined (HAVE_UNISTD_H)
30 #  include <unistd.h>
31 #endif
32 #include "posixtime.h"
33 #include "bashansi.h"
34 #include "bashintl.h"
35 
36 #include "shell.h"
37 #include "execute_cmd.h"
38 #include "mailcheck.h"
39 #include <tilde/tilde.h>
40 
41 /* Values for flags word in struct _fileinfo */
42 #define MBOX_INITIALIZED	0x01
43 
44 extern time_t shell_start_time;
45 
46 extern int mailstat PARAMS((const char *, struct stat *));
47 
48 typedef struct _fileinfo {
49   char *name;
50   char *msg;
51   time_t access_time;
52   time_t mod_time;
53   off_t file_size;
54   int flags;
55 } FILEINFO;
56 
57 /* The list of remembered mail files. */
58 static FILEINFO **mailfiles = (FILEINFO **)NULL;
59 
60 /* Number of mail files that we have. */
61 static int mailfiles_count;
62 
63 /* The last known time that mail was checked. */
64 static time_t last_time_mail_checked = 0;
65 
66 /* Non-zero means warn if a mail file has been read since last checked. */
67 int mail_warning;
68 
69 static int find_mail_file PARAMS((char *));
70 static void init_mail_file PARAMS((int));
71 static void update_mail_file PARAMS((int));
72 static int add_mail_file PARAMS((char *, char *));
73 
74 static FILEINFO *alloc_mail_file PARAMS((char *, char *));
75 static void dispose_mail_file PARAMS((FILEINFO *));
76 
77 static int file_mod_date_changed PARAMS((int));
78 static int file_access_date_changed PARAMS((int));
79 static int file_has_grown PARAMS((int));
80 
81 static char *parse_mailpath_spec PARAMS((char *));
82 
83 /* Returns non-zero if it is time to check mail. */
84 int
time_to_check_mail()85 time_to_check_mail ()
86 {
87   char *temp;
88   time_t now;
89   intmax_t seconds;
90 
91   temp = get_string_value ("MAILCHECK");
92 
93   /* Negative number, or non-numbers (such as empty string) cause no
94      checking to take place. */
95   if (temp == 0 || legal_number (temp, &seconds) == 0 || seconds < 0)
96     return (0);
97 
98   now = NOW;
99   /* Time to check if MAILCHECK is explicitly set to zero, or if enough
100      time has passed since the last check. */
101   return (seconds == 0 || ((now - last_time_mail_checked) >= seconds));
102 }
103 
104 /* Okay, we have checked the mail.  Perhaps I should make this function
105    go away. */
106 void
reset_mail_timer()107 reset_mail_timer ()
108 {
109   last_time_mail_checked = NOW;
110 }
111 
112 /* Locate a file in the list.  Return index of
113    entry, or -1 if not found. */
114 static int
find_mail_file(file)115 find_mail_file (file)
116      char *file;
117 {
118   register int i;
119 
120   for (i = 0; i < mailfiles_count; i++)
121     if (STREQ (mailfiles[i]->name, file))
122       return i;
123 
124   return -1;
125 }
126 
127 #define RESET_MAIL_FILE(i) \
128   do \
129     { \
130       mailfiles[i]->access_time = mailfiles[i]->mod_time = 0; \
131       mailfiles[i]->file_size = 0; \
132       mailfiles[i]->flags = 0; \
133     } \
134   while (0)
135 
136 #define UPDATE_MAIL_FILE(i, finfo) \
137   do \
138     { \
139       mailfiles[i]->access_time = finfo.st_atime; \
140       mailfiles[i]->mod_time = finfo.st_mtime; \
141       mailfiles[i]->file_size = finfo.st_size; \
142       mailfiles[i]->flags |= MBOX_INITIALIZED; \
143     } \
144   while (0)
145 
146 static void
init_mail_file(i)147 init_mail_file (i)
148      int i;
149 {
150   mailfiles[i]->access_time = mailfiles[i]->mod_time = last_time_mail_checked ? last_time_mail_checked : shell_start_time;
151   mailfiles[i]->file_size = 0;
152   mailfiles[i]->flags = 0;
153 }
154 
155 static void
update_mail_file(i)156 update_mail_file (i)
157      int i;
158 {
159   char *file;
160   struct stat finfo;
161 
162   file = mailfiles[i]->name;
163   if (mailstat (file, &finfo) == 0)
164     UPDATE_MAIL_FILE (i, finfo);
165   else
166     RESET_MAIL_FILE (i);
167 }
168 
169 /* Add this file to the list of remembered files and return its index
170    in the list of mail files. */
171 static int
add_mail_file(file,msg)172 add_mail_file (file, msg)
173      char *file, *msg;
174 {
175   struct stat finfo;
176   char *filename;
177   int i;
178 
179   filename = full_pathname (file);
180   i = find_mail_file (filename);
181   if (i >= 0)
182     {
183       if (mailstat (filename, &finfo) == 0)
184 	UPDATE_MAIL_FILE (i, finfo);
185 
186       free (filename);
187       return i;
188     }
189 
190   i = mailfiles_count++;
191   mailfiles = (FILEINFO **)xrealloc
192 		(mailfiles, mailfiles_count * sizeof (FILEINFO *));
193 
194   mailfiles[i] = alloc_mail_file (filename, msg);
195   init_mail_file (i);
196 
197   return i;
198 }
199 
200 /* Reset the existing mail files access and modification times to zero. */
201 void
reset_mail_files()202 reset_mail_files ()
203 {
204   register int i;
205 
206   for (i = 0; i < mailfiles_count; i++)
207     RESET_MAIL_FILE (i);
208 }
209 
210 static FILEINFO *
alloc_mail_file(filename,msg)211 alloc_mail_file (filename, msg)
212      char *filename, *msg;
213 {
214   FILEINFO *mf;
215 
216   mf = (FILEINFO *)xmalloc (sizeof (FILEINFO));
217   mf->name = filename;
218   mf->msg = msg ? savestring (msg) : (char *)NULL;
219   mf->flags = 0;
220 
221   return mf;
222 }
223 
224 static void
dispose_mail_file(mf)225 dispose_mail_file (mf)
226      FILEINFO *mf;
227 {
228   free (mf->name);
229   FREE (mf->msg);
230   free (mf);
231 }
232 
233 /* Free the information that we have about the remembered mail files. */
234 void
free_mail_files()235 free_mail_files ()
236 {
237   register int i;
238 
239   for (i = 0; i < mailfiles_count; i++)
240     dispose_mail_file (mailfiles[i]);
241 
242   if (mailfiles)
243     free (mailfiles);
244 
245   mailfiles_count = 0;
246   mailfiles = (FILEINFO **)NULL;
247 }
248 
249 void
init_mail_dates()250 init_mail_dates ()
251 {
252   if (mailfiles == 0)
253     remember_mail_dates ();
254 }
255 
256 /* Return non-zero if FILE's mod date has changed and it has not been
257    accessed since modified.  If the size has dropped to zero, reset
258    the cached mail file info. */
259 static int
file_mod_date_changed(i)260 file_mod_date_changed (i)
261      int i;
262 {
263   time_t mtime;
264   struct stat finfo;
265   char *file;
266 
267   file = mailfiles[i]->name;
268   mtime = mailfiles[i]->mod_time;
269 
270   if (mailstat (file, &finfo) != 0)
271     return (0);
272 
273   if (finfo.st_size > 0)
274     return (mtime < finfo.st_mtime);
275 
276   if (finfo.st_size == 0 && mailfiles[i]->file_size > 0)
277     UPDATE_MAIL_FILE (i, finfo);
278 
279   return (0);
280 }
281 
282 /* Return non-zero if FILE's access date has changed. */
283 static int
file_access_date_changed(i)284 file_access_date_changed (i)
285      int i;
286 {
287   time_t atime;
288   struct stat finfo;
289   char *file;
290 
291   file = mailfiles[i]->name;
292   atime = mailfiles[i]->access_time;
293 
294   if (mailstat (file, &finfo) != 0)
295     return (0);
296 
297   if (finfo.st_size > 0)
298     return (atime < finfo.st_atime);
299 
300   return (0);
301 }
302 
303 /* Return non-zero if FILE's size has increased. */
304 static int
file_has_grown(i)305 file_has_grown (i)
306      int i;
307 {
308   off_t size;
309   struct stat finfo;
310   char *file;
311 
312   file = mailfiles[i]->name;
313   size = mailfiles[i]->file_size;
314 
315   return ((mailstat (file, &finfo) == 0) && (finfo.st_size > size));
316 }
317 
318 /* Take an element from $MAILPATH and return the portion from
319    the first unquoted `?' or `%' to the end of the string.  This is the
320    message to be printed when the file contents change. */
321 static char *
parse_mailpath_spec(str)322 parse_mailpath_spec (str)
323      char *str;
324 {
325   char *s;
326   int pass_next;
327 
328   for (s = str, pass_next = 0; s && *s; s++)
329     {
330       if (pass_next)
331 	{
332 	  pass_next = 0;
333 	  continue;
334 	}
335       if (*s == '\\')
336 	{
337 	  pass_next++;
338 	  continue;
339 	}
340       if (*s == '?' || *s == '%')
341 	return s;
342     }
343   return ((char *)NULL);
344 }
345 
346 char *
make_default_mailpath()347 make_default_mailpath ()
348 {
349 #if defined (DEFAULT_MAIL_DIRECTORY)
350   char *mp;
351 
352   get_current_user_info ();
353   mp = (char *)xmalloc (2 + sizeof (DEFAULT_MAIL_DIRECTORY) + strlen (current_user.user_name));
354   strcpy (mp, DEFAULT_MAIL_DIRECTORY);
355   mp[sizeof(DEFAULT_MAIL_DIRECTORY) - 1] = '/';
356   strcpy (mp + sizeof (DEFAULT_MAIL_DIRECTORY), current_user.user_name);
357   return (mp);
358 #else
359   return ((char *)NULL);
360 #endif
361 }
362 
363 /* Remember the dates of the files specified by MAILPATH, or if there is
364    no MAILPATH, by the file specified in MAIL.  If neither exists, use a
365    default value, which we randomly concoct from using Unix. */
366 
367 void
remember_mail_dates()368 remember_mail_dates ()
369 {
370   char *mailpaths;
371   char *mailfile, *mp;
372   int i = 0;
373 
374   mailpaths = get_string_value ("MAILPATH");
375 
376   /* If no $MAILPATH, but $MAIL, use that as a single filename to check. */
377   if (mailpaths == 0 && (mailpaths = get_string_value ("MAIL")))
378     {
379       add_mail_file (mailpaths, (char *)NULL);
380       return;
381     }
382 
383   if (mailpaths == 0)
384     {
385       mailpaths = make_default_mailpath ();
386       if (mailpaths)
387 	{
388 	  add_mail_file (mailpaths, (char *)NULL);
389 	  free (mailpaths);
390 	}
391       return;
392     }
393 
394   while (mailfile = extract_colon_unit (mailpaths, &i))
395     {
396       mp = parse_mailpath_spec (mailfile);
397       if (mp && *mp)
398 	*mp++ = '\0';
399       add_mail_file (mailfile, mp);
400       free (mailfile);
401     }
402 }
403 
404 /* check_mail () is useful for more than just checking mail.  Since it has
405    the paranoids dream ability of telling you when someone has read your
406    mail, it can just as easily be used to tell you when someones .profile
407    file has been read, thus letting one know when someone else has logged
408    in.  Pretty good, huh? */
409 
410 /* Check for mail in some files.  If the modification date of any
411    of the files in MAILPATH has changed since we last did a
412    remember_mail_dates () then mention that the user has mail.
413    Special hack:  If the variable MAIL_WARNING is non-zero and the
414    mail file has been accessed since the last time we remembered, then
415    the message "The mail in <mailfile> has been read" is printed. */
416 void
check_mail()417 check_mail ()
418 {
419   char *current_mail_file, *message;
420   int i, use_user_notification;
421   char *dollar_underscore, *temp;
422 
423   dollar_underscore = get_string_value ("_");
424   if (dollar_underscore)
425     dollar_underscore = savestring (dollar_underscore);
426 
427   for (i = 0; i < mailfiles_count; i++)
428     {
429       current_mail_file = mailfiles[i]->name;
430 
431       if (*current_mail_file == '\0')
432 	continue;
433 
434       if (file_mod_date_changed (i))
435 	{
436 	  int file_is_bigger;
437 
438 	  use_user_notification = mailfiles[i]->msg != (char *)NULL;
439 	  message = mailfiles[i]->msg ? mailfiles[i]->msg : _("You have mail in $_");
440 
441 	  bind_variable ("_", current_mail_file, 0);
442 
443 #define atime mailfiles[i]->access_time
444 #define mtime mailfiles[i]->mod_time
445 
446 	  /* Have to compute this before the call to update_mail_file, which
447 	     resets all the information. */
448 	  file_is_bigger = file_has_grown (i);
449 
450 	  update_mail_file (i);
451 
452 	  /* If the user has just run a program which manipulates the
453 	     mail file, then don't bother explaining that the mail
454 	     file has been manipulated.  Since some systems don't change
455 	     the access time to be equal to the modification time when
456 	     the mail in the file is manipulated, check the size also.  If
457 	     the file has not grown, continue. */
458 	  if ((atime >= mtime) && !file_is_bigger)
459 	    continue;
460 
461 	  /* If the mod time is later than the access time and the file
462 	     has grown, note the fact that this is *new* mail. */
463 	  if (use_user_notification == 0 && (atime < mtime) && file_is_bigger)
464 	    message = _("You have new mail in $_");
465 #undef atime
466 #undef mtime
467 
468 	  if (temp = expand_string_to_string (message, Q_DOUBLE_QUOTES))
469 	    {
470 	      puts (temp);
471 	      free (temp);
472 	    }
473 	  else
474 	    putchar ('\n');
475 	}
476 
477       if (mail_warning && file_access_date_changed (i))
478 	{
479 	  update_mail_file (i);
480 	  printf (_("The mail in %s has been read\n"), current_mail_file);
481 	}
482     }
483 
484   if (dollar_underscore)
485     {
486       bind_variable ("_", dollar_underscore, 0);
487       free (dollar_underscore);
488     }
489   else
490     unbind_variable ("_");
491 }
492