#include "qpage.h" /* ** global variables */ #ifndef lint static char sccsid[] = "@(#)queue.c 1.22 07/07/98 tomiii@qpage.org"; #endif /* ** insert_jobs() ** ** This function inserts a page into the job list. Since each page ** may contain multiple recipients each with a possibly different ** paging service, each recipient is considered a separate job. The ** job list is sorted first by service name and then by level within ** each service. This allows all pages for a particular service to ** be sent with one phone call (if possible). Note that each job ** simply contains pointers to the respective elements of a page ** structure. Therefore, a job in this context does not actually ** contain any data, only pointers to data. ** ** Input: ** joblist - a pointer to the head node in the list ** p - the new page to be added to the list. ** ** Returns: ** the number of pending jobs in the specified page ** ** Note: ** Jobs scheduled for the future (i.e. seen here but ** not added to the job list) are counted in the number ** returned by this function. */ int insert_jobs(job_t **joblist, PAGE *p) { job_t *curr; job_t *prev; job_t *tmp; rcpt_t *rcpt; service_t *service; pager_t *pager; int jobcount; int i; jobcount = 0; for (rcpt=p->rcpts; rcpt; rcpt=rcpt->next) { /* ** skip pages we've already sent */ if (rcpt->flags & F_SENT) continue; /* ** skip pages which are scheduled for the future */ if (rcpt->holduntil > time(NULL)) { if (Debug || Interactive) qpage_log(LOG_DEBUG, "skipping %s until %s", rcpt->pager, my_ctime(&rcpt->holduntil)); jobcount++; continue; } pager = lookup(Pagers, rcpt->pager); /* ** If this is a raw pagerid we need to kludge something */ if (pager == NULL && (rcpt->flags & F_RAWPID)) { pager = (void *)malloc(sizeof(*pager)); (void)memset((char *)pager, 0, sizeof(*pager)); pager->name = strdup(rcpt->pager); pager->pagerid = strdup(rcpt->pager); pager->flags = rcpt->flags; } /* ** "pager" better not be NULL at this point */ if (pager == NULL) { qpage_log(LOG_ERR, "no such pager %s", rcpt->pager); continue; } /* ** If they specified a coverage, use it, otherwise ** use the default coverage for this pager. */ if (rcpt->coverage) service = lookup(Services, rcpt->coverage); else service = pager->service; if (service == NULL) { qpage_log(LOG_ERR, "no such service %s", rcpt->coverage); continue; } #ifdef REJECT_IS_FAILURE /* ** do not retry rejected pages */ if (rcpt->flags & F_REJECT) { rcpt->flags |= F_FAILED; continue; } #endif /* ** limit retries to the number specified by the paging service */ if (rcpt->goodtries >= service->maxtries) { rcpt->flags |= F_FAILED; qpage_log(LOG_ERR, "too many retries for %s in %s", rcpt->pager, p->filename); continue; } /* ** build a new job for this recipient */ tmp = (void *)malloc(sizeof(*tmp)); tmp->next = NULL; tmp->p = p; tmp->rcpt = rcpt; tmp->service = service; tmp->pager = pager; if (Debug) { qpage_log(LOG_DEBUG, "pager=%s, pagerid=%s, service=%s", pager->name, pager->pagerid, service->name); } curr = *joblist; prev = NULL; /* ** Scan through the job list to find an appropriate ** place to insert this recipient. */ while (curr) { i = strcmp(curr->service->name, tmp->service->name); if (i == 0) i = curr->rcpt->level - tmp->rcpt->level; if (i == 0) i = curr->p->created - tmp->p->created; if (i>0) break; prev = curr; curr = curr->next; } if (prev == NULL) { /* ** insert the job at the beginning of the list */ tmp->next = *joblist; *joblist = tmp; } else { /* ** insert the job somewhere after the first node */ tmp->next = prev->next; prev->next = tmp; } jobcount++; } /* ** At this point, a jobcount of zero means all the recipients ** of this page fall into one of these two categories: ** ** - we already successfully sent the page ** - we failed to send the page and we should stop trying ** ** If there are no valid jobs here now, there won't be any in ** the future so let's nuke this page. */ if (jobcount == 0) p->flags |= F_BADPAGE; return(jobcount); } /* ** read_page() ** ** This function reads a page from the page queue. ** ** Input: ** file - the filename to read from ** ** Returns: ** a page structure, or NULL on failure */ PAGE * read_page(char *file) { rcpt_t *tmp; FILE *fp; PAGE *p; time_t holduntil; time_t lasttry; char keyword[255]; char coverage[255]; char status[255]; char name[255]; char msgid[255]; char hostname[257]; char *buf; char *ptr; int tries; int goodtries; int version; int buflen; int gotmarker; int line; int level; int flags; int bytes; int n; gotmarker = 0; line = 0; coverage[0] = '\0'; level = DEFAULT_LEVEL; holduntil = 0; lasttry = 0; flags = 0; tries = 0; goodtries = 0; if ((fp = fopen(file, "r")) == NULL) { qpage_log(LOG_NOTICE, "cannot open file %s for reading", file); return(NULL); } if (lock_file(fileno(fp), O_RDONLY, TRUE) < 0) { qpage_log(LOG_ERR, "cannot lock %s: %s", file, strerror(errno)); return(NULL); } p = (void *)malloc(sizeof(*p)); (void)memset((char *)p, 0, sizeof(*p)); buf = (void *)malloc(BUFCHUNKSIZE); buflen = BUFCHUNKSIZE; while (fgets(buf, buflen, fp)) { line++; if ((ptr = strchr(buf, '\n')) == NULL) { qpage_log(LOG_ERR, "short read (this should never happen)"); } else *ptr = '\0'; if (sscanf(buf, "%s %n", keyword, &n) != 1) { qpage_log(LOG_ERR, "no keyword (this should never happen)"); continue; } switch (keyword[0]) { case '#': /* comment */ break; case '-': /* end-of-recipients marker */ gotmarker++; break; case 'B': /* bytes (size of message) */ (void)sscanf(&buf[n], "%d", &bytes); if (bytes > buflen) { buf = (void *)realloc(buf, bytes); buflen = bytes; } break; case 'C': /* created/coverage */ if (gotmarker) { (void)sscanf(&buf[n], "%ld", &p->created); } else { (void)sscanf(&buf[n], "%s", coverage); } break; case 'F': /* from/flags */ if (gotmarker) { /* ** We can't use sscanf() here ** because there may be embedded ** whitespace in the CALLerid ** information. */ while (buf[n] && isspace(buf[n])) n++; p->from = strdup(&buf[n]); } else { (void)sscanf(&buf[n], "%d", &flags); } break; case 'G': /* goodtries */ (void)sscanf(&buf[n], "%d", &goodtries); break; case 'H': /* hostname/holduntil */ if (gotmarker) { (void)sscanf(&buf[n], "%s", hostname); p->hostname = strdup(hostname); } else { (void)sscanf(&buf[n], "%ld", &holduntil); } break; case 'I': /* ident */ p->ident = strdup(&buf[n]); break; case 'L': /* lasttry */ (void)sscanf(&buf[n], "%ld", &lasttry); break; case 'M': /* message */ p->message = strdup(&buf[n]); break; case 'P': /* pager */ name[0] = '\0'; (void)sscanf(&buf[n], "%s", name); tmp = (void *)malloc(sizeof(*tmp)); (void)memset((char *)tmp, 0, sizeof(*tmp)); tmp->next = p->rcpts; p->rcpts = tmp; tmp->pager = strdup(name); if (coverage[0]) tmp->coverage = strdup(coverage); tmp->holduntil = holduntil; tmp->lasttry = lasttry; tmp->goodtries = goodtries; tmp->tries = tries; tmp->level = level; tmp->flags = flags; coverage[0] = '\0'; level = DEFAULT_LEVEL; holduntil = 0; lasttry = 0; goodtries = 0; tries = 0; flags = 0; break; case 'S': /* status/servicelevel */ if (gotmarker) { (void)sscanf(&buf[n], "%s", status); } else { (void)sscanf(&buf[n], "%d", &level); } break; case 'T': /* tries */ (void)sscanf(&buf[n], "%d", &tries); break; case 'U': /* unique id */ (void)sscanf(&buf[n], "%s", msgid); p->messageid = strdup(msgid); break; case 'V': /* version */ (void)sscanf(&buf[n], "%d", &version); if (version != 3) { qpage_log(LOG_ERR, "FATAL ERROR: incompatible version of queue file"); clear_page(p, FALSE); free(p); return(NULL); } break; default: qpage_log(LOG_NOTICE, "%s line %d: unknown meaning (%s)", file, line, buf); break; } } free(buf); (void)fclose(fp); p->filename = strdup(file); if (p->messageid == NULL) p->messageid = strdup("[none]"); return(p); } /* ** write_page() ** ** This function writes a page to the page queue. If the filename ** field of the page structure is not null, it is assumed to point ** to the name of the file the page was read from. In this case, ** if the page has been successfully delivered to all the recipients, ** the file is removed. Otherwise the page is written back to that ** file. If the filename field of the page structure is NULL, this ** function assumes this is a new page. A new filename is created ** based on the current time. ** ** Input: ** p - a page structure ** new - whether this is a new page ** ** Returns: ** an integer status code: ** 0 = queue file was removed/renamed ** 1 = page was written to queue file ** -1 = error occurred; status unknown */ int write_page(PAGE *p, int new) { rcpt_t *tmp; FILE *fp; char filename[255]; char *badname; int fd; int ext; int doit; /* ** send e-mail notification (if needed) of the page status */ if (new == FALSE) { if (Administrator) notify_administrator(p); if (p->from) notify_submitter(p); } /* ** first verify whether this page should be written back or not */ doit = FALSE; for (tmp=p->rcpts; tmp; tmp=tmp->next) { if (tmp->flags & F_SENT) continue; doit = TRUE; } if (doit == FALSE) { if (p->filename) { if (Debug) qpage_log(LOG_DEBUG, "unlinking %s", p->filename); if (unlink(p->filename) < 0) qpage_log(LOG_WARNING, "unlink failed for %s: %s", p->filename, strerror(errno)); } return(0); } if (p->filename == NULL) { ext = 0; do { (void)sprintf(filename, "P%lu.%03u", time(NULL), ext++); fd = open(filename, O_CREAT|O_EXCL|O_WRONLY, 0644); if (fd >= 0) break; if (errno != EEXIST) { qpage_log(LOG_NOTICE, "cannot create %s: %s", filename, strerror(errno)); } } while (ext < 100); if (fd < 0) { qpage_log(LOG_ERR, "cannot create file %s: %s", filename, strerror(errno)); return(-1); } p->filename = strdup(filename); } else { if ((fd = open(p->filename, O_WRONLY, 0644)) < 0) { qpage_log(LOG_ERR, "cannot open file %s: %s", p->filename, strerror(errno)); return(-1); } } if (lock_file(fd, O_RDWR, TRUE) < 0) { qpage_log(LOG_ERR, "cannot lock %s: %s", p->filename, strerror(errno)); return(-1); } /* ** explicitly truncate the file now that it's locked */ (void)ftruncate(fd, (off_t)0); if ((fp = fdopen(fd, "w")) == NULL) { qpage_log(LOG_ERR, "cannot reopen file %s", p->filename); (void)close(fd); return(-1); } fprintf(fp, "Version: 3\n"); for (tmp=p->rcpts; tmp; tmp=tmp->next) { if (tmp->coverage) fprintf(fp, "Coverage: %s\n", tmp->coverage); if (tmp->holduntil) fprintf(fp, "Holduntil: %lu %s\n", tmp->holduntil, my_ctime(&tmp->holduntil)); if (tmp->lasttry) fprintf(fp, "Lasttry: %lu %s\n", tmp->lasttry, my_ctime(&tmp->lasttry)); fprintf(fp, "Tries: %d\n", tmp->tries); if (tmp->goodtries) fprintf(fp, "Goodtries: %d\n", tmp->goodtries); if (tmp->level != DEFAULT_LEVEL) fprintf(fp, "Servicelevel: %d\n", tmp->level); if (tmp->flags) { fprintf(fp, "Flags: %d (", tmp->flags); if (tmp->flags & F_SENT) fprintf(fp, " F_SENT"); if (tmp->flags & F_FAILED) fprintf(fp, " F_FAILED"); if (tmp->flags & F_BUSY) fprintf(fp, " F_BUSY"); if (tmp->flags & F_NOCARRIER) fprintf(fp, " F_NOCARRIER"); if (tmp->flags & F_NOMODEM) fprintf(fp, " F_NOMODEM"); if (tmp->flags & F_FORCED) fprintf(fp, " F_FORCED"); if (tmp->flags & F_NOPROMPT) fprintf(fp, " F_NOPROMPT"); if (tmp->flags & F_UNKNOWN) fprintf(fp, " F_UNKNOWN"); if (tmp->flags & F_REJECT) fprintf(fp, " F_REJECT"); if (tmp->flags & F_RAWPID) fprintf(fp, " F_RAWPID"); if (tmp->flags & F_SENDMAIL) fprintf(fp, " F_SENDMAIL"); if (tmp->flags & F_SENTMAIL) fprintf(fp, " F_SENTMAIL"); if (tmp->flags & F_SENTADMIN) fprintf(fp, " F_SENTADMIN"); fprintf(fp, " )\n"); } fprintf(fp, "Pager: %s\n", tmp->pager); } fprintf(fp, "-\n"); if (p->from) fprintf(fp, "From: %s\n", p->from); if (p->ident) fprintf(fp, "Ident: %s\n", p->ident); if (p->hostname) fprintf(fp, "Hostname: %s\n", p->hostname); /* ** Tell the reader how big the buffer has to be in order to read ** the next line. This includes the message length plus the word ** "Message:" plus a space, a newline, and a null character. */ fprintf(fp, "Bytes: %d\n", strlen(p->message)+11); fprintf(fp, "Message: %s\n", p->message); fprintf(fp, "Created: %lu %s\n", p->created, my_ctime(&p->created)); fprintf(fp, "UniqueID: %s\n", p->messageid); if (p->status) fprintf(fp, "Status: %s\n", p->status); /* ** Before we close the file (which releases the lock), we ** should check to see if this page is worth reading again ** in the future. If not, rename the file to start with 'B'. */ badname = NULL; if (p->flags & F_BADPAGE) { badname = strdup(p->filename); badname[0] = 'B'; qpage_log(LOG_NOTICE, "renaming bad page to %s", badname); if (rename(p->filename, badname) < 0) { qpage_log(LOG_WARNING, "cannot rename %s to %s: %s", p->filename, badname, strerror(errno)); } } if (fclose(fp)) { qpage_log(LOG_WARNING, "error writing queue file: %s", strerror(errno)); return(-1); } if (badname) { free(badname); return(0); } return(1); } /* ** read_queue() ** ** This function reads the filenames in the page queue. Any file which ** starts with a 'P' is considered a page. All other files are ignored. ** Files containing pages are passed to read_page() to be read. ** ** Input: ** bad - a boolean flag indicating whether to read bad pages ** ** Returns: ** a linked list of pages, linked in the order they are read */ PAGE * read_queue(int bad) { struct dirent *entry; PAGE *head; PAGE *curr; PAGE *tmp; DIR *dirp; head = NULL; curr = NULL; if ((dirp = opendir(".")) == NULL) { qpage_log(LOG_ERR, "cannot read current directory"); return(NULL); } while ((entry = readdir(dirp)) != NULL) { if (entry->d_name[0] != 'P') { if (bad == FALSE || entry->d_name[0] != 'B') continue; } if ((tmp = read_page(entry->d_name)) != NULL) { if (head == NULL) head = tmp; else curr->next = tmp; curr = tmp; } } (void)closedir(dirp); return(head); } /* ** showqueue() ** ** This function shows the pages currently in the queue. ** ** Input: ** nothing ** ** Returns: ** the number of pages in the page queue */ int showqueue(void) { PAGE *pagelist; PAGE *tmp; rcpt_t *rcpt; int count; time_t now; count = 0; now = time(NULL); pagelist = read_queue(TRUE); for (tmp=pagelist; tmp; tmp=tmp->next) { printf("ID=%s%s\n", tmp->messageid, tmp->filename[0] == 'B' ? " (*** bad page ***)" : ""); printf("\t Date: %s\n", my_ctime(&tmp->created)); printf("\t File: %s\n", tmp->filename); printf("\t From: %s\n", tmp->from ? tmp->from : "[anonymous]"); if (tmp->hostname) printf("\t Host: %s\n", tmp->hostname); printf("\tLength: %d bytes\n", strlen(tmp->message)); for (rcpt=tmp->rcpts; rcpt; rcpt=rcpt->next) { printf("\t To: pager=%s", rcpt->pager); if (rcpt->tries) printf(", goodtries/tries=%d/%d", rcpt->goodtries, rcpt->tries); if (rcpt->holduntil > now) printf(", holduntil=%s", my_ctime(&rcpt->holduntil)); if (rcpt->flags & F_SENT) printf(", status=SENT"); if (rcpt->flags & F_FAILED) printf(", status=FAILED"); printf("\n"); } if (tmp->next) printf("\n"); count++; } if (count == 0) printf("The page queue is empty.\n"); return(count); } /* ** runqueue() ** ** This function reads the pages in the page queue, sorts them into ** a job list, sends the jobs, and writes pages back to the page queue. ** The number of pages remaining in the page queue is returned. This ** number includes pages which were not sent because of retry counts ** being exceeded. ** ** Input: ** nothing ** ** Returns: ** an integer status (0=success) ** ** Note: ** This function creates memory leaks. Fortunately, ** it is only called from within a sub-process and ** the memory will be reclaimed by the operating ** system when the process exits. */ int runqueue(void) { PAGE *pagelist; PAGE *tmp; job_t *joblist; int count; int lock; count = 0; joblist = NULL; /* ** lock the page queue */ if ((lock = lock_queue()) < 0) return(-1); pagelist = read_queue(FALSE); if (Debug) qpage_log(LOG_DEBUG, "getting job list"); for (tmp=pagelist; tmp; tmp=tmp->next) { if ((tmp->flags & F_BADPAGE) == 0) count += insert_jobs(&joblist, tmp); } if (Debug) qpage_log(LOG_DEBUG, "pending jobs: %d", count); if (joblist) { if (Debug) qpage_log(LOG_DEBUG, "sending job list"); send_pages(joblist); } /* ** We must write the pages back out regardless of whether ** any jobs were processed. This is because we may have ** changed some flags (e.g. F_FAILED) and we need to ensure ** that the changes are seen on the next iteration. */ if (Debug && pagelist) qpage_log(LOG_DEBUG, "writing job list"); for (tmp=pagelist; tmp; tmp=tmp->next) { if (write_page(tmp, FALSE) < 0) { qpage_log(LOG_WARNING, "lost page id=%s", tmp->messageid); } } /* ** unlock the page queue */ (void)close(lock); return(0); }