1 # include "sendmail.h"
2 # include <sys/stat.h>
3 # include <ndir.h>
4 # include <signal.h>
5 # include <errno.h>
6 
7 # ifndef QUEUE
8 SCCSID(@(#)queue.c	3.24		07/14/82	(no queueing));
9 # else QUEUE
10 
11 SCCSID(@(#)queue.c	3.24		07/14/82);
12 
13 /*
14 **  QUEUEUP -- queue a message up for future transmission.
15 **
16 **	The queued message should already be in the correct place.
17 **	This routine just outputs the control file as appropriate.
18 **
19 **	Parameters:
20 **		e -- the envelope to queue up.
21 **		queueall -- if TRUE, queue all addresses, rather than
22 **			just those with the QQUEUEUP flag set.
23 **
24 **	Returns:
25 **		none.
26 **
27 **	Side Effects:
28 **		The current request (only unsatisfied addresses)
29 **			are saved in a control file.
30 */
31 
32 queueup(e, queueall)
33 	register ENVELOPE *e;
34 	bool queueall;
35 {
36 	char cf[MAXNAME];
37 	char buf[MAXNAME];
38 	register FILE *cfp;
39 	register HDR *h;
40 	register ADDRESS *q;
41 	extern char *mktemp();
42 	register int i;
43 
44 	/*
45 	**  Create control file.
46 	*/
47 
48 	(void) strcpy(cf, QueueDir);
49 	(void) strcat(cf, "/tfXXXXXX");
50 	(void) mktemp(cf);
51 	cfp = fopen(cf, "w");
52 	if (cfp == NULL)
53 	{
54 		syserr("queueup: cannot create control file %s", cf);
55 		return;
56 	}
57 	(void) chmod(cf, 0600);
58 
59 # ifdef DEBUG
60 	if (Debug)
61 		printf("queueing in %s\n", cf);
62 # endif DEBUG
63 
64 	/*
65 	**  If there is no data file yet, create one.
66 	*/
67 
68 	if (e->e_df == NULL)
69 	{
70 		register FILE *dfp;
71 
72 		(void) strcpy(buf, QueueDir);
73 		(void) strcat(buf, "/dfXXXXXX");
74 		e->e_df = newstr(mktemp(buf));
75 		dfp = fopen(e->e_df, "w");
76 		if (dfp == NULL)
77 		{
78 			syserr("queueup: cannot create %s", e->e_df);
79 			(void) fclose(cfp);
80 			return;
81 		}
82 		(void) chmod(e->e_df, 0600);
83 		(*e->e_putbody)(dfp, Mailer[1], FALSE);
84 		(void) fclose(dfp);
85 	}
86 
87 	/*
88 	**  Output future work requests.
89 	*/
90 
91 	/* output name of data file */
92 	fprintf(cfp, "D%s\n", e->e_df);
93 
94 	/* output name of sender */
95 	fprintf(cfp, "S%s\n", e->e_from.q_paddr);
96 
97 	/* output timeout */
98 	fprintf(cfp, "T%ld\n", TimeOut);
99 
100 	/* output message priority */
101 	fprintf(cfp, "P%ld\n", e->e_msgpriority);
102 
103 	/* output message class */
104 	fprintf(cfp, "C%d\n", e->e_class);
105 
106 	/* output macro definitions */
107 	for (i = 0; i < 128; i++)
108 	{
109 		register char *p = e->e_macro[i];
110 
111 		if (p != NULL && i != (int) 'b')
112 			fprintf(cfp, "M%c%s\n", i, p);
113 	}
114 
115 	/* output list of recipient addresses */
116 	for (q = e->e_sendqueue; q != NULL; q = q->q_next)
117 	{
118 # ifdef DEBUG
119 		if (Debug > 0)
120 		{
121 			printf("queueing ");
122 			printaddr(q, FALSE);
123 		}
124 # endif DEBUG
125 		if (queueall || bitset(QQUEUEUP, q->q_flags))
126 			fprintf(cfp, "R%s\n", q->q_paddr);
127 	}
128 
129 	/* output headers for this message */
130 	for (h = e->e_header; h != NULL; h = h->h_link)
131 	{
132 		if (h->h_value == NULL || h->h_value[0] == '\0')
133 			continue;
134 		fprintf(cfp, "H");
135 		if (h->h_mflags != 0 && bitset(H_CHECK|H_ACHECK, h->h_flags))
136 			mfdecode(h->h_mflags, cfp);
137 		fprintf(cfp, "%s: %s\n", h->h_field, h->h_value);
138 	}
139 
140 	/*
141 	**  Clean up.
142 	*/
143 
144 	(void) fclose(cfp);
145 	(void) strcpy(buf, QueueDir);
146 	(void) strcat(buf, "/cfXXXXXX");
147 	(void) mktemp(buf);
148 	if (link(cf, buf) < 0)
149 		syserr("cannot link(%s, %s), df=%s", cf, buf, e->e_df);
150 	else
151 		(void) unlink(cf);
152 
153 	/* disconnect this temp file from the job */
154 	e->e_df = NULL;
155 }
156 /*
157 **  RUNQUEUE -- run the jobs in the queue.
158 **
159 **	Gets the stuff out of the queue in some presumably logical
160 **	order and processes them.
161 **
162 **	Parameters:
163 **		none.
164 **
165 **	Returns:
166 **		none.
167 **
168 **	Side Effects:
169 **		runs things in the mail queue.
170 */
171 
172 bool	ReorderQueue;		/* if set, reorder the send queue */
173 int	QueuePid;		/* pid of child running queue */
174 
175 runqueue(forkflag)
176 	bool forkflag;
177 {
178 	extern reordersig();
179 
180 	if (QueueIntvl != 0)
181 	{
182 		(void) signal(SIGALRM, reordersig);
183 		(void) alarm(QueueIntvl);
184 	}
185 
186 	if (forkflag)
187 	{
188 		QueuePid = dofork();
189 		if (QueuePid > 0)
190 		{
191 			/* parent */
192 			return;
193 		}
194 		else
195 			(void) alarm(0);
196 	}
197 
198 	for (;;)
199 	{
200 		/*
201 		**  Order the existing work requests.
202 		*/
203 
204 		orderq();
205 
206 		if (WorkQ == NULL)
207 		{
208 			/* no work?  well, maybe later */
209 			if (QueueIntvl == 0)
210 				break;
211 			pause();
212 			continue;
213 		}
214 
215 		ReorderQueue = FALSE;
216 
217 		/*
218 		**  Process them once at a time.
219 		**	The queue could be reordered while we do this to take
220 		**	new requests into account.  If so, the existing job
221 		**	will be finished but the next thing taken off WorkQ
222 		**	may be something else.
223 		*/
224 
225 		while (WorkQ != NULL)
226 		{
227 			WORK *w = WorkQ;
228 
229 			WorkQ = WorkQ->w_next;
230 			dowork(w);
231 			free(w->w_name);
232 			free((char *) w);
233 			if (ReorderQueue)
234 				break;
235 		}
236 
237 		if (QueueIntvl == 0)
238 			break;
239 	}
240 
241 	/* no work to do -- just exit */
242 	finis();
243 }
244 /*
245 **  REORDERSIG -- catch the alarm signal and tell sendmail to reorder queue.
246 **
247 **	Parameters:
248 **		none.
249 **
250 **	Returns:
251 **		none.
252 **
253 **	Side Effects:
254 **		sets the "reorder work queue" flag.
255 */
256 
257 reordersig()
258 {
259 	if (QueuePid == 0)
260 	{
261 		/* we are in a child doing queueing */
262 		ReorderQueue = TRUE;
263 	}
264 	else
265 	{
266 		/* we are in a parent -- poke child or start new one */
267 		if (kill(QueuePid, SIGALRM) < 0)
268 		{
269 			/* no child -- get zombie & start new one */
270 			static int st;
271 
272 			(void) wait(&st);
273 			QueuePid = dofork();
274 			if (QueuePid == 0)
275 			{
276 				/* new child; run queue */
277 				runqueue(FALSE);
278 				finis();
279 			}
280 		}
281 	}
282 
283 	/*
284 	**  Arrange to get this signal again.
285 	*/
286 
287 	(void) signal(SIGALRM, reordersig);
288 	(void) alarm(QueueIntvl);
289 }
290 /*
291 **  ORDERQ -- order the work queue.
292 **
293 **	Parameters:
294 **		none.
295 **
296 **	Returns:
297 **		none.
298 **
299 **	Side Effects:
300 **		Sets WorkQ to the queue of available work, in order.
301 */
302 
303 # define WLSIZE		120	/* max size of worklist per sort */
304 
305 orderq()
306 {
307 	register struct direct *d;
308 	register WORK *w;
309 	register WORK **wp;		/* parent of w */
310 	DIR *f;
311 	register int i;
312 	WORK wlist[WLSIZE];
313 	int wn = 0;
314 	extern workcmpf();
315 	extern char *QueueDir;
316 
317 	/* clear out old WorkQ */
318 	for (w = WorkQ; w != NULL; )
319 	{
320 		register WORK *nw = w->w_next;
321 
322 		WorkQ = nw;
323 		free(w->w_name);
324 		free((char *) w);
325 		w = nw;
326 	}
327 
328 	/* open the queue directory */
329 	f = opendir(QueueDir);
330 	if (f == NULL)
331 	{
332 		syserr("orderq: cannot open %s", QueueDir);
333 		return;
334 	}
335 
336 	/*
337 	**  Read the work directory.
338 	*/
339 
340 	while (wn < WLSIZE && (d = readdir(f)) != NULL)
341 	{
342 		char cbuf[MAXNAME];
343 		char lbuf[MAXNAME];
344 		FILE *cf;
345 		register char *p;
346 
347 		/* is this an interesting entry? */
348 		if (d->d_name[0] != 'c')
349 			continue;
350 
351 		/* yes -- find the control file location */
352 		(void) strcpy(cbuf, QueueDir);
353 		(void) strcat(cbuf, "/");
354 		p = &cbuf[strlen(cbuf)];
355 		(void) strcpy(p, d->d_name);
356 
357 		/* open control file */
358 		cf = fopen(cbuf, "r");
359 		if (cf == NULL)
360 		{
361 			/* this may be some random person sending hir msgs */
362 			/* syserr("orderq: cannot open %s", cbuf); */
363 			errno = 0;
364 			continue;
365 		}
366 		wlist[wn].w_name = newstr(cbuf);
367 
368 		/* extract useful information */
369 		while (fgets(lbuf, sizeof lbuf, cf) != NULL)
370 		{
371 			fixcrlf(lbuf, TRUE);
372 
373 			switch (lbuf[0])
374 			{
375 			  case 'P':		/* message priority */
376 				(void) sscanf(&lbuf[1], "%ld", &wlist[wn].w_pri);
377 				break;
378 			}
379 		}
380 		wn++;
381 		(void) fclose(cf);
382 	}
383 	(void) closedir(f);
384 
385 	/*
386 	**  Sort the work directory.
387 	*/
388 
389 	qsort(wlist, wn, sizeof *wlist, workcmpf);
390 
391 	/*
392 	**  Convert the work list into canonical form.
393 	*/
394 
395 	wp = &WorkQ;
396 	for (i = 0; i < wn; i++)
397 	{
398 		w = (WORK *) xalloc(sizeof *w);
399 		w->w_name = wlist[i].w_name;
400 		w->w_pri = wlist[i].w_pri;
401 		w->w_next = NULL;
402 		*wp = w;
403 		wp = &w->w_next;
404 	}
405 
406 # ifdef DEBUG
407 	if (Debug)
408 	{
409 		for (w = WorkQ; w != NULL; w = w->w_next)
410 			printf("%32s: pri=%ld\n", w->w_name, w->w_pri);
411 	}
412 # endif DEBUG
413 }
414 /*
415 **	WORKCMPF -- compare function for ordering work.
416 **
417 **	Parameters:
418 **		a -- the first argument.
419 **		b -- the second argument.
420 **
421 **	Returns:
422 **		-1 if a < b
423 **		0 if a == b
424 **		1 if a > b
425 **
426 **	Side Effects:
427 **		none.
428 */
429 
430 # define PRIFACT	1800		/* bytes each priority point is worth */
431 
432 workcmpf(a, b)
433 	register WORK *a;
434 	register WORK *b;
435 {
436 	if (a->w_pri == b->w_pri)
437 		return (0);
438 	else if (a->w_pri > b->w_pri)
439 		return (1);
440 	else
441 		return (-1);
442 }
443 /*
444 **  DOWORK -- do a work request.
445 **
446 **	Parameters:
447 **		w -- the work request to be satisfied.
448 **
449 **	Returns:
450 **		none.
451 **
452 **	Side Effects:
453 **		The work request is satisfied if possible.
454 */
455 
456 dowork(w)
457 	register WORK *w;
458 {
459 	register int i;
460 	auto int xstat;
461 
462 # ifdef DEBUG
463 	if (Debug)
464 		printf("dowork: %s pri %ld\n", w->w_name, w->w_pri);
465 # endif DEBUG
466 
467 	/*
468 	**  Fork for work.
469 	*/
470 
471 	i = fork();
472 	if (i < 0)
473 	{
474 		syserr("dowork: cannot fork");
475 		return;
476 	}
477 
478 	if (i == 0)
479 	{
480 		char buf[MAXNAME];
481 
482 		/*
483 		**  CHILD
484 		**	Change the name of the control file to avoid
485 		**		duplicate deliveries.   Then run the file
486 		**		as though we had just read it.
487 		**	We save an idea of the temporary name so we
488 		**		can recover on interrupt.
489 		*/
490 
491 		(void) alarm(0);
492 		FatalErrors = FALSE;
493 		QueueRun = TRUE;
494 		MailBack = TRUE;
495 		(void) strcpy(buf, QueueDir);
496 		(void) strcat(buf, "/tfXXXXXX");
497 		(void) mktemp(buf);
498 		if (link(w->w_name, buf) < 0)
499 		{
500 			syserr("dowork: link(%s, %s)", w->w_name, buf);
501 
502 			/* it's ok to lie -- it will be run later */
503 			exit(EX_OK);
504 		}
505 		ControlFile = newstr(buf);
506 		(void) unlink(w->w_name);
507 
508 		/* create ourselves a transcript file */
509 		openxscrpt();
510 
511 		/* do basic system initialization */
512 		initsys();
513 
514 		/* read the queue control file */
515 		readqf(buf);
516 
517 		/* do the delivery */
518 		sendall(CurEnv, FALSE);
519 
520 		/* if still not sent, perhaps we should time out.... */
521 # ifdef DEBUG
522 		if (Debug > 2)
523 			printf("CurTime=%ld, TimeOut=%ld\n", CurTime, TimeOut);
524 # endif DEBUG
525 		if (CurEnv->e_queueup && CurTime > TimeOut)
526 			timeout(w);
527 
528 		/* get rid of the temporary file -- a new cf will be made */
529 		ControlFile = NULL;
530 		(void) unlink(buf);
531 
532 		/* finish up and exit */
533 		finis();
534 	}
535 
536 	/*
537 	**  Parent -- pick up results.
538 	*/
539 
540 	errno = 0;
541 	while ((i = wait(&xstat)) > 0 && errno != EINTR)
542 	{
543 		if (errno == EINTR)
544 		{
545 			errno = 0;
546 		}
547 	}
548 }
549 /*
550 **  READQF -- read queue file and set up environment.
551 **
552 **	Parameters:
553 **		cf -- name of queue control file.
554 **
555 **	Returns:
556 **		none.
557 **
558 **	Side Effects:
559 **		cf is read and created as the current job, as though
560 **		we had been invoked by argument.
561 */
562 
563 readqf(cf)
564 	char *cf;
565 {
566 	register FILE *f;
567 	char buf[MAXLINE];
568 
569 	/*
570 	**  Open the file created by queueup.
571 	*/
572 
573 	f = fopen(cf, "r");
574 	if (f == NULL)
575 	{
576 		syserr("readqf: no cf file %s", cf);
577 		return;
578 	}
579 
580 	/*
581 	**  Read and process the file.
582 	*/
583 
584 	if (Verbose)
585 		printf("\nRunning %s\n", cf);
586 	while (fgets(buf, sizeof buf, f) != NULL)
587 	{
588 		fixcrlf(buf, TRUE);
589 
590 		switch (buf[0])
591 		{
592 		  case 'R':		/* specify recipient */
593 			sendto(&buf[1], 1, (ADDRESS *) NULL, &CurEnv->e_sendqueue);
594 			break;
595 
596 		  case 'H':		/* header */
597 			(void) chompheader(&buf[1], FALSE);
598 			break;
599 
600 		  case 'S':		/* sender */
601 			setsender(newstr(&buf[1]));
602 			break;
603 
604 		  case 'D':		/* data file name */
605 			CurEnv->e_df = newstr(&buf[1]);
606 			TempFile = fopen(CurEnv->e_df, "r");
607 			if (TempFile == NULL)
608 				syserr("readqf: cannot open %s", CurEnv->e_df);
609 			break;
610 
611 		  case 'T':		/* timeout */
612 			(void) sscanf(&buf[1], "%ld", &TimeOut);
613 			break;
614 
615 		  case 'P':		/* message priority */
616 			(void) sscanf(&buf[1], "%ld", &CurEnv->e_msgpriority);
617 
618 			/* make sure that big things get sent eventually */
619 			CurEnv->e_msgpriority -= WKTIMEFACT;
620 			break;
621 
622 		  case 'C':		/* message class */
623 			(void) sscanf(&buf[1], "%hd", &CurEnv->e_class);
624 			break;
625 
626 		  case 'M':		/* define macro */
627 			define(buf[1], newstr(&buf[2]));
628 			break;
629 
630 		  default:
631 			syserr("readqf(%s): bad line \"%s\"", cf, buf);
632 			break;
633 		}
634 	}
635 }
636 /*
637 **  TIMEOUT -- process timeout on queue file.
638 **
639 **	Parameters:
640 **		w -- pointer to work request that timed out.
641 **
642 **	Returns:
643 **		none.
644 **
645 **	Side Effects:
646 **		Returns a message to the sender saying that this
647 **		message has timed out.
648 */
649 
650 timeout(w)
651 	register WORK *w;
652 {
653 	char buf[MAXLINE];
654 	extern char *TextTimeOut;
655 
656 # ifdef DEBUG
657 	if (Debug > 0)
658 		printf("timeout(%s)\n", w->w_name);
659 # endif DEBUG
660 	message(Arpa_Info, "Message has timed out");
661 
662 	/* return message to sender */
663 	(void) sprintf(buf, "Cannot send mail for %s", TextTimeOut);
664 	(void) returntosender(buf, &CurEnv->e_from, TRUE);
665 
666 	/* arrange to remove files from queue */
667 	CurEnv->e_queueup = FALSE;
668 }
669 
670 # endif QUEUE
671