1 #include "qpage.h"
2
3
4 /*
5 ** global variables
6 */
7 #ifndef lint
8 static char sccsid[] = "@(#)queue.c 1.22 07/07/98 tomiii@qpage.org";
9 #endif
10
11
12 /*
13 ** insert_jobs()
14 **
15 ** This function inserts a page into the job list. Since each page
16 ** may contain multiple recipients each with a possibly different
17 ** paging service, each recipient is considered a separate job. The
18 ** job list is sorted first by service name and then by level within
19 ** each service. This allows all pages for a particular service to
20 ** be sent with one phone call (if possible). Note that each job
21 ** simply contains pointers to the respective elements of a page
22 ** structure. Therefore, a job in this context does not actually
23 ** contain any data, only pointers to data.
24 **
25 ** Input:
26 ** joblist - a pointer to the head node in the list
27 ** p - the new page to be added to the list.
28 **
29 ** Returns:
30 ** the number of pending jobs in the specified page
31 **
32 ** Note:
33 ** Jobs scheduled for the future (i.e. seen here but
34 ** not added to the job list) are counted in the number
35 ** returned by this function.
36 */
37 int
insert_jobs(job_t ** joblist,PAGE * p)38 insert_jobs(job_t **joblist, PAGE *p)
39 {
40 job_t *curr;
41 job_t *prev;
42 job_t *tmp;
43 rcpt_t *rcpt;
44 service_t *service;
45 pager_t *pager;
46 int jobcount;
47 int i;
48
49
50 jobcount = 0;
51
52 for (rcpt=p->rcpts; rcpt; rcpt=rcpt->next) {
53 /*
54 ** skip pages we've already sent
55 */
56 if (rcpt->flags & F_SENT)
57 continue;
58
59 /*
60 ** skip pages which are scheduled for the future
61 */
62 if (rcpt->holduntil > time(NULL)) {
63 if (Debug || Interactive)
64 qpage_log(LOG_DEBUG, "skipping %s until %s",
65 rcpt->pager,
66 my_ctime(&rcpt->holduntil));
67
68 jobcount++;
69 continue;
70 }
71
72 pager = lookup(Pagers, rcpt->pager);
73
74 /*
75 ** If this is a raw pagerid we need to kludge something
76 */
77 if (pager == NULL && (rcpt->flags & F_RAWPID)) {
78 pager = (void *)malloc(sizeof(*pager));
79 (void)memset((char *)pager, 0, sizeof(*pager));
80 pager->name = strdup(rcpt->pager);
81 pager->pagerid = strdup(rcpt->pager);
82 pager->flags = rcpt->flags;
83 }
84
85 /*
86 ** "pager" better not be NULL at this point
87 */
88 if (pager == NULL) {
89 qpage_log(LOG_ERR, "no such pager %s", rcpt->pager);
90 continue;
91 }
92
93 /*
94 ** If they specified a coverage, use it, otherwise
95 ** use the default coverage for this pager.
96 */
97 if (rcpt->coverage)
98 service = lookup(Services, rcpt->coverage);
99 else
100 service = pager->service;
101
102 if (service == NULL) {
103 qpage_log(LOG_ERR, "no such service %s",
104 rcpt->coverage);
105
106 continue;
107 }
108
109 #ifdef REJECT_IS_FAILURE
110 /*
111 ** do not retry rejected pages
112 */
113 if (rcpt->flags & F_REJECT) {
114 rcpt->flags |= F_FAILED;
115 continue;
116 }
117 #endif
118
119 /*
120 ** limit retries to the number specified by the paging service
121 */
122 if (rcpt->goodtries >= service->maxtries) {
123 rcpt->flags |= F_FAILED;
124
125 qpage_log(LOG_ERR, "too many retries for %s in %s",
126 rcpt->pager, p->filename);
127
128 continue;
129 }
130
131 /*
132 ** build a new job for this recipient
133 */
134 tmp = (void *)malloc(sizeof(*tmp));
135 tmp->next = NULL;
136 tmp->p = p;
137 tmp->rcpt = rcpt;
138 tmp->service = service;
139 tmp->pager = pager;
140
141 if (Debug) {
142 qpage_log(LOG_DEBUG, "pager=%s, pagerid=%s, service=%s",
143 pager->name, pager->pagerid, service->name);
144 }
145
146 curr = *joblist;
147 prev = NULL;
148
149 /*
150 ** Scan through the job list to find an appropriate
151 ** place to insert this recipient.
152 */
153 while (curr) {
154 i = strcmp(curr->service->name, tmp->service->name);
155
156 if (i == 0)
157 i = curr->rcpt->level - tmp->rcpt->level;
158
159 if (i == 0)
160 i = curr->p->created - tmp->p->created;
161
162 if (i>0)
163 break;
164
165 prev = curr;
166 curr = curr->next;
167 }
168
169 if (prev == NULL) {
170 /*
171 ** insert the job at the beginning of the list
172 */
173 tmp->next = *joblist;
174 *joblist = tmp;
175 }
176 else {
177 /*
178 ** insert the job somewhere after the first node
179 */
180 tmp->next = prev->next;
181 prev->next = tmp;
182 }
183
184 jobcount++;
185 }
186
187 /*
188 ** At this point, a jobcount of zero means all the recipients
189 ** of this page fall into one of these two categories:
190 **
191 ** - we already successfully sent the page
192 ** - we failed to send the page and we should stop trying
193 **
194 ** If there are no valid jobs here now, there won't be any in
195 ** the future so let's nuke this page.
196 */
197 if (jobcount == 0)
198 p->flags |= F_BADPAGE;
199
200 return(jobcount);
201 }
202
203
204 /*
205 ** read_page()
206 **
207 ** This function reads a page from the page queue.
208 **
209 ** Input:
210 ** file - the filename to read from
211 **
212 ** Returns:
213 ** a page structure, or NULL on failure
214 */
215 PAGE *
read_page(char * file)216 read_page(char *file)
217 {
218 rcpt_t *tmp;
219 FILE *fp;
220 PAGE *p;
221 time_t holduntil;
222 time_t lasttry;
223 char keyword[255];
224 char coverage[255];
225 char status[255];
226 char name[255];
227 char msgid[255];
228 char hostname[257];
229 char *buf;
230 char *ptr;
231 int tries;
232 int goodtries;
233 int version;
234 int buflen;
235 int gotmarker;
236 int line;
237 int level;
238 int flags;
239 int bytes;
240 int n;
241
242
243 gotmarker = 0;
244 line = 0;
245 coverage[0] = '\0';
246 level = DEFAULT_LEVEL;
247 holduntil = 0;
248 lasttry = 0;
249 flags = 0;
250 tries = 0;
251 goodtries = 0;
252
253 if ((fp = fopen(file, "r")) == NULL) {
254 qpage_log(LOG_NOTICE, "cannot open file %s for reading", file);
255 return(NULL);
256 }
257
258 if (lock_file(fileno(fp), O_RDONLY, TRUE) < 0) {
259 qpage_log(LOG_ERR, "cannot lock %s: %s", file,
260 strerror(errno));
261
262 return(NULL);
263 }
264
265 p = (void *)malloc(sizeof(*p));
266 (void)memset((char *)p, 0, sizeof(*p));
267
268 buf = (void *)malloc(BUFCHUNKSIZE);
269 buflen = BUFCHUNKSIZE;
270
271 while (fgets(buf, buflen, fp)) {
272 line++;
273
274 if ((ptr = strchr(buf, '\n')) == NULL) {
275 qpage_log(LOG_ERR,
276 "short read (this should never happen)");
277 }
278 else
279 *ptr = '\0';
280
281 if (sscanf(buf, "%s %n", keyword, &n) != 1) {
282 qpage_log(LOG_ERR,
283 "no keyword (this should never happen)");
284
285 continue;
286 }
287
288 switch (keyword[0]) {
289 case '#': /* comment */
290 break;
291
292 case '-': /* end-of-recipients marker */
293 gotmarker++;
294 break;
295
296 case 'B': /* bytes (size of message) */
297 (void)sscanf(&buf[n], "%d", &bytes);
298 if (bytes > buflen) {
299 buf = (void *)realloc(buf, bytes);
300 buflen = bytes;
301 }
302 break;
303
304 case 'C': /* created/coverage */
305 if (gotmarker) {
306 (void)sscanf(&buf[n], "%ld",
307 &p->created);
308 }
309 else {
310 (void)sscanf(&buf[n], "%s", coverage);
311 }
312 break;
313
314 case 'F': /* from/flags */
315 if (gotmarker) {
316 /*
317 ** We can't use sscanf() here
318 ** because there may be embedded
319 ** whitespace in the CALLerid
320 ** information.
321 */
322 while (buf[n] && isspace(buf[n]))
323 n++;
324
325 p->from = strdup(&buf[n]);
326 }
327 else {
328 (void)sscanf(&buf[n], "%d", &flags);
329 }
330 break;
331
332 case 'G': /* goodtries */
333 (void)sscanf(&buf[n], "%d", &goodtries);
334 break;
335
336 case 'H': /* hostname/holduntil */
337 if (gotmarker) {
338 (void)sscanf(&buf[n], "%s",
339 hostname);
340
341 p->hostname = strdup(hostname);
342 }
343 else {
344 (void)sscanf(&buf[n], "%ld",
345 &holduntil);
346 }
347 break;
348
349 case 'I': /* ident */
350 p->ident = strdup(&buf[n]);
351 break;
352
353 case 'L': /* lasttry */
354 (void)sscanf(&buf[n], "%ld", &lasttry);
355 break;
356
357 case 'M': /* message */
358 p->message = strdup(&buf[n]);
359 break;
360
361 case 'P': /* pager */
362 name[0] = '\0';
363 (void)sscanf(&buf[n], "%s", name);
364
365 tmp = (void *)malloc(sizeof(*tmp));
366 (void)memset((char *)tmp, 0, sizeof(*tmp));
367 tmp->next = p->rcpts;
368 p->rcpts = tmp;
369
370 tmp->pager = strdup(name);
371
372 if (coverage[0])
373 tmp->coverage = strdup(coverage);
374
375 tmp->holduntil = holduntil;
376 tmp->lasttry = lasttry;
377 tmp->goodtries = goodtries;
378 tmp->tries = tries;
379 tmp->level = level;
380 tmp->flags = flags;
381
382 coverage[0] = '\0';
383 level = DEFAULT_LEVEL;
384 holduntil = 0;
385 lasttry = 0;
386 goodtries = 0;
387 tries = 0;
388 flags = 0;
389
390 break;
391
392 case 'S': /* status/servicelevel */
393 if (gotmarker) {
394 (void)sscanf(&buf[n], "%s", status);
395 }
396 else {
397 (void)sscanf(&buf[n], "%d", &level);
398 }
399 break;
400
401 case 'T': /* tries */
402 (void)sscanf(&buf[n], "%d", &tries);
403 break;
404
405 case 'U': /* unique id */
406 (void)sscanf(&buf[n], "%s", msgid);
407 p->messageid = strdup(msgid);
408 break;
409
410 case 'V': /* version */
411 (void)sscanf(&buf[n], "%d", &version);
412 if (version != 3) {
413 qpage_log(LOG_ERR, "FATAL ERROR: incompatible version of queue file");
414 clear_page(p, FALSE);
415 free(p);
416 return(NULL);
417 }
418 break;
419
420 default:
421 qpage_log(LOG_NOTICE, "%s line %d: unknown meaning (%s)",
422 file, line, buf);
423 break;
424 }
425 }
426
427 free(buf);
428 (void)fclose(fp);
429
430 p->filename = strdup(file);
431
432 if (p->messageid == NULL)
433 p->messageid = strdup("[none]");
434
435 return(p);
436 }
437
438
439 /*
440 ** write_page()
441 **
442 ** This function writes a page to the page queue. If the filename
443 ** field of the page structure is not null, it is assumed to point
444 ** to the name of the file the page was read from. In this case,
445 ** if the page has been successfully delivered to all the recipients,
446 ** the file is removed. Otherwise the page is written back to that
447 ** file. If the filename field of the page structure is NULL, this
448 ** function assumes this is a new page. A new filename is created
449 ** based on the current time.
450 **
451 ** Input:
452 ** p - a page structure
453 ** new - whether this is a new page
454 **
455 ** Returns:
456 ** an integer status code:
457 ** 0 = queue file was removed/renamed
458 ** 1 = page was written to queue file
459 ** -1 = error occurred; status unknown
460 */
461 int
write_page(PAGE * p,int new)462 write_page(PAGE *p, int new)
463 {
464 rcpt_t *tmp;
465 FILE *fp;
466 char filename[255];
467 char *badname;
468 int fd;
469 int ext;
470 int doit;
471
472
473 /*
474 ** send e-mail notification (if needed) of the page status
475 */
476 if (new == FALSE) {
477 if (Administrator)
478 notify_administrator(p);
479
480 if (p->from)
481 notify_submitter(p);
482 }
483
484 /*
485 ** first verify whether this page should be written back or not
486 */
487 doit = FALSE;
488 for (tmp=p->rcpts; tmp; tmp=tmp->next) {
489 if (tmp->flags & F_SENT)
490 continue;
491
492 doit = TRUE;
493 }
494
495 if (doit == FALSE) {
496 if (p->filename) {
497 if (Debug)
498 qpage_log(LOG_DEBUG, "unlinking %s",
499 p->filename);
500
501 if (unlink(p->filename) < 0)
502 qpage_log(LOG_WARNING, "unlink failed for %s: %s",
503 p->filename, strerror(errno));
504 }
505
506 return(0);
507 }
508
509 if (p->filename == NULL) {
510 ext = 0;
511
512 do {
513 (void)sprintf(filename, "P%lu.%03u", time(NULL), ext++);
514
515 fd = open(filename, O_CREAT|O_EXCL|O_WRONLY, 0644);
516
517 if (fd >= 0)
518 break;
519
520 if (errno != EEXIST) {
521 qpage_log(LOG_NOTICE, "cannot create %s: %s",
522 filename, strerror(errno));
523 }
524 }
525 while (ext < 100);
526
527 if (fd < 0) {
528 qpage_log(LOG_ERR, "cannot create file %s: %s",
529 filename, strerror(errno));
530
531 return(-1);
532 }
533
534 p->filename = strdup(filename);
535 }
536 else {
537 if ((fd = open(p->filename, O_WRONLY, 0644)) < 0) {
538 qpage_log(LOG_ERR, "cannot open file %s: %s",
539 p->filename, strerror(errno));
540
541 return(-1);
542 }
543 }
544
545 if (lock_file(fd, O_RDWR, TRUE) < 0) {
546 qpage_log(LOG_ERR, "cannot lock %s: %s", p->filename,
547 strerror(errno));
548
549 return(-1);
550 }
551
552 /*
553 ** explicitly truncate the file now that it's locked
554 */
555 (void)ftruncate(fd, (off_t)0);
556
557 if ((fp = fdopen(fd, "w")) == NULL) {
558 qpage_log(LOG_ERR, "cannot reopen file %s", p->filename);
559 (void)close(fd);
560 return(-1);
561 }
562
563 fprintf(fp, "Version: 3\n");
564
565 for (tmp=p->rcpts; tmp; tmp=tmp->next) {
566 if (tmp->coverage)
567 fprintf(fp, "Coverage: %s\n", tmp->coverage);
568
569 if (tmp->holduntil)
570 fprintf(fp, "Holduntil: %lu %s\n", tmp->holduntil,
571 my_ctime(&tmp->holduntil));
572
573 if (tmp->lasttry)
574 fprintf(fp, "Lasttry: %lu %s\n", tmp->lasttry,
575 my_ctime(&tmp->lasttry));
576
577 fprintf(fp, "Tries: %d\n", tmp->tries);
578
579 if (tmp->goodtries)
580 fprintf(fp, "Goodtries: %d\n", tmp->goodtries);
581
582 if (tmp->level != DEFAULT_LEVEL)
583 fprintf(fp, "Servicelevel: %d\n", tmp->level);
584
585 if (tmp->flags) {
586 fprintf(fp, "Flags: %d (", tmp->flags);
587
588 if (tmp->flags & F_SENT)
589 fprintf(fp, " F_SENT");
590
591 if (tmp->flags & F_FAILED)
592 fprintf(fp, " F_FAILED");
593
594 if (tmp->flags & F_BUSY)
595 fprintf(fp, " F_BUSY");
596
597 if (tmp->flags & F_NOCARRIER)
598 fprintf(fp, " F_NOCARRIER");
599
600 if (tmp->flags & F_NOMODEM)
601 fprintf(fp, " F_NOMODEM");
602
603 if (tmp->flags & F_FORCED)
604 fprintf(fp, " F_FORCED");
605
606 if (tmp->flags & F_NOPROMPT)
607 fprintf(fp, " F_NOPROMPT");
608
609 if (tmp->flags & F_UNKNOWN)
610 fprintf(fp, " F_UNKNOWN");
611
612 if (tmp->flags & F_REJECT)
613 fprintf(fp, " F_REJECT");
614
615 if (tmp->flags & F_RAWPID)
616 fprintf(fp, " F_RAWPID");
617
618 if (tmp->flags & F_SENDMAIL)
619 fprintf(fp, " F_SENDMAIL");
620
621 if (tmp->flags & F_SENTMAIL)
622 fprintf(fp, " F_SENTMAIL");
623
624 if (tmp->flags & F_SENTADMIN)
625 fprintf(fp, " F_SENTADMIN");
626
627 fprintf(fp, " )\n");
628 }
629
630 fprintf(fp, "Pager: %s\n", tmp->pager);
631 }
632
633 fprintf(fp, "-\n");
634
635 if (p->from)
636 fprintf(fp, "From: %s\n", p->from);
637
638 if (p->ident)
639 fprintf(fp, "Ident: %s\n", p->ident);
640
641 if (p->hostname)
642 fprintf(fp, "Hostname: %s\n", p->hostname);
643
644 /*
645 ** Tell the reader how big the buffer has to be in order to read
646 ** the next line. This includes the message length plus the word
647 ** "Message:" plus a space, a newline, and a null character.
648 */
649 fprintf(fp, "Bytes: %d\n", strlen(p->message)+11);
650 fprintf(fp, "Message: %s\n", p->message);
651 fprintf(fp, "Created: %lu %s\n", p->created, my_ctime(&p->created));
652 fprintf(fp, "UniqueID: %s\n", p->messageid);
653
654 if (p->status)
655 fprintf(fp, "Status: %s\n", p->status);
656
657 /*
658 ** Before we close the file (which releases the lock), we
659 ** should check to see if this page is worth reading again
660 ** in the future. If not, rename the file to start with 'B'.
661 */
662 badname = NULL;
663 if (p->flags & F_BADPAGE) {
664 badname = strdup(p->filename);
665 badname[0] = 'B';
666
667 qpage_log(LOG_NOTICE, "renaming bad page to %s", badname);
668
669 if (rename(p->filename, badname) < 0) {
670 qpage_log(LOG_WARNING, "cannot rename %s to %s: %s",
671 p->filename, badname, strerror(errno));
672 }
673 }
674
675 if (fclose(fp)) {
676 qpage_log(LOG_WARNING, "error writing queue file: %s",
677 strerror(errno));
678
679 return(-1);
680 }
681
682 if (badname) {
683 free(badname);
684 return(0);
685 }
686
687 return(1);
688 }
689
690
691 /*
692 ** read_queue()
693 **
694 ** This function reads the filenames in the page queue. Any file which
695 ** starts with a 'P' is considered a page. All other files are ignored.
696 ** Files containing pages are passed to read_page() to be read.
697 **
698 ** Input:
699 ** bad - a boolean flag indicating whether to read bad pages
700 **
701 ** Returns:
702 ** a linked list of pages, linked in the order they are read
703 */
704 PAGE *
read_queue(int bad)705 read_queue(int bad)
706 {
707 struct dirent *entry;
708 PAGE *head;
709 PAGE *curr;
710 PAGE *tmp;
711 DIR *dirp;
712
713
714 head = NULL;
715 curr = NULL;
716
717 if ((dirp = opendir(".")) == NULL) {
718 qpage_log(LOG_ERR, "cannot read current directory");
719 return(NULL);
720 }
721
722 while ((entry = readdir(dirp)) != NULL) {
723 if (entry->d_name[0] != 'P') {
724 if (bad == FALSE || entry->d_name[0] != 'B')
725 continue;
726 }
727
728 if ((tmp = read_page(entry->d_name)) != NULL) {
729 if (head == NULL)
730 head = tmp;
731 else
732 curr->next = tmp;
733
734 curr = tmp;
735 }
736 }
737
738 (void)closedir(dirp);
739
740 return(head);
741 }
742
743
744 /*
745 ** showqueue()
746 **
747 ** This function shows the pages currently in the queue.
748 **
749 ** Input:
750 ** nothing
751 **
752 ** Returns:
753 ** the number of pages in the page queue
754 */
755 int
showqueue(void)756 showqueue(void)
757 {
758 PAGE *pagelist;
759 PAGE *tmp;
760 rcpt_t *rcpt;
761 int count;
762 time_t now;
763
764
765 count = 0;
766 now = time(NULL);
767
768 pagelist = read_queue(TRUE);
769
770 for (tmp=pagelist; tmp; tmp=tmp->next) {
771 printf("ID=%s%s\n", tmp->messageid,
772 tmp->filename[0] == 'B' ? " (*** bad page ***)" : "");
773
774 printf("\t Date: %s\n", my_ctime(&tmp->created));
775 printf("\t File: %s\n", tmp->filename);
776 printf("\t From: %s\n", tmp->from ? tmp->from : "[anonymous]");
777
778 if (tmp->hostname)
779 printf("\t Host: %s\n", tmp->hostname);
780
781 printf("\tLength: %d bytes\n", strlen(tmp->message));
782
783 for (rcpt=tmp->rcpts; rcpt; rcpt=rcpt->next) {
784 printf("\t To: pager=%s", rcpt->pager);
785
786 if (rcpt->tries)
787 printf(", goodtries/tries=%d/%d",
788 rcpt->goodtries, rcpt->tries);
789
790 if (rcpt->holduntil > now)
791 printf(", holduntil=%s",
792 my_ctime(&rcpt->holduntil));
793
794 if (rcpt->flags & F_SENT)
795 printf(", status=SENT");
796
797 if (rcpt->flags & F_FAILED)
798 printf(", status=FAILED");
799
800 printf("\n");
801 }
802
803 if (tmp->next)
804 printf("\n");
805
806 count++;
807 }
808
809 if (count == 0)
810 printf("The page queue is empty.\n");
811
812 return(count);
813 }
814
815
816 /*
817 ** runqueue()
818 **
819 ** This function reads the pages in the page queue, sorts them into
820 ** a job list, sends the jobs, and writes pages back to the page queue.
821 ** The number of pages remaining in the page queue is returned. This
822 ** number includes pages which were not sent because of retry counts
823 ** being exceeded.
824 **
825 ** Input:
826 ** nothing
827 **
828 ** Returns:
829 ** an integer status (0=success)
830 **
831 ** Note:
832 ** This function creates memory leaks. Fortunately,
833 ** it is only called from within a sub-process and
834 ** the memory will be reclaimed by the operating
835 ** system when the process exits.
836 */
837 int
runqueue(void)838 runqueue(void)
839 {
840 PAGE *pagelist;
841 PAGE *tmp;
842 job_t *joblist;
843 int count;
844 int lock;
845
846
847 count = 0;
848 joblist = NULL;
849
850 /*
851 ** lock the page queue
852 */
853 if ((lock = lock_queue()) < 0)
854 return(-1);
855
856 pagelist = read_queue(FALSE);
857
858 if (Debug)
859 qpage_log(LOG_DEBUG, "getting job list");
860
861 for (tmp=pagelist; tmp; tmp=tmp->next) {
862 if ((tmp->flags & F_BADPAGE) == 0)
863 count += insert_jobs(&joblist, tmp);
864 }
865
866 if (Debug)
867 qpage_log(LOG_DEBUG, "pending jobs: %d", count);
868
869 if (joblist) {
870 if (Debug)
871 qpage_log(LOG_DEBUG, "sending job list");
872
873 send_pages(joblist);
874 }
875
876 /*
877 ** We must write the pages back out regardless of whether
878 ** any jobs were processed. This is because we may have
879 ** changed some flags (e.g. F_FAILED) and we need to ensure
880 ** that the changes are seen on the next iteration.
881 */
882 if (Debug && pagelist)
883 qpage_log(LOG_DEBUG, "writing job list");
884
885 for (tmp=pagelist; tmp; tmp=tmp->next) {
886 if (write_page(tmp, FALSE) < 0) {
887 qpage_log(LOG_WARNING, "lost page id=%s",
888 tmp->messageid);
889 }
890 }
891
892 /*
893 ** unlock the page queue
894 */
895 (void)close(lock);
896
897 return(0);
898 }
899