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