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