Only detect ^From_ and not '^from_' by accident
[infodrom/newmail] / mbox.c
1 /*
2     Copyright (c) 2004,8  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 #define HDR_LEN 1024
32
33 struct mail {
34   char from[HDR_LEN];
35   char subject[HDR_LEN];
36   int priority;
37   off_t size;
38 };
39
40 #define TAB     0x09
41 #define LWSP    0x20
42
43 /*
44  * Like strncpy() but with terminated result.
45  */
46 char *stringcopy(char *dest, const char *src, size_t n)
47 {
48   strncpy (dest, src, n-1);
49   dest[n-1] = '\0';
50   return dest;
51 }
52
53 /*
54  * Strips balanced quotes in the middle of the realname
55  */
56 char *strip_quotes(char *name)
57 {
58   static char realname[HDR_LEN];
59   char *cpl, *cpr;
60   char *cp, *xp;
61
62   if ((cpl = index(name, '"')) != NULL && (cpr = index(cpl+1, '"')) != NULL) {
63     for (cp=name,xp=realname; *cp && xp < realname+sizeof(realname)-1; cp++) {
64       if (cp != cpl && cp != cpr)
65         *xp++ = *cp;
66     }
67     *xp = '\0';
68     return realname;
69   } else
70     return name;
71 }
72
73 /*
74  * Extract the realname from a mail address
75  *
76  * From: "Log Kristian Koehntopp" <joey@infodrom.org>
77  * From: "Jérôme" ATHIAS <jerome@athias.fr>
78  * From: frank@kuesterei.ch (=?iso-8859-1?q?Frank_K=FCster?=)
79  * From: root@luonnotar.infodrom.org (Cron Daemon)
80  * From: <JoMaBusch@web.de>
81  * From: root@luonnotar.infodrom.org
82 */
83 char *realname(char *from)
84 {
85   static char name[HDR_LEN];
86   char *cpl, *cpr;
87
88   name[0] = '\0';
89
90   /* From: REALNAME <login@host.domain> */
91   if ((cpr = index(from, '<')) != NULL && index(from, '>') != NULL) {
92     if (cpr > from) cpr--;
93
94     /* Strip trailing spaces */
95     while (cpr > from && isspace(*cpr)) cpr--;
96
97     /* Strip leading spaces */
98     cpl=from;while (*cpl && isspace(*cpl)) cpl++;
99
100     /* Strip balanced surrounding quotes */
101     if (*cpl == '"' && *cpr == '"') { cpl++;cpr--; }
102
103     if (cpr > cpl) {
104       stringcopy (name, cpl,
105                   sizeof(name) < cpr-cpl+2?sizeof(name):cpr-cpl+2);
106
107       if (index(name, '"') != NULL)
108         stringcopy (name, strip_quotes(name), sizeof(name));
109     } else {
110       /* Apparently no realname included */
111       cpl = index(from, '<');
112       cpr = index(from, '>');
113       stringcopy (name, cpl+1,
114                   sizeof(name) < cpr-cpl?sizeof(name):cpr-cpl);
115     }
116
117   /* From: login@host.domain (REALNAME) */
118   } else if ((cpl = index(from, '(')) != NULL && (cpr = index(from, ')')) != NULL) {
119     stringcopy (name, cpl+1,
120                 sizeof(name) < cpr-cpl?sizeof(name):cpr-cpl);
121
122   /* From: login@host.domain */
123   } else {
124     /* Strip leading spaces */
125     cpl=from;while (*cpl && isspace(*cpl)) cpl++;
126     for (cpr=cpl; *cpr && !isspace(*cpr); cpr++);
127     stringcopy (name, cpl,
128                 sizeof(name) < cpr-cpl+1?sizeof(name):cpr-cpl+1);
129   }
130
131   return name;
132 }
133
134 /*
135  * Tries to extract useful content from the From_ line
136  */
137 char *reduce_from_(char *from_)
138 {
139   static char name[HDR_LEN];
140   char *cpl, *cpr;
141
142   for (cpl=from_; *cpl &&  isspace(*cpl); cpl++);
143   for (cpr=cpl;   *cpr && !isspace(*cpr); cpr++);
144
145   if (cpr > cpl)
146     stringcopy (name, cpl, 
147                 sizeof(name) < cpr-cpl+1?sizeof(name):cpr-cpl+1);
148   return name;
149 }
150
151 int inspect_mbox(char *path, char *prefix, off_t size, int opt_flags)
152 {
153   FILE *f;
154   char buf[HDR_LEN];
155   char tmp[512];
156   char *cp;
157   int inheader = 1;
158   int readnewline = 0;
159   int newmail = 0;
160   int lookahead;
161
162   char from_[HDR_LEN] = "";
163   char from[HDR_LEN] = "";
164   char realfrom[HDR_LEN] = "";
165 #ifdef FROM_DETECTION
166   char to[HDR_LEN] = "";
167 #endif
168   char subject[HDR_LEN] = "";
169   int priority = 0;
170
171   if ((f = fopen(path, "r")) == NULL)
172     return 0;
173
174   if (size > 0 && fseek(f, size, SEEK_SET) != 0)
175     return 0;
176
177   while (!feof(f)) {
178     if ((cp = fgets(buf, sizeof(buf), f)) == NULL)
179       continue;
180     if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n') {
181       buf[strlen(buf)-1] = '\0';
182       if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\r')
183         buf[strlen(buf)-1] = '\0';
184
185       if (inheader && !feof(f)) {
186         while ((lookahead=fgetc(f)) == 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         }
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
221         if (strlen(from))
222           stringcopy (realfrom, realname(from), sizeof(realfrom));
223         else if (strlen(from_))
224           stringcopy (realfrom, reduce_from_(from_), sizeof(realfrom));
225         else
226           realfrom[0] = '\0';
227
228         if (realfrom[0] != '\0' || subject[0] != '\0') {
229           emit (prefix, realfrom, subject, priority, opt_flags);
230           newmail = 1;
231         }
232
233 #ifdef FROM_DETECTION
234         from_[0] = from[0] = to[0] = subject[0] = '\0';
235 #else
236         from_[0] = from[0] = subject[0] = '\0';
237 #endif
238         priority = 0;
239       } else {
240         if (strncmp(buf, "From ", 5) == 0)
241           stringcopy (from_, buf+5, sizeof(from_));
242         else if (strncasecmp(buf, "From: ", 6) == 0)
243           stringcopy (from, buf+6, sizeof(from));
244 #ifdef FROM_DETECTION
245         else if (strncasecmp(buf, "To: ", 4) == 0)
246           stringcopy (to, buf+4, sizeof(to));
247 #endif
248         else if (strncasecmp(buf, "Subject: ", 9) == 0)
249           stringcopy (subject, buf+9, sizeof(subject));
250         else if (strncasecmp(buf, "Priority: urgent", 16) == 0 && buf[16] == '\0')
251           priority = 1;
252         else if (strncasecmp(buf, "X-Priority: 1", 13) == 0 && buf[13] == '\0')
253           priority = 1;
254       }
255     } else if (strlen(buf) == 0) {
256       readnewline = 1;
257     } else if (readnewline && strncasecmp(buf, "From ", 5) == 0) {
258       inheader = 1;
259       readnewline = 0;
260       stringcopy (from_, buf+5, sizeof(from_));
261     } else
262       readnewline = 0;
263   }
264
265   fclose(f);
266
267   return newmail;
268 }
269
270 int watch_mbox(char *path, char *prefix, off_t *size, int opt_flags)
271 {
272   struct stat st;
273   struct utimbuf timbuf;
274   int newmail = 0;
275
276   if (stat(path, &st) == 0) {
277     if (st.st_size > *size)
278       if (access(path, R_OK) == 0) {
279         timbuf.actime = st.st_atime;
280         timbuf.modtime = st.st_mtime;
281
282         newmail = inspect_mbox(path, prefix, *size, opt_flags);
283
284         utime(path, &timbuf);
285       }
286     *size = st.st_size;
287     return newmail;
288   } else {
289     *size = 0;
290   }
291
292   return 0;
293 }