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