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