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