1 /*
2 ** Copyright 1998 - 2005 Double Precision, Inc.
3 ** See COPYING for distribution information.
4 */
5 
6 #include	"courier.h"
7 #include	"comctlfile.h"
8 #include	"comqueuename.h"
9 #include	"comstrtimestamp.h"
10 #include	"comtrack.h"
11 #include	"maxlongsize.h"
12 #include	<sys/types.h>
13 #include	<sys/uio.h>
14 #if	HAVE_SYS_STAT_H
15 #include	<sys/stat.h>
16 #endif
17 #if	HAVE_UNISTD_H
18 #include	<unistd.h>
19 #endif
20 #if	HAVE_FCNTL_H
21 #include	<fcntl.h>
22 #endif
23 #include	<stdlib.h>
24 #include	<string.h>
25 #include	<errno.h>
26 
27 static const char rcsid[]="$Id: comctlfile.c,v 1.11 2005/02/04 02:44:59 mrsam Exp $";
28 
ctlfile_openi(ino_t i,struct ctlfile * p,int ro)29 int ctlfile_openi(ino_t i, struct ctlfile *p, int ro)
30 {
31 	return (ctlfile_openfn(qmsgsctlname(i), p, ro, 1));
32 }
33 
ctlfile_openit(ino_t i,time_t t,struct ctlfile * p,int ro)34 int ctlfile_openit(ino_t i, time_t t, struct ctlfile *p, int ro)
35 {
36 	return (ctlfile_openfn(qmsgqname(i, t), p, ro, 1));
37 }
38 
ctlfile_openfn(const char * n,struct ctlfile * p,int ro,int chk)39 int ctlfile_openfn(const char *n, struct ctlfile *p, int ro, int chk)
40 {
41 struct	stat	stat_buf;
42 unsigned nlines=2, nreceipients=1;
43 char	*cp;
44 unsigned i, j, rcnt, orcnt, dsncnt, nrcnt;
45 char	*q;
46 static char deferred_status[2]={COMCTLFILE_DELDEFERRED, 0};
47 
48 	p->cancelled=0;
49 	if ((p->fd=open(n, ro ? O_RDONLY:O_RDWR | O_APPEND)) < 0)
50 		return (-1);
51 
52 	p->msgsize=0;
53 
54 #ifdef	FD_CLOEXEC
55 	fcntl(p->fd, F_SETFD, FD_CLOEXEC);
56 #endif
57 	if (fstat(p->fd, &stat_buf) < 0)
58 	{
59 		close (p->fd);
60 		return (-1);
61 	}
62 
63 	if (chk && stat_buf.st_nlink != 2)
64 	{
65 		if (ro)
66 		{
67 			close(p->fd);
68 			errno=0;
69 			return (-1);
70 		}
71 
72 		if (stat_buf.st_nlink == 1)
73 			/*
74 			** Happens all the time - delivery complete, hanging
75 			** link
76 			*/
77 		{
78 			unlink(n);
79 			close(p->fd);
80 			errno=0;
81 			return (-1);
82 		}
83 		clog_msg_start_err();
84 		clog_msg_str("Invalid number of links for ");
85 		clog_msg_str(n);
86 		clog_msg_send();
87 		close(p->fd);
88 		errno=EINVAL;
89 		return (-1);
90 	}
91 	p->contents=q=courier_malloc(stat_buf.st_size+1);
92 
93 	i=stat_buf.st_size;
94 	while (i)
95 	{
96 		j=read(p->fd, q, i);
97 		if (j <= 0)
98 		{
99 			close(p->fd);
100 			free(p->contents);
101 			return (-1);
102 		}
103 		q += j;
104 		i -= j;
105 	}
106 	*q=0;
107 
108 	p->n=stat_buf.st_ino;
109 	p->mtime=stat_buf.st_mtime;
110 	p->starttime=0;
111 	for (cp=p->contents; *cp; cp++)
112 	{
113 		if (*cp == '\r')
114 			*cp=' ';
115 		if (*cp == '\n')	++nlines;
116 	}
117 	p->lines=(char **)courier_malloc(nlines * sizeof(char *));
118 
119 	for (nlines=0, cp=p->contents; (cp=strtok(cp, "\n")) != 0; cp=0)
120 	{
121 		p->lines[nlines++]=cp;
122 		if (*cp == COMCTLFILE_RECEIPIENT)	++nreceipients;
123 		if (*cp == COMCTLFILE_CANCEL_MSG)	p->cancelled=1;
124 	}
125 	p->lines[nlines]=0;
126 
127 	p->sender="";
128 	p->receipients=(char **)courier_malloc(nreceipients*sizeof(char *));
129 	p->oreceipients=(char **)courier_malloc(nreceipients*sizeof(char *));
130 	p->dsnreceipients=(char **)courier_malloc(nreceipients*sizeof(char *));
131 	p->delstatus=(char **)courier_malloc(nreceipients*sizeof(char *));
132 
133 	for (i=0; i<nreceipients; i++)
134 	{
135 		p->receipients[i]=p->oreceipients[i]=p->dsnreceipients[i]=0;
136 		p->delstatus[i]=deferred_status;
137 	}
138 
139 	for (i=0, nrcnt=0, rcnt=0, orcnt=0, dsncnt=0; p->lines[i]; i++)
140 	{
141 		switch (p->lines[i][0])	{
142 		case COMCTLFILE_SENDER:
143 			p->sender=p->lines[i]+1;
144 			break;
145 		case COMCTLFILE_RECEIPIENT:
146 			p->receipients[rcnt++]=p->lines[i]+1;
147 			break;
148 		case COMCTLFILE_ORECEIPIENT:
149 			if (orcnt < nreceipients)
150 				p->oreceipients[orcnt++]=p->lines[i]+1;
151 			break;
152 		case COMCTLFILE_DSN:
153 			if (dsncnt < nreceipients)
154 				p->dsnreceipients[dsncnt++]=p->lines[i]+1;
155 			break;
156 		case COMCTLFILE_DELSUCCESS:
157 		case COMCTLFILE_DELFAIL:
158 			j=atoi(p->lines[i]+1);
159 			if (j < nreceipients)
160 				p->delstatus[j]=p->lines[i];
161 			break;
162 		}
163 	}
164 	p->nreceipients=rcnt;
165 
166 	return (0);
167 }
168 
ctlfile_close(struct ctlfile * p)169 void ctlfile_close(struct ctlfile *p)
170 {
171 	free( p->oreceipients );
172 	free( p->dsnreceipients );
173 	free( p->receipients );
174 	free( p->lines );
175 	free( p->contents );
176 	free( p->delstatus );
177 
178 #if	EXPLICITSYNC
179 	fsync(p->fd);
180 #endif
181 	close(p->fd);
182 }
183 
ctlfile_searchfirst(struct ctlfile * p,char c)184 int ctlfile_searchfirst(struct ctlfile *p, char c)
185 {
186 int	i;
187 
188 	for (i=0; p->lines[i]; i++)
189 		if (p->lines[i][0] == c)	return (i);
190 	return (-1);
191 }
192 
notatomic()193 static void notatomic()
194 {
195 	clog_msg_start_err();
196 	clog_msg_str("Write to control file FAILED - not atomic.");
197 	clog_msg_send();
198 	exit(1);
199 }
200 
ctlfile_append(struct ctlfile * p,const char * q)201 void ctlfile_append(struct ctlfile *p, const char *q)
202 {
203 int	l=strlen(q);
204 
205 	if (write(p->fd, q, l) != l)	notatomic();
206 }
207 
ctlfile_appendv(struct ctlfile * p,const struct iovec * iov,int iovcnt)208 void ctlfile_appendv(struct ctlfile *p, const struct iovec *iov, int iovcnt)
209 {
210 	ctlfile_appendvfd(p->fd, iov, iovcnt);
211 }
212 
ctlfile_appendvfd(int fd,const struct iovec * iov,int iovcnt)213 void ctlfile_appendvfd(int fd, const struct iovec *iov, int iovcnt)
214 {
215 int	l=0;
216 int	i;
217 
218 	for (i=0; i<iovcnt; i++)
219 		l += iov[i].iov_len;
220 
221 	if (writev(fd, iov, iovcnt) != l)	notatomic();
222 }
223 
ctlfile_append_info(struct ctlfile * cf,unsigned rcptnum,const char * info)224 void ctlfile_append_info(struct ctlfile *cf, unsigned rcptnum, const char *info)
225 {
226 	ctlfile_append_connectioninfo(cf, rcptnum, COMCTLFILE_DELINFO_REPLY,
227 		info);
228 }
229 
ctlfile_append_connectioninfo(struct ctlfile * cf,unsigned rcptnum,char infotype,const char * info)230 void ctlfile_append_connectioninfo(struct ctlfile *cf,
231 	unsigned rcptnum, char infotype, const char *info)
232 {
233 	if (infotype == COMCTLFILE_DELINFO_CONNECTIONERROR
234 		|| infotype == COMCTLFILE_DELINFO_REPLY)
235 	{
236 		clog_msg_start_info();
237 		clog_msg_str("id=");
238 		clog_msg_msgid(cf);
239 		clog_msg_str(",from=<");
240 		clog_msg_str(cf->sender);
241 		clog_msg_str(">,addr=<");
242 		clog_msg_str(cf->receipients[rcptnum]);
243 		clog_msg_str(">: ");
244 		clog_msg_str(info);
245 		clog_msg_send();
246 	}
247 	ctlfile_append_connectioninfofd(cf->fd, rcptnum, infotype, info);
248 }
249 
ctlfile_append_infofd(int fd,unsigned rcptnum,const char * info)250 void ctlfile_append_infofd(int fd, unsigned rcptnum, const char *info)
251 {
252 	ctlfile_append_connectioninfofd(fd, rcptnum, COMCTLFILE_DELINFO_REPLY,
253 		info);
254 }
255 
ctlfile_append_connectioninfofd(int fd,unsigned rcptnum,char infotype,const char * info)256 void ctlfile_append_connectioninfofd(int fd, unsigned rcptnum,
257 		char infotype, const char *info)
258 {
259 char	fmtbuf[MAXLONGSIZE+10];
260 struct iovec iov[4];
261 char	buf2[2];
262 
263 	sprintf(fmtbuf, "%c%u ", COMCTLFILE_DELINFO, rcptnum);
264 	iov[0].iov_base=(caddr_t)fmtbuf;
265 	iov[0].iov_len=strlen(fmtbuf);
266 
267 	buf2[0]=infotype;
268 	buf2[1]=' ';
269 	iov[1].iov_base=(caddr_t)buf2;
270 	iov[1].iov_len=2;
271 	iov[3].iov_base=(caddr_t)"\n";
272 	iov[3].iov_len=1;
273 
274 	while (info && *info)
275 	{
276 	unsigned	i;
277 
278 		for (i=0; info[i] && info[i] != '\n'; i++)
279 			;
280 
281 		iov[2].iov_base=(caddr_t)info;
282 		iov[2].iov_len=i;
283 
284 		if (i && info[i-1] == '\r')	--iov[2].iov_len;
285 		ctlfile_appendvfd(fd, iov, 4);
286 		info += i;
287 		if (*info)	++info;
288 	}
289 }
290 
291 /* Record delivery completion status into the log file */
292 
293 static void ctlfile_append_msgsize(struct ctlfile *);
294 
ctlfile_append_reply(struct ctlfile * cf,unsigned rcptnum,const char * errmsg,char type,const char * delextra)295 void ctlfile_append_reply(struct ctlfile *cf, unsigned rcptnum,
296 	const char *errmsg, char type, const char *delextra)
297 {
298 	char type2;
299 
300 	if (errmsg && *errmsg)
301 	{
302 		clog_msg_start_info();
303 		clog_msg_str("id=");
304 		clog_msg_msgid(cf);
305 		clog_msg_str(",from=<");
306 		clog_msg_str(cf->sender);
307 		clog_msg_str(">,addr=<");
308 		clog_msg_str(cf->receipients[rcptnum]);
309 		clog_msg_str(">");
310 		if (type == COMCTLFILE_DELSUCCESS
311 		    || type == COMCTLFILE_DELSUCCESS_NOLOG)
312 		{
313 			if (cf->msgsize != 0)
314 				ctlfile_append_msgsize(cf);
315 			clog_msg_str(",success");
316 		}
317 		clog_msg_str(": ");
318 		clog_msg_str(errmsg);
319 		clog_msg_send();
320 		if (type == COMCTLFILE_DELSUCCESS)
321 			goto ugly_hack;
322 	}
323 	clog_msg_start_info();
324 	clog_msg_str("id=");
325 	clog_msg_msgid(cf);
326 	clog_msg_str(",from=<");
327 	clog_msg_str(cf->sender);
328 	clog_msg_str(">,addr=<");
329 	clog_msg_str(cf->receipients[rcptnum]);
330 	clog_msg_str(">");
331 
332 	if ((type == COMCTLFILE_DELSUCCESS
333 	     || type == COMCTLFILE_DELSUCCESS_NOLOG)
334 	    && cf->msgsize != 0)
335 		ctlfile_append_msgsize(cf);
336 
337 	clog_msg_str(type == COMCTLFILE_DELSUCCESS ||
338 		     type == COMCTLFILE_DELSUCCESS_NOLOG
339 		     ? ",status: success":
340 		     type == COMCTLFILE_DELFAIL ||
341 		     type == COMCTLFILE_DELFAIL_NOTRACK ? ",status: failure":
342 		     ",status: deferred");
343 	clog_msg_send();
344 
345 ugly_hack:
346 
347 	if (type == COMCTLFILE_DELSUCCESS_NOLOG)
348 	{
349 		type=COMCTLFILE_DELSUCCESS;
350 		errmsg=0;
351 	}
352 
353 	type2=type;
354 
355 	if (type2 == COMCTLFILE_DELFAIL_NOTRACK)
356 		type2=COMCTLFILE_DELFAIL;
357 
358 	ctlfile_append_replyfd(cf->fd, rcptnum, errmsg, type2, delextra);
359 
360 	if (rcptnum < cf->nreceipients && /* Sanity check */
361 	    ctlfile_searchfirst(cf, COMCTLFILE_TRACK) >= 0)
362 	{
363 		time_t timestamp;
364 		int results;
365 
366 		results=track_find(cf->receipients[rcptnum], &timestamp);
367 
368 		switch (type) {
369 		case COMCTLFILE_DELFAIL_NOTRACK:
370 			break;
371 
372 		case COMCTLFILE_DELSUCCESS:
373 			if (results == TRACK_ADDRDEFERRED ||
374 			    results == TRACK_ADDRFAILED)
375 				track_save(cf->receipients[rcptnum],
376 					   TRACK_ADDRACCEPTED);
377 			break;
378 
379 		case COMCTLFILE_DELFAIL:
380 		case COMCTLFILE_DELDEFERRED:
381 
382 			if (results == TRACK_ADDRDEFERRED ||
383 			    results == TRACK_ADDRFAILED)
384 			{
385 				if (timestamp >= time(NULL) -
386 				    (TRACK_NHOURS * 60 * 60)/2)
387 					break;
388 				/* If already had a failed attempt within
389 				** half the tracking window, no need to
390 				** add another record.
391 				*/
392 			}
393 			track_save(cf->receipients[rcptnum],
394 				   type == COMCTLFILE_DELFAIL ?
395 				   TRACK_ADDRFAILED:
396 				   TRACK_ADDRDEFERRED);
397 			break;
398 		default:
399 			break;
400 		}
401 	}
402 }
403 
ctlfile_append_msgsize(struct ctlfile * cf)404 static void ctlfile_append_msgsize(struct ctlfile *cf)
405 {
406 	clog_msg_str(",size=");
407 	clog_msg_ulong(cf->msgsize);
408 }
409 
ctlfile_append_replyfd(int fd,unsigned rcptnum,const char * errmsg,char type,const char * delextra)410 void ctlfile_append_replyfd(int fd, unsigned rcptnum,
411 	const char *errmsg, char type, const char *delextra)
412 {
413 const char *cp;
414 time_t	timestamp;
415 char	numbuf[1+MAXLONGSIZE];
416 int	numbuflen;
417 struct iovec iov[12];
418 int	n;
419 static const char delinfo=COMCTLFILE_DELINFO;
420 char	replybuf[2];
421 
422 	time(&timestamp);
423 
424 	sprintf(numbuf, "%u ", rcptnum);
425 	numbuflen=strlen(numbuf);
426 
427 	replybuf[0]=COMCTLFILE_DELINFO_REPLY;
428 	replybuf[1]=' ';
429 
430 	/*
431 	** errmsg can a multiline response.  Each line is logged.  The
432 	** last line of the response is logged together with the final
433 	** success/fail/deferred entry, so in most cases where there's
434 	** a one-line response, this results in only one write call.
435 	*/
436 
437 	n=0;
438 
439 	while (errmsg && *errmsg)
440 	{
441 		for (cp=errmsg; *cp && *cp != '\n'; cp++)
442 			;
443 
444 		iov[0].iov_base=(caddr_t)&delinfo;
445 		iov[0].iov_len=1;
446 		iov[1].iov_base=(caddr_t)numbuf;
447 		iov[1].iov_len=numbuflen;
448 
449 		iov[2].iov_base=(caddr_t)replybuf;
450 		iov[2].iov_len=2;
451 
452 		iov[3].iov_base=(caddr_t)errmsg;
453 
454 		/* Fix cruft like CRLF, or messages without trailing LFs */
455 
456 		if (cp > errmsg && cp[-1] == '\r')
457 		{
458 			iov[3].iov_len= cp - errmsg - 1;
459 			iov[4].iov_base=(caddr_t)"\n";
460 			iov[4].iov_len=1;
461 			n=5;
462 		}
463 		else if (*cp == '\n')
464 		{
465 			iov[3].iov_len= cp - errmsg + 1;
466 			n=4;
467 		}
468 		else
469 		{
470 			iov[3].iov_len= cp - errmsg;
471 			iov[4].iov_base=(caddr_t)"\n";
472 			iov[4].iov_len=1;
473 			n=5;
474 		}
475 		if (*cp == '\n')	++cp;
476 		errmsg=cp;
477 		if (*errmsg == '\0')
478 			break;	/* Last line, attach it to status record */
479 
480 		ctlfile_appendvfd(fd, iov, n);
481 		n=0;
482 	}
483 
484 	iov[n].iov_base= (caddr_t)&type;
485 	iov[n].iov_len=1;
486 	++n;
487 	iov[n].iov_base= (caddr_t)numbuf;
488 	iov[n].iov_len= numbuflen;
489 	++n;
490 	cp=strtimestamp(timestamp);
491 	iov[n].iov_base=(caddr_t)cp;
492 	iov[n].iov_len=strlen(cp);
493 	++n;
494 
495 	if (delextra)
496 	{
497 		iov[n].iov_base=(caddr_t)delextra;
498 		iov[n].iov_len=strlen(delextra);
499 		++n;
500 	}
501 
502 	iov[n].iov_base=(caddr_t)"\n";
503 	iov[n].iov_len=1;
504 	++n;
505 	ctlfile_appendvfd(fd, iov, n);
506 }
507 
ctlfile_nextattempt(struct ctlfile * ctf,time_t t)508 void ctlfile_nextattempt(struct ctlfile *ctf, time_t t)
509 {
510 	ctlfile_nextattemptfd(ctf->fd, t);
511 }
512 
513 
ctlfile_nextattemptfd(int fd,time_t t)514 void ctlfile_nextattemptfd(int fd, time_t t)
515 {
516 const char *cp=strtimestamp(t);
517 struct iovec iov[3];
518 static const char a=COMCTLFILE_NEXTATTEMPT;
519 
520 	iov[0].iov_base=(caddr_t)&a;
521 	iov[0].iov_len=1;
522 	iov[1].iov_base=(caddr_t)cp;
523 	iov[1].iov_len=strlen(cp);
524 	iov[2].iov_base="\n";
525 	iov[2].iov_len=1;
526 
527 	ctlfile_appendvfd(fd, iov, 3);
528 #if	EXPLICITSYNC
529 	fsync(fd);
530 #endif
531 }
532 
ctlfile_getnextattempt(struct ctlfile * c)533 time_t ctlfile_getnextattempt(struct ctlfile *c)
534 {
535 unsigned i;
536 time_t	t;
537 const char *cp;
538 struct	stat	stat_buf;
539 
540 	for (i=0; c->lines[i]; i++)
541 		;
542 
543 	while (i)
544 	{
545 		if (c->lines[--i][0] != COMCTLFILE_NEXTATTEMPT)
546 			continue;
547 		for (t=0, cp=c->lines[i]+1; *cp >= '0' && *cp <= '9'; ++cp)
548 			t=t*10 + (*cp-'0');
549 		if (stat(qmsgqname(c->n, t), &stat_buf) == 0)
550 			return (t);
551 	}
552 	return (0);
553 }
554