1 /*
2 * Mailbox access.
3 */
4
5 #include <stdio.h>
6 #include <unistd.h>
7 #include <fcntl.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <signal.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13 #include <time.h>
14 #include <errno.h>
15
16 #include "md5/md5.h"
17
18 #include "misc.h"
19 #include "params.h"
20 #include "protocol.h"
21 #include "database.h"
22
23 static int mailbox_fd; /* fd for the mailbox, or -1 */
24 static time_t mailbox_mtime; /* mtime, as of the last check */
25 static unsigned long mailbox_size; /* Its original size */
26
27 static struct db_message *cmp;
28
29 /*
30 * If a message has changed since the database was filled in, then we
31 * consider the database stale. This is called for every message when
32 * the mailbox is being re-parsed (because of its mtime change).
33 */
db_compare(struct db_message * msg)34 static int db_compare(struct db_message *msg)
35 {
36 if (!cmp) return 1;
37
38 if (msg->raw_size != cmp->raw_size || msg->size != cmp->size ||
39 memcmp(msg->hash, cmp->hash, sizeof(msg->hash))) {
40 db.flags |= DB_STALE;
41 return 1;
42 }
43
44 cmp = cmp->next;
45
46 return 0;
47 }
48
49 /*
50 * Checks if the buffer pointed to by s1, of n1 chars, starts with the
51 * string s2, of n2 chars.
52 */
53 #ifdef __GNUC__
54 __inline__
55 #endif
eq(char * s1,int n1,char * s2,int n2)56 static int eq(char *s1, int n1, char *s2, int n2)
57 {
58 if (n1 < n2) return 0;
59 if (!memcmp(s1, s2, n2)) return 1;
60 return !strncasecmp(s1, s2, n2);
61 }
62
63 /*
64 * The mailbox parsing routine: first called to fill the database in,
65 * then to check if the database is still up to date. We implement a
66 * state machine at the line fragment level (that is, full or partial
67 * lines). This is faster than dealing with individual characters (we
68 * leave that job for libc), and doesn't require ever loading entire
69 * lines into memory.
70 */
mailbox_parse(int init)71 static int mailbox_parse(int init)
72 {
73 struct stat stat; /* File information */
74 struct db_message msg; /* Message being parsed */
75 MD5_CTX hash; /* Its hash being computed */
76 int (*db_op)(struct db_message *msg); /* db_add or db_compare */
77 char *file_buffer, *line_buffer; /* Our internal buffers */
78 unsigned long file_offset, line_offset; /* Their offsets in the file */
79 unsigned long offset; /* A line fragment's offset */
80 char *current, *next, *line; /* Line pointers */
81 int block, saved, extra, length; /* Internal block sizes */
82 int done, start, end; /* Various boolean flags: */
83 int blank, header, body; /* the state information */
84 int fixed, received; /* ...and more of it */
85
86 if (fstat(mailbox_fd, &stat)) return 1;
87
88 if (init) {
89 /* Prepare for the database initialization */
90 if (!S_ISREG(stat.st_mode)) return 1;
91 mailbox_mtime = stat.st_mtime;
92 if (stat.st_size > MAX_MAILBOX_OPEN_BYTES ||
93 stat.st_size > ~0UL) return 1;
94 mailbox_size = stat.st_size;
95 if (!mailbox_size) return 0;
96 db_op = db_add;
97 } else {
98 /* Prepare for checking against the database */
99 if (mailbox_mtime == stat.st_mtime) return 0;
100 if (!mailbox_size) return 0;
101 if (stat.st_size < mailbox_size) {
102 db.flags |= DB_STALE;
103 return 1;
104 }
105 if (stat.st_size > MAX_MAILBOX_WORK_BYTES ||
106 stat.st_size > ~0UL) return 1;
107 if (lseek(mailbox_fd, 0, SEEK_SET) < 0) return 1;
108 db_op = db_compare; cmp = db.head;
109 }
110
111 memset(&msg, 0, sizeof(msg));
112 MD5_Init(&hash);
113
114 file_buffer = malloc(FILE_BUFFER_SIZE + LINE_BUFFER_SIZE);
115 if (!file_buffer) return 1;
116 line_buffer = &file_buffer[FILE_BUFFER_SIZE];
117
118 file_offset = 0; line_offset = 0; offset = 0; /* Start at 0, with */
119 current = file_buffer; block = 0; saved = 0; /* empty buffers */
120
121 done = 0; /* Haven't reached EOF or the original size yet */
122 end = 1; /* Assume we've just seen a LF: parse a new line */
123 blank = 1; /* Assume we've seen a blank line: look for "From " */
124 header = 0; /* Not in message headers, */
125 body = 0; /* and not in message body */
126 fixed = 0; /* Not in a "fixed" part of a message, */
127 received = 0; /* and haven't got a Received: header yet */
128
129 /*
130 * The main loop. Its first part extracts the line fragments, while the
131 * second one manages the state flags and performs whatever is required
132 * based on the state. Unfortunately, splitting this into two functions
133 * didn't seem to simplify the code.
134 */
135 do {
136 /*
137 * Part 1.
138 * The line fragment extraction.
139 */
140
141 /* Look for the next LF in the file buffer */
142 if ((next = memchr(current, '\n', block))) {
143 /* Found it: get the length of this piece, and check for buffered data */
144 length = ++next - current;
145 if (saved) {
146 /* Have this line's beginning in the line buffer: combine them */
147 extra = LINE_BUFFER_SIZE - saved;
148 if (extra > length) extra = length;
149 memcpy(&line_buffer[saved], current, extra);
150 current += extra; block -= extra;
151 length = saved + extra;
152 line = line_buffer;
153 offset = line_offset;
154 start = end; end = current == next;
155 saved = 0;
156 } else {
157 /* Nothing in the line buffer: just process what we've got now */
158 line = current;
159 offset = file_offset - block;
160 start = end; end = 1;
161 current = next; block -= length;
162 }
163 } else {
164 /* No more LFs in the file buffer */
165 if (saved || block <= LINE_BUFFER_SIZE) {
166 /* Have this line's beginning in the line buffer: combine them */
167 /* Not enough data to process right now: buffer it */
168 extra = LINE_BUFFER_SIZE - saved;
169 if (extra > block) extra = block;
170 if (!saved) line_offset = file_offset - block;
171 memcpy(&line_buffer[saved], current, extra);
172 current += extra; block -= extra;
173 saved += extra;
174 length = saved;
175 line = line_buffer;
176 offset = line_offset;
177 } else {
178 /* Nothing in the line buffer and we've got enough data: just process it */
179 length = block - 1;
180 line = current;
181 offset = file_offset - block;
182 current += length;
183 block = 1;
184 }
185 if (!block) {
186 /* We've emptied the file buffer: fetch some more data */
187 current = file_buffer;
188 block = FILE_BUFFER_SIZE;
189 if (!init &&
190 block > mailbox_size - file_offset)
191 block = mailbox_size - file_offset;
192 block = read(mailbox_fd, file_buffer, block);
193 if (block < 0) break;
194 file_offset += block;
195 if (block > 0 && saved < LINE_BUFFER_SIZE)
196 continue;
197 if (!saved) {
198 /* Nothing in the line buffer, and read(2) returned 0: we're done */
199 offset = file_offset;
200 done = 1;
201 break;
202 }
203 }
204 start = end; end = !block;
205 saved = 0;
206 }
207
208 /*
209 * Part 2.
210 * The following variables are set when we get here:
211 * -- line the line fragment, not NUL terminated;
212 * -- length its length;
213 * -- offset its offset in the file;
214 * -- start whether it's at the start of the line;
215 * -- end whether it's at the end of the line
216 * (all four combinations of "start" and "end" are possible).
217 */
218
219 /* Check for a new message if we've just seen a blank line */
220 if (blank && start)
221 if (line[0] == 'F' && length >= 5 &&
222 line[1] == 'r' && line[2] == 'o' && line[3] == 'm' &&
223 line[4] == ' ') {
224 /* Process the previous one first, if exists */
225 if (offset) {
226 /* If we aren't at the very beginning, there must have been a message */
227 if (!msg.data_offset) break;
228 msg.raw_size = offset - msg.raw_offset;
229 msg.data_size = offset - body - msg.data_offset;
230 msg.size -= body << 1;
231 MD5_Final(msg.hash, &hash);
232 if (db_op(&msg)) break;
233 }
234 /* Now prepare for parsing the new one */
235 msg.raw_offset = offset;
236 msg.data_offset = 0;
237 MD5_Init(&hash);
238 header = 1; body = 0;
239 fixed = received = 0;
240 continue;
241 }
242
243 /* Memorize file offset of the message data (the line next to "From ") */
244 if (header && start && !msg.data_offset) {
245 msg.data_offset = offset;
246 msg.data_size = 0;
247 msg.size = 0;
248 }
249
250 /* Count this fragment, with LFs as CRLF, into the message size */
251 if (msg.data_offset)
252 msg.size += length + end;
253
254 /* If we see LF at start of line, then this is a blank line :-) */
255 blank = start && line[0] == '\n';
256
257 if (!header) {
258 /* If we're no longer in message headers and we see more data, then it's
259 * the body. */
260 if (msg.data_offset)
261 body = 1;
262 /* The rest of actions in this loop are for header lines only */
263 continue;
264 }
265
266 /* Blank line ends message headers */
267 if (blank) {
268 header = 0;
269 continue;
270 }
271
272 /* Some header lines are known to remain fixed over MUA runs */
273 if (start)
274 switch (line[0]) {
275 case '\t':
276 case ' ':
277 /* Inherit "fixed" from the previous line */
278 break;
279
280 case 'R':
281 case 'r':
282 /* One Received: header from the local MTA should be sufficient */
283 fixed = !received &&
284 (received = eq(line, length, "Received:", 9));
285 break;
286
287 case 'D':
288 case 'd':
289 fixed = eq(line, length, "Delivered-To:", 13) ||
290 (!received && eq(line, length, "Date:", 5));
291 break;
292
293 case 'M':
294 case 'm':
295 fixed = !received &&
296 eq(line, length, "Message-ID:", 11);
297 break;
298
299 case 'X':
300 /* Let the local delivery agent help generate unique IDs but don't blindly
301 * trust this header alone as it could just as easily come from the remote. */
302 fixed = eq(line, length, "X-Delivery-ID:", 14);
303 break;
304
305 default:
306 fixed = 0;
307 continue;
308 }
309
310 /* We can hash all fragments of those lines, for UIDL */
311 if (fixed)
312 MD5_Update(&hash, line, length);
313 } while (1);
314
315 free(file_buffer);
316
317 if (done) {
318 /* Process the last message */
319 if (offset != mailbox_size) return 1;
320 if (!msg.data_offset) return 1;
321 msg.raw_size = offset - msg.raw_offset;
322 msg.data_size = offset - (blank & body) - msg.data_offset;
323 msg.size -= (blank & body) << 1;
324 MD5_Final(msg.hash, &hash);
325 if (db_op(&msg)) return 1;
326
327 /* Everything went well, update our timestamp if we were checking */
328 if (!init) mailbox_mtime = stat.st_mtime;
329 }
330
331 return !done;
332 }
333
mailbox_open(char * spool,char * mailbox)334 int mailbox_open(char *spool, char *mailbox)
335 {
336 char *pathname;
337 struct stat stat;
338 int result;
339
340 mailbox_fd = -1;
341
342 pathname = concat(spool, "/", mailbox, NULL);
343 if (!pathname) return 1;
344
345 if (lstat(pathname, &stat)) {
346 free(pathname);
347 return errno != ENOENT;
348 }
349
350 if (!S_ISREG(stat.st_mode)) {
351 free(pathname);
352 return 1;
353 }
354
355 if (!stat.st_size) {
356 free(pathname);
357 return 0;
358 }
359
360 mailbox_fd = open(pathname, O_RDWR | O_NOCTTY | O_NONBLOCK);
361
362 free(pathname);
363
364 if (mailbox_fd < 0)
365 return errno != ENOENT;
366
367 if (lock_fd(mailbox_fd, 1)) return 1;
368
369 result = mailbox_parse(1);
370
371 if (!result && time(NULL) == mailbox_mtime)
372 if (sleep_select(1, 0)) result = 1;
373
374 if (unlock_fd(mailbox_fd)) return 1;
375
376 return result;
377 }
378
mailbox_changed(void)379 static int mailbox_changed(void)
380 {
381 struct stat stat;
382 int result;
383
384 if (fstat(mailbox_fd, &stat)) return 1;
385 if (mailbox_mtime == stat.st_mtime) return 0;
386
387 if (lock_fd(mailbox_fd, 1)) return 1;
388
389 result = mailbox_parse(0);
390
391 if (!result && time(NULL) == mailbox_mtime)
392 if (sleep_select(1, 0)) result = 1;
393
394 if (unlock_fd(mailbox_fd)) return 1;
395
396 return result;
397 }
398
mailbox_get(struct db_message * msg,int lines)399 int mailbox_get(struct db_message *msg, int lines)
400 {
401 int event;
402
403 if (mailbox_changed()) return POP_CRASH_SERVER;
404
405 /* The calls to mailbox_changed() will set DB_STALE if that is the case */
406 if (lseek(mailbox_fd, msg->data_offset, SEEK_SET) < 0) {
407 mailbox_changed();
408 return POP_CRASH_SERVER;
409 }
410 if ((event = pop_reply_multiline(mailbox_fd, msg->data_size, lines))) {
411 if (event == POP_CRASH_SERVER) mailbox_changed();
412 return event;
413 }
414
415 if (mailbox_changed()) return POP_CRASH_SERVER;
416
417 if (pop_reply_terminate()) return POP_CRASH_NETFAIL;
418
419 return POP_OK;
420 }
421
mailbox_write(char * buffer)422 static int mailbox_write(char *buffer)
423 {
424 struct db_message *msg;
425 unsigned long old, new;
426 unsigned long size;
427 int block;
428
429 msg = db.head;
430 old = new = 0;
431 do {
432 if (msg->flags & MSG_DELETED) continue;
433 old = msg->raw_offset;
434
435 if (old == new) {
436 old = (new += msg->raw_size);
437 continue;
438 }
439
440 while ((size = msg->raw_size - (old - msg->raw_offset))) {
441 if (lseek(mailbox_fd, old, SEEK_SET) < 0) return 1;
442 if (size > FILE_BUFFER_SIZE)
443 size = FILE_BUFFER_SIZE;
444 block = read(mailbox_fd, buffer, size);
445 if (!block && old == mailbox_size) break;
446 if (block <= 0) return 1;
447
448 if (lseek(mailbox_fd, new, SEEK_SET) < 0) return 1;
449 if (write_loop(mailbox_fd, buffer, block) != block)
450 return 1;
451
452 old += block; new += block;
453 }
454 } while ((msg = msg->next));
455
456 old = mailbox_size;
457 while (1) {
458 if (lseek(mailbox_fd, old, SEEK_SET) < 0) return 1;
459 block = read(mailbox_fd, buffer, FILE_BUFFER_SIZE);
460 if (!block) break;
461 if (block < 0) return 1;
462
463 if (lseek(mailbox_fd, new, SEEK_SET) < 0) return 1;
464 if (write_loop(mailbox_fd, buffer, block) != block) return 1;
465
466 /* Cannot overflow unless locking is bypassed */
467 if ((old += block) < block || (new += block) < block) return 1;
468 }
469
470 if (ftruncate(mailbox_fd, new)) return 1;
471
472 return fsync(mailbox_fd);
473 }
474
mailbox_write_blocked(void)475 static int mailbox_write_blocked(void)
476 {
477 sigset_t blocked_set, old_set;
478 char *buffer;
479 int result;
480
481 if (sigfillset(&blocked_set)) return 1;
482 if (sigprocmask(SIG_BLOCK, &blocked_set, &old_set)) return 1;
483
484 if ((buffer = malloc(FILE_BUFFER_SIZE))) {
485 result = mailbox_write(buffer);
486 free(buffer);
487 } else
488 result = 1;
489
490 if (sigprocmask(SIG_SETMASK, &old_set, NULL)) return 1;
491
492 return result;
493 }
494
mailbox_update(void)495 int mailbox_update(void)
496 {
497 int result;
498
499 if (mailbox_fd < 0 || !(db.flags & DB_DIRTY)) return 0;
500
501 if (lock_fd(mailbox_fd, 0)) return 1;
502
503 if (!(result = mailbox_parse(0)))
504 result = mailbox_write_blocked();
505
506 if (unlock_fd(mailbox_fd)) return 1;
507
508 return result;
509 }
510
mailbox_close(void)511 int mailbox_close(void)
512 {
513 if (mailbox_fd < 0) return 0;
514
515 return close(mailbox_fd);
516 }
517