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