ecb0c54d859bc12016a31eda3092fe70cbc1914f
[infodrom/newmail] / mbox.c
1 /*
2     Copyright (c) 2004  Joey Schulze <joey@infodrom.org>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <stdio.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23 #include <utime.h>
24 #include <string.h>
25 #include <strings.h>
26 #include <ctype.h>
27 #include "output.h"
28
29 /* #define FROM_DETECTION */
30
31 struct mail {
32   char from[128];
33   char subject[128];
34   int priority;
35   off_t size;
36 };
37
38 #define HDR_LEN 1024
39 #define TAB     0x09
40 #define LWSP    0x20
41
42 /*
43  * Like strncpy() but with terminated result.
44  */
45 char *stringcopy(char *dest, const char *src, size_t n)
46 {
47   strncpy (dest, src, n-1);
48   dest[n-1] = '\0';
49   return dest;
50 }
51
52 /*
53  * Strips balanced quotes in the middle of the realname
54  */
55 char *strip_quotes(char *name)
56 {
57   static char realname[HDR_LEN];
58   char *cpl, *cpr;
59   char *cp, *xp;
60
61   if ((cpl = index(name, '"')) != NULL && (cpr = index(cpl+1, '"')) != NULL) {
62     for (cp=name,xp=realname; *cp && xp < realname+sizeof(realname)-1; cp++) {
63       if (cp != cpl && cp != cpr)
64         *xp++ = *cp;
65     }
66     *xp = '\0';
67     return realname;
68   } else
69     return name;
70 }
71
72 /*
73  * Extract the realname from a mail address
74  *
75  * From: "Log Kristian Koehntopp" <joey@infodrom.org>
76  * From: "Jérôme" ATHIAS <jerome@athias.fr>
77  * From: frank@kuesterei.ch (=?iso-8859-1?q?Frank_K=FCster?=)
78  * From: root@luonnotar.infodrom.org (Cron Daemon)
79  * From: <JoMaBusch@web.de>
80  * From: root@luonnotar.infodrom.org
81 */
82 char *realname(char *from)
83 {
84   static char name[HDR_LEN];
85   char *cpl, *cpr;
86
87   name[0] = '\0';
88
89   /* From: REALNAME <login@host.domain> */
90   if ((cpr = index(from, '<')) != NULL && index(from, '>') != NULL) {
91     if (cpr > from) cpr--;
92
93     /* Strip trailing spaces */
94     while (cpr > from && isspace(*cpr)) cpr--;
95
96     /* Strip leading spaces */
97     cpl=from;while (*cpl && isspace(*cpl)) cpl++;
98
99     /* Strip balanced surrounding quotes */
100     if (*cpl == '"' && *cpr == '"') { cpl++;cpr--; }
101
102     if (cpr > cpl) {
103       stringcopy (name, cpl,
104                   sizeof(name) < cpr-cpl+2?sizeof(name):cpr-cpl+2);
105
106       if (index(name, '"') != NULL)
107         stringcopy (name, strip_quotes(name), sizeof(name));
108     } else {
109       /* Apparently no realname included */
110       cpl = index(from, '<');
111       cpr = index(from, '>');
112       stringcopy (name, cpl+1,
113                   sizeof(name) < cpr-cpl?sizeof(name):cpr-cpl);
114     }
115
116   /* From: login@host.domain (REALNAME) */
117   } else if ((cpl = index(from, '(')) != NULL && (cpr = index(from, ')')) != NULL) {
118     stringcopy (name, cpl+1,
119                 sizeof(name) < cpr-cpl?sizeof(name):cpr-cpl);
120
121   /* From: login@host.domain */
122   } else {
123     /* Strip leading spaces */
124     cpl=from;while (*cpl && isspace(*cpl)) cpl++;
125     for (cpr=cpl; *cpr && !isspace(*cpr); cpr++);
126     stringcopy (name, cpl,
127                 sizeof(name) < cpr-cpl+1?sizeof(name):cpr-cpl+1);
128   }
129
130   return name;
131 }
132
133 /*
134  * Tries to extract useful content from the From_ line
135  */
136 char *reduce_from_(char *from_)
137 {
138   static char name[HDR_LEN];
139   char *cpl, *cpr;
140
141   for (cpl=from_; *cpl &&  isspace(*cpl); cpl++);
142   for (cpr=cpl;   *cpr && !isspace(*cpr); cpr++);
143
144   if (cpr > cpl)
145     stringcopy (name, cpl, 
146                 sizeof(name) < cpr-cpl+1?sizeof(name):cpr-cpl+1);
147   return name;
148 }
149
150 int inspect_mbox(char *path, char *prefix, off_t size, int opt_flags)
151 {
152   FILE *f;
153   char buf[HDR_LEN];
154   char tmp[512];
155   char *cp;
156   int inheader = 1;
157   int readnewline = 0;
158   int newmail = 0;
159   int lookahead;
160
161   char from_[HDR_LEN] = "";
162   char from[HDR_LEN] = "";
163   char realfrom[HDR_LEN] = "";
164 #ifdef FROM_DETECTION
165   char to[HDR_LEN] = "";
166 #endif
167   char subject[HDR_LEN] = "";
168   int priority = 0;
169
170   if ((f = fopen(path, "r")) == NULL)
171     return 0;
172
173   if (size > 0 && fseek(f, size, SEEK_SET) != 0)
174     return 0;
175
176   while (!feof(f)) {
177     if ((cp = fgets(buf, sizeof(buf), f)) == NULL)
178       continue;
179     if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n') {
180       buf[strlen(buf)-1] = '\0';
181       if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\r')
182         buf[strlen(buf)-1] = '\0';
183
184       if (inheader && !feof(f)) {
185         lookahead = fgetc(f);
186         if (lookahead == TAB || lookahead == LWSP) {
187           if (buf[strlen(buf)-1] != LWSP)
188             strncat (buf, " ", sizeof(buf)-strlen(buf)-1);
189           if ((cp = fgets(tmp, sizeof(tmp), f)) != NULL) {
190             strncat (buf, tmp, sizeof(buf)-strlen(buf)-1);
191             if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
192               buf[strlen(buf)-1] = '\0';
193             if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\r')
194               buf[strlen(buf)-1] = '\0';
195
196             if (strlen(tmp) > 0 && buf[strlen(tmp)-1] == '\n') {
197               /* Read the remainder */
198               while (!feof(f) && fgets(tmp, sizeof(tmp), f) != NULL) {
199                 if (strlen(tmp) > 0 && tmp[strlen(tmp)-1] == '\n')
200                   break;
201               }
202             }
203           }
204         } else
205           /* Rewind by one character for next read */
206           if (lookahead != EOF)
207             fseek(f, -1, SEEK_CUR);
208       }
209     } else {
210       /* Read the remainder */
211       while (!feof(f) && fgets(tmp, sizeof(tmp), f) != NULL) {
212         if (strlen(tmp) > 0 && tmp[strlen(tmp)-1] == '\n')
213           break;
214       }
215     }
216
217     if (inheader) {
218       if (strlen(buf) == 0) {
219         inheader = 0;
220         newmail = 1;
221
222         if (strlen(from))
223           stringcopy (realfrom, realname(from), sizeof(realfrom));
224         else if (strlen(from_))
225           stringcopy (realfrom, reduce_from_(from_), sizeof(realfrom));
226         else
227           realfrom[0] = '\0';
228
229         if (realfrom[0] != '\0')
230           emit (prefix, realfrom, subject, priority, opt_flags);
231
232 #ifdef FROM_DETECTION
233         from_[0] = from[0] = to[0] = subject[0] = '\0';
234 #else
235         from_[0] = from[0] = subject[0] = '\0';
236 #endif
237         priority = 0;
238       } else {
239         if (strncasecmp(buf, "From ", 5) == 0)
240           stringcopy (from_, buf+5, sizeof(from_));
241         else if (strncasecmp(buf, "From: ", 6) == 0)
242           stringcopy (from, buf+6, sizeof(from));
243 #ifdef FROM_DETECTION
244         else if (strncasecmp(buf, "To: ", 4) == 0)
245           stringcopy (to, buf+4, sizeof(to));
246 #endif
247         else if (strncasecmp(buf, "Subject: ", 9) == 0)
248           stringcopy (subject, buf+9, sizeof(subject));
249         else if (strncasecmp(buf, "Priority: urgent", 16) == 0 && buf[16] == '\0')
250           priority = 1;
251         else if (strncasecmp(buf, "X-Priority: 1", 13) == 0 && buf[13] == '\0')
252           priority = 1;
253       }
254     } else if (strlen(buf) == 0) {
255       readnewline = 1;
256     } else if (readnewline && strncasecmp(buf, "From ", 5) == 0) {
257       inheader = 1;
258       readnewline = 0;
259       stringcopy (from_, buf+5, sizeof(from_));
260     } else
261       readnewline = 0;
262   }
263
264   fclose(f);
265
266   return newmail;
267 }
268
269 int watch_mbox(char *path, char *prefix, off_t *size, int opt_flags)
270 {
271   struct stat st;
272   struct utimbuf timbuf;
273   int newmail = 0;
274
275   if (stat(path, &st) == 0) {
276     if (st.st_size > *size)
277       if (access(path, R_OK) == 0) {
278         timbuf.actime = st.st_atime;
279         timbuf.modtime = st.st_mtime;
280
281         newmail = inspect_mbox(path, prefix, *size, opt_flags);
282
283         utime(path, &timbuf);
284       }
285     *size = st.st_size;
286     return newmail;
287   } else {
288     *size = 0;
289   }
290
291   return 0;
292 }