1 /*
2 **  MasqMail
3 **  Copyright (C) 1999-2001 Oliver Kurth
4 **  Copyright (C) 2010 markus schnalke <meillo@marmaro.de>
5 **
6 **  This program is free software; you can redistribute it and/or modify
7 **  it under the terms of the GNU General Public License as published by
8 **  the Free Software Foundation; either version 2 of the License, or
9 **  (at your option) any later version.
10 **
11 **  This program is distributed in the hope that it will be useful,
12 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 **  GNU General Public License for more details.
15 **
16 **  You should have received a copy of the GNU General Public License
17 **  along with this program; if not, write to the Free Software
18 **  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 */
20 
21 #include <sys/stat.h>
22 
23 #include "masqmail.h"
24 #include "dotlock.h"
25 
26 static gint
read_line(FILE * in,gchar * buf,gint buf_len)27 read_line(FILE *in, gchar *buf, gint buf_len)
28 {
29 	gint p = 0;
30 	gint c;
31 
32 	while ((c = getc(in)) != '\n' && (c != EOF)) {
33 		if (p >= buf_len - 1) {
34 			buf[buf_len-1] = '\0';
35 			ungetc(c, in);
36 			return buf_len;
37 		}
38 		buf[p++] = c;
39 	}
40 
41 	if (c == EOF) {
42 		return -1;
43 	}
44 	if ((p > 0) && (buf[p - 1] == '\r'))
45 		p--;
46 	buf[p++] = '\n';
47 	buf[p] = '\0';
48 
49 	return p;
50 }
51 
52 static void
spool_write_rcpt(FILE * out,address * rcpt)53 spool_write_rcpt(FILE *out, address *rcpt)
54 {
55 	gchar dlvrd_char = addr_is_delivered(rcpt) ? 'X' : (addr_is_failed(rcpt) ? 'F' : ' ');
56 
57 	if (rcpt->local_part[0] != '|') {
58 		/* this is a paranoid check, in case it slipped through: */
59 		/* if this happens, it is a bug */
60 		if (rcpt->domain == NULL) {
61 			logwrite(LOG_WARNING, "BUG: null domain for address %s, setting to %s\n", rcpt->local_part, conf.host_name);
62 			logwrite(LOG_WARNING, "please report this bug.\n");
63 			rcpt->domain = g_strdup(conf.host_name);
64 		}
65 		fprintf(out, "RT:%c%s\n", dlvrd_char, addr_string(rcpt));
66 	} else {
67 		fprintf(out, "RT:%c%s\n", dlvrd_char, rcpt->local_part);
68 	}
69 }
70 
71 static address*
spool_scan_rcpt(gchar * line)72 spool_scan_rcpt(gchar *line)
73 {
74 	address *rcpt = NULL;
75 
76 	if (!line[3]) {
77 		return NULL;
78 	}
79 	if (line[4] == '|') {
80 		rcpt = create_address_pipe(line+4);
81 	} else {
82 		rcpt = create_address(line+4, TRUE);
83 	}
84 	if (line[3] == 'X') {
85 		addr_mark_delivered(rcpt);
86 	} else if (line[3] == 'F') {
87 		addr_mark_failed(rcpt);
88 	}
89 	return rcpt;
90 }
91 
92 gboolean
spool_read_data(message * msg)93 spool_read_data(message *msg)
94 {
95 	FILE *in;
96 	gchar *spool_file;
97 
98 	DEBUG(5) debugf("spool_read_data entered\n");
99 	spool_file = g_strdup_printf("%s/%s-D", conf.spool_dir, msg->uid);
100 	DEBUG(5) debugf("reading data spool file '%s'\n", spool_file);
101 	in = fopen(spool_file, "r");
102 	if (!in) {
103 		logwrite(LOG_ALERT, "could not open spool data file %s: %s\n", spool_file, strerror(errno));
104 		return FALSE;
105 	}
106 
107 	char buf[MAX_DATALINE];
108 	int len;
109 
110 	/* msg uid */
111 	read_line(in, buf, MAX_DATALINE);
112 
113 	/* data */
114 	msg->data_list = NULL;
115 	while ((len = read_line(in, buf, MAX_DATALINE)) > 0) {
116 		msg->data_list = g_list_prepend(msg->data_list, g_strdup(buf));
117 	}
118 	msg->data_list = g_list_reverse(msg->data_list);
119 	fclose(in);
120 	return TRUE;
121 }
122 
123 gboolean
spool_read_header(message * msg)124 spool_read_header(message *msg)
125 {
126 	FILE *in;
127 	gchar *spool_file;
128 
129 	/* header spool: */
130 	spool_file = g_strdup_printf("%s/%s-H", conf.spool_dir, msg->uid);
131 	in = fopen(spool_file, "r");
132 	if (!in) {
133 		logwrite(LOG_ALERT, "could not open spool header file %s: %s\n",
134 		         spool_file, strerror(errno));
135 		return FALSE;
136 	}
137 
138 	header *hdr = NULL;
139 	char buf[MAX_DATALINE];
140 	int len;
141 
142 	/* msg uid */
143 	read_line(in, buf, MAX_DATALINE);
144 
145 	/* envelope header */
146 	while ((len = read_line(in, buf, MAX_DATALINE)) > 0) {
147 		if (buf[0] == '\n') {
148 			break;
149 		} else if (strncasecmp(buf, "MF:", 3) == 0) {
150 			msg->return_path = create_address(&(buf[3]), TRUE);
151 			DEBUG(3) debugf("spool_read: MAIL FROM: %s\n",
152 					msg->return_path->address);
153 		} else if (strncasecmp(buf, "RT:", 3) == 0) {
154 			address *addr;
155 			addr = spool_scan_rcpt(buf);
156 			if (addr_is_delivered(addr) || addr_is_failed(addr)) {
157 				msg->non_rcpt_list = g_list_append(msg->non_rcpt_list, addr);
158 			} else {
159 				msg->rcpt_list = g_list_append(msg->rcpt_list, addr);
160 			}
161 		} else if (strncasecmp(buf, "PR:", 3) == 0) {
162 			prot_id i;
163 			for (i = 0; i < PROT_NUM; i++) {
164 				if (strncasecmp(prot_names[i], &(buf[3]), strlen(prot_names[i])) == 0) {
165 					break;
166 				}
167 			}
168 			msg->received_prot = i;
169 		} else if (strncasecmp(buf, "RH:", 3) == 0) {
170 			g_strchomp(buf);
171 			msg->received_host = g_strdup(&(buf[3]));
172 		} else if (strncasecmp(buf, "ID:", 3) == 0) {
173 			g_strchomp(buf);
174 			msg->ident = g_strdup(&(buf[3]));
175 		} else if (strncasecmp(buf, "DS:", 3) == 0) {
176 			msg->data_size = atoi(&(buf[3]));
177 		} else if (strncasecmp(buf, "TR:", 3) == 0) {
178 			msg->received_time = (time_t) (atoi(&(buf[3])));
179 		} else if (strncasecmp(buf, "TW:", 3) == 0) {
180 			msg->warned_time = (time_t) (atoi(&(buf[3])));
181 		}
182 		/* so far ignore other tags */
183 	}
184 
185 	/* mail headers */
186 	while ((len = read_line(in, buf, MAX_DATALINE)) > 0) {
187 		if (strncasecmp(buf, "HD:", 3) == 0) {
188 			DEBUG(6) debugf("spool_read_header(): hdr start\n");
189 			hdr = get_header(&(buf[3]));
190 			msg->hdr_list = g_list_append(msg->hdr_list, hdr);
191 		} else if ((buf[0] == ' ' || buf[0] == '\t') && hdr) {
192 			DEBUG(6) debugf("spool_read_header(): hdr continuation\n");
193 			char *tmp = hdr->header;
194 			/* header continuation */
195 			hdr->header = g_strconcat(hdr->header, buf, NULL);
196 			hdr->value = hdr->header + (hdr->value - tmp);
197 			free(tmp);  /* because g_strconcat() allocs and copies */
198 		} else {
199 			break;
200 		}
201 	}
202 	fclose(in);
203 	return TRUE;
204 }
205 
206 message*
msg_spool_read(gchar * uid)207 msg_spool_read(gchar *uid)
208 {
209 	message *msg;
210 	gboolean ok = FALSE;
211 
212 	msg = create_message();
213 	msg->uid = g_strdup(uid);
214 
215 	DEBUG(4) debugf("msg_spool_read():\n");
216 	/* header spool: */
217 	ok = spool_read_header(msg);
218 	DEBUG(4) debugf("spool_read_header() returned: %d\n", ok);
219 	return msg;
220 }
221 
222 /*
223 **  write header. uid and gid should already be set to the
224 **  mail ids. Better call spool_write(msg, FALSE).
225 */
226 static gboolean
spool_write_header(message * msg)227 spool_write_header(message *msg)
228 {
229 	GList *node;
230 	gchar *spool_file, *tmp_file;
231 	FILE *out;
232 	gboolean ok = TRUE;
233 
234 	/* header spool: */
235 	tmp_file = g_strdup_printf("%s/%d-H.tmp", conf.spool_dir, getpid());
236 	DEBUG(4) debugf("tmp_file = %s\n", tmp_file);
237 
238 	if ((out = fopen(tmp_file, "w"))) {
239 		DEBUG(6) debugf("opened tmp_file %s\n", tmp_file);
240 
241 		fprintf(out, "%s\n", msg->uid);
242 		fprintf(out, "MF:%s\n", addr_string(msg->return_path));
243 
244 		DEBUG(6) debugf("after MF\n");
245 		foreach(msg->rcpt_list, node) {
246 			address *rcpt = (address *) (node->data);
247 			spool_write_rcpt(out, rcpt);
248 		}
249 		foreach(msg->non_rcpt_list, node) {
250 			address *rcpt = (address *) (node->data);
251 			spool_write_rcpt(out, rcpt);
252 		}
253 		DEBUG(6) debugf("after RT\n");
254 		fprintf(out, "PR:%s\n", prot_names[msg->received_prot]);
255 		if (msg->received_host != NULL)
256 			fprintf(out, "RH:%s\n", msg->received_host);
257 
258 		if (msg->ident != NULL)
259 			fprintf(out, "ID:%s\n", msg->ident);
260 
261 		if (msg->data_size >= 0)
262 			fprintf(out, "DS: %d\n", msg->data_size);
263 
264 		if (msg->received_time > 0)
265 			fprintf(out, "TR: %u\n", (int) (msg->received_time));
266 
267 		if (msg->warned_time > 0)
268 			fprintf(out, "TW: %u\n", (int) (msg->warned_time));
269 
270 		DEBUG(6) debugf("after RH\n");
271 		fprintf(out, "\n");
272 
273 		foreach(msg->hdr_list, node) {
274 			header *hdr = (header *) (node->data);
275 			fprintf(out, "HD:%s", hdr->header);
276 		}
277 		if (fflush(out) == EOF)
278 			ok = FALSE;
279 		else if (fdatasync(fileno(out)) != 0) {
280 			if (errno != EINVAL)  /* some fs do not support this..  I hope this also means that it is not necessary */
281 				ok = FALSE;
282 		}
283 		fclose(out);
284 		if (ok) {
285 			spool_file = g_strdup_printf("%s/%s-H", conf.spool_dir, msg->uid);
286 			DEBUG(4) debugf("spool_file = %s\n", spool_file);
287 			ok = (rename(tmp_file, spool_file) != -1);
288 			g_free(spool_file);
289 		}
290 	} else {
291 		logwrite(LOG_ALERT, "could not open temporary header spool file '%s': %s\n", tmp_file, strerror(errno));
292 		DEBUG(1) debugf("euid = %d, egid = %d\n", geteuid(), getegid());
293 		ok = FALSE;
294 	}
295 
296 	g_free(tmp_file);
297 
298 	return ok;
299 }
300 
301 gboolean
spool_write(message * msg,gboolean do_write_data)302 spool_write(message *msg, gboolean do_write_data)
303 {
304 	GList *list;
305 	gchar *spool_file, *tmp_file;
306 	FILE *out;
307 	gboolean ok = TRUE;
308 	uid_t saved_uid, saved_gid;
309 	/* user can read/write, group can read, others cannot do anything: */
310 	mode_t saved_mode = saved_mode = umask(026);
311 
312 	/* set uid and gid to the mail ids */
313 	if (!conf.run_as_user) {
314 		set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
315 	}
316 
317 	/* header spool: */
318 	ok = spool_write_header(msg);
319 
320 	if (ok && do_write_data) {
321 		/* data spool: */
322 		tmp_file = g_strdup_printf("%s/%d-D.tmp", conf.spool_dir, getpid());
323 		DEBUG(4) debugf("tmp_file = %s\n", tmp_file);
324 
325 		if ((out = fopen(tmp_file, "w"))) {
326 			fprintf(out, "%s\n", msg->uid);
327 			for (list = g_list_first(msg->data_list); list != NULL; list = g_list_next(list)) {
328 				fprintf(out, "%s", (gchar *) (list->data));
329 			}
330 
331 			/* possibly paranoid ;-) */
332 			if (fflush(out) == EOF) {
333 				ok = FALSE;
334 			} else if (fdatasync(fileno(out)) != 0) {
335 				if (errno != EINVAL) {  /* some fs do not support this..  I hope this also means that it is not necessary */
336 					ok = FALSE;
337 				}
338 			}
339 			fclose(out);
340 			if (ok) {
341 				spool_file = g_strdup_printf("%s/%s-D", conf.spool_dir, msg->uid);
342 				DEBUG(4) debugf("spool_file = %s\n", spool_file);
343 				ok = (rename(tmp_file, spool_file) != -1);
344 				g_free(spool_file);
345 			}
346 		} else {
347 			logwrite(LOG_ALERT, "could not open temporary data spool file: %s\n",
348 			         strerror(errno));
349 			ok = FALSE;
350 		}
351 		g_free(tmp_file);
352 	}
353 
354 	/* set uid and gid back */
355 	if (!conf.run_as_user) {
356 		set_euidgid(saved_uid, saved_gid, NULL, NULL);
357 	}
358 
359 	umask(saved_mode);
360 
361 	return ok;
362 }
363 
364 #define MAX_LOCKAGE 300
365 
366 gboolean
spool_lock(gchar * uid)367 spool_lock(gchar *uid)
368 {
369 	uid_t saved_uid, saved_gid;
370 	gchar *hitch_name;
371 	gchar *lock_name;
372 	gboolean ok = FALSE;
373 
374 	hitch_name = g_strdup_printf("%s/%s-%d.lock", conf.lock_dir, uid, getpid());
375 	lock_name = g_strdup_printf("%s/%s.lock", conf.lock_dir, uid);
376 
377 	/* set uid and gid to the mail ids */
378 	if (!conf.run_as_user) {
379 		set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
380 	}
381 
382 	ok = dot_lock(lock_name, hitch_name);
383 	if (!ok)
384 		logwrite(LOG_WARNING, "spool file %s is locked\n", uid);
385 
386 	/* set uid and gid back */
387 	if (!conf.run_as_user) {
388 		set_euidgid(saved_uid, saved_gid, NULL, NULL);
389 	}
390 
391 	g_free(lock_name);
392 	g_free(hitch_name);
393 
394 	return ok;
395 }
396 
397 gboolean
spool_unlock(gchar * uid)398 spool_unlock(gchar *uid)
399 {
400 	uid_t saved_uid, saved_gid;
401 	gchar *lock_name;
402 
403 	/* set uid and gid to the mail ids */
404 	if (!conf.run_as_user) {
405 		set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
406 	}
407 
408 	lock_name = g_strdup_printf("%s/%s.lock", conf.lock_dir, uid);
409 	dot_unlock(lock_name);
410 	g_free(lock_name);
411 
412 	/* set uid and gid back */
413 	if (!conf.run_as_user) {
414 		set_euidgid(saved_uid, saved_gid, NULL, NULL);
415 	}
416 	return TRUE;
417 }
418 
419 gboolean
spool_delete_all(message * msg)420 spool_delete_all(message *msg)
421 {
422 	uid_t saved_uid, saved_gid;
423 	gchar *spool_file;
424 
425 	/* set uid and gid to the mail ids */
426 	if (!conf.run_as_user) {
427 		set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
428 	}
429 
430 	/* header spool: */
431 	spool_file = g_strdup_printf("%s/%s-H", conf.spool_dir, msg->uid);
432 	if (unlink(spool_file) != 0) {
433 		logwrite(LOG_ALERT, "could not delete spool file %s: %s\n", spool_file, strerror(errno));
434 	}
435 	g_free(spool_file);
436 
437 	/* data spool: */
438 	spool_file = g_strdup_printf("%s/%s-D", conf.spool_dir, msg->uid);
439 	if (unlink(spool_file) != 0) {
440 		logwrite(LOG_ALERT, "could not delete spool file %s: %s\n", spool_file, strerror(errno));
441 	}
442 	g_free(spool_file);
443 
444 	/* set uid and gid back */
445 	if (!conf.run_as_user) {
446 		set_euidgid(saved_uid, saved_gid, NULL, NULL);
447 	}
448 	return TRUE;
449 }
450