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