Files
seta75D d6fe8fe829 Init
2021-10-11 22:19:34 -03:00

1722 lines
39 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
static char sccsid[] = "@(#)19 1.17.2.2 src/bos/usr/lib/sendmail/queue.c, cmdsend, bos41J, 9510A_all 2/24/95 10:45:52";
/*
* COMPONENT_NAME: CMDSEND queue.c
*
* FUNCTIONS: MSGSTR, dowork, freewq, orderq, plist, printqueue,
* prtq, queuename, queueup, readqf, runqueue, trunqueue,
* unlockqueue, workcmpf, getctluser, setctluser, clrctluser,
* setctladdr
*
* ORIGINS: 10 26 27
*
* (C) COPYRIGHT International Business Machines Corp. 1985, 1989
* All Rights Reserved
* Licensed Materials - Property of IBM
*
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*
** Sendmail
** Copyright (c) 1983 Eric P. Allman
** Berkeley, California
**
** Copyright (c) 1983 Regents of the University of California.
** All rights reserved. The Berkeley software License Agreement
** specifies the terms and conditions for redistribution.
**
*/
#include <nl_types.h>
#include "sendmail_msg.h"
extern nl_catd catd;
#define MSGSTR(n,s) catgets(catd,MS_SENDMAIL,n,s)
# include <stdio.h>
# include <ctype.h>
# include <time.h>
# include <memory.h>
# include <fcntl.h>
# include "conf.h"
# include "useful.h"
# include <sys/types.h>
# include <netinet/in.h>
# include "sysexits.h"
# include "sendmail.h"
# include <sys/stat.h>
# include <sys/dir.h>
# include <unistd.h> /* for access () call */
# include <errno.h>
# include <string.h>
# include <sys/access.h> /* security */
# include <sys/file.h>
# include <pwd.h>
void exit ();
void qsort ();
int putbody();
int workcmpf();
long atol();
char *fgetfolded();
char *queuename ();
char *xalloc ();
char *safestr ();
EVENT *setevent ();
/*
** Work queue.
*/
struct work
{
char *w_name; /* name of control file */
long w_pri; /* priority of message, see below */
long w_ctime; /* creation time of message */
char *w_df; /* name of data file */
struct work *w_next; /* next in queue */
};
typedef struct work WORK;
static WORK *WorkQ; /* list of things to be done */
static WORK *freewq (WORK *);
static orderq (int);
static int prtq (char *, int);
static int plist (char *, WORK **, int);
extern int Sid; /* semaphore id for cleanup */
extern int Scount; /* max count of users */
/*
** QUEUEUP -- queue a message up for future transmission.
**
** Parameters:
** e -- the envelope to queue up.
** queueall -- if TRUE, queue all addresses, rather than
** just those with the QQUEUEUP flag set.
** announce -- if TRUE, tell when you are queueing up.
**
** Returns:
** none.
** The only report of error is through syserr. This is bad.
**
** Side Effects:
** The current request are saved in a control file.
*/
queueup(e, queueall, announce)
register ENVELOPE *e;
int queueall;
int announce;
{
char *tf;
char *qf;
char buf[MAXLINE];
register FILE *tfp;
register HDR *h;
register ADDRESS *q;
MAILER nullmailer;
int fd, ret;
char *ctluser, *getctluser();
/*
** Create control file.
*/
/*
* Should we do an unlink first?
* Then if main umasked properly we wouldn't need the chmod.
*/
tf = newstr(queuename(e, 't'));
tfp = fopen(tf, "w");
if (tfp == NULL)
{
syserr(MSGSTR(QU_ETEMP, "queueup: cannot create temp file %s"), tf); /*MSG*/
return;
}
(void) acl_set (tf, R_ACC | W_ACC, R_ACC | W_ACC, NO_ACC);
#ifdef DEBUG
if (tTd(40, 1))
(void) printf("queueing %s\n", e->e_id);
#endif DEBUG
/*
** If there is no data file yet, create one.
*/
if (e->e_df == NULL)
{
register FILE *dfp;
e->e_df = newstr(queuename(e, 'd'));
dfp = fopen(e->e_df, "w");
if (dfp == NULL)
{
syserr(MSGSTR(QU_CREAT, "queueup: cannot create %s"), e->e_df); /*MSG*/
(void) fclose(tfp);
return;
}
(void) acl_set(e->e_df, R_ACC | W_ACC, R_ACC | W_ACC, NO_ACC);
(*e->e_putbody)(dfp, ProgMailer, e, e->e_btype);
(void) fclose(dfp);
e->e_putbody = putbody;
}
/*
** Output future work requests.
** Priority and creation time should be first, since
** they are required by orderq.
*/
/**/
/* What about checking the return codes on the following fprintf's? */
/* output message priority */
fprintf(tfp, "P%ld\n", e->e_msgpriority);
/* output creation time */
fprintf(tfp, "T%ld\n", e->e_ctime);
/* output name of data file */
fprintf(tfp, "D%s\n", e->e_df);
/* output body type if BT_NLS or BT_ESC */
if (e->e_btype == BT_ESC)
fprintf(tfp, "Be type=NLesc\n");
else if (e->e_btype == BT_NLS)
fprintf(tfp, "Bn type=NLS\n");
/* output body type */
if (e->e_btype == BT_MC)
fprintf(tfp, "Bs type=%s\n", MailCode);
else if (e->e_btype == BT_NC)
fprintf(tfp, "Bj type=%s\n", NetCode);
/* message from envelope, if it exists */
if (e->e_message != NULL)
fprintf(tfp, "M%s\n", safestr(e->e_message));
/* output name of sender */
fprintf(tfp, "S%s\n", safestr(e->e_from.q_paddr));
/* output list of recipient addresses */
for (q = e->e_sendqueue; q != NULL; q = q->q_next)
{
if (queueall ? !bitset(QDONTSEND, q->q_flags) :
bitset(QQUEUEUP, q->q_flags))
{
if ((ctluser = getctluser(q)) != NULL)
fprintf(tfp, "C%s\n", safestr(ctluser));
fprintf(tfp, "R%s\n", safestr(q->q_paddr));
if (announce)
{
e->e_to = q->q_paddr;
message(Arpa_Info, MSGSTR(QU_QUARPA, "queued")); /*MSG*/
if (LogLevel > 4)
logdelivery(MSGSTR(QU_QUARPA, "queued")); /*MSG*/
e->e_to = NULL;
}
#ifdef DEBUG
if (tTd(40, 1))
{
(void) printf("queueing ");
printaddr(q, FALSE);
}
#endif DEBUG
}
}
/* output list of error recipients */
for (q = e->e_errorqueue; q != NULL; q = q->q_next)
{
if (!bitset(QDONTSEND, q->q_flags))
{
if ((ctluser = getctluser(q)) != NULL)
fprintf(tfp, "C%s\n", safestr(ctluser));
fprintf(tfp, "E%s\n", safestr(q->q_paddr));
}
}
/*
** Output headers for this message.
** Expand macros completely here. Queue run will deal with
** everything as absolute headers.
** All headers that must be relative to the recipient
** can be cracked later.
** We set up a "null mailer" -- i.e., a mailer that will have
** no effect on the addresses as they are output.
*/
(void) memset((char *) &nullmailer, 0, sizeof nullmailer);
nullmailer.m_r_rwset = nullmailer.m_s_rwset = -1;
nullmailer.m_eol = "\n";
define('g', "\001f", e);
for (h = e->e_header; h != NULL; h = h->h_link)
{
/* don't output null headers */
if (h->h_value == NULL || h->h_value[0] == '\0')
continue;
/* don't output resent headers on non-resent messages */
if (bitset(H_RESENT, h->h_flags) && !bitset(EF_RESENT, e->e_flags))
continue;
/* output this header */
fprintf(tfp, "H");
/* if conditional, output the set of conditions */
if (!bitzerop(h->h_mflags) && bitset(H_CHECK|H_ACHECK, h->h_flags))
{
int j;
(void) putc('?', tfp);
for (j = '\0'; j <= '\177'; j++)
if (bitnset(j, h->h_mflags))
(void) putc(j, tfp);
(void) putc('?', tfp);
}
/* output the header: expand macros, convert addresses */
if (bitset(H_DEFAULT, h->h_flags))
{
(void) expand(h->h_value, buf, &buf[sizeof buf], e);
fprintf(tfp, "%s: %s\n", h->h_field, buf);
}
else if (bitset(H_FROM|H_RCPT, h->h_flags))
{
commaize(h, h->h_value, tfp, bitset(EF_OLDSTYLE,
e->e_flags), &nullmailer);
}
else
fprintf(tfp, "%s: %s\n", h->h_field, h->h_value);
}
/*
** Clean up.
*/
(void) fclose(tfp);
qf = queuename(e, 'q');
if (tf != NULL)
{
(void) unlink(qf);
if (rename(tf, qf) < 0)
syserr(MSGSTR(QU_ERENAME, "cannot rename(%s, %s), df=%s"), tf, qf, e->e_df); /*MSG*/
errno = 0;
}
# ifdef LOG
/* save log info */
if (LogLevel > 15)
syslog(LOG_DEBUG, "%s: queueup, qf=%s, df=%s\n", e->e_id, qf, e->e_df);
# endif LOG
}
/*
** TRUNQUEUE -- handle timed queue runs. This is called from daemon/queue
** background (and possibly its clock signal handler).
**
** Parameters:
** none.
**
** Returns:
** none.
**
** Side Effects:
** Calls runqueue.
*/
trunqueue ()
{
int pid;
/*
* When the clock event occurs, trunqueue is started
* from the signal handler. It forks, then returns,
* terminating the signal handler.
*
* Trunqueue is called first at base level by main.c.
*/
if (QueueIntvl != 0)
(void) setevent (QueueIntvl, trunqueue, TRUE);
/*
* If no work will ever be selected, don't even bother reading
* the queue.
*
* How can the value of shouldqueue EVER be TRUE?
* Look at the code in it!
*/
if (shouldqueue(-100000000L))
{
if (Verbose)
(void) printf(MSGSTR(QU_SKIP, "Skipping queue run -- load average too high\n")); /*MSG*/
return;
}
/*
* Make child. If fails, then forget it. The alarm will trigger
* another attempt later. This used to be a fork with retry
* to try to overcome lack of system resources, but now it just
* waits for the next queue interval. Nothing is lost but time.
*/
pid = fork();
if (pid != 0) /* parent or failed */
return;
/*
* "Wait" on sema4 to indicate that we are preparing to enter queue.
* We can wait here if a clean is in progress. Process exit
* releases our "wait" operation. This sets the semadj value
* in this child process so that the semaphore is adjusted
* properly upon process exit. Runqueue may fiddle with the
* semaphore, also. This wait is done on the child side of
* the above fork.
*
* All failures cause syserr's. However, it is assumed that once
* semop's fail, they always fail. This means that no queue
* cleaning will take place in orderq.
*/
(void) semwait (Sid, 1, 0, 0);
#ifdef DEBUG
if (tTd (0, 109))
exit (99);
#endif DEBUG
runqueue ();
/* exit without the usual cleanup */
exit(ExitStat);
}
/*
** RUNQUEUE -- run the jobs in the queue.
**
** Gets the stuff out of the queue in some presumably logical
** order and processes it.
**
** Parameters:
** none.
**
** Returns:
** none.
**
** Side Effects:
** runs things in the mail queue.
*/
runqueue ()
{
int i;
/*
* If no work will ever be selected, don't even bother reading
* the queue.
*
* How can the value of shouldqueue EVER be TRUE?
* Look at the code in it!
*/
if (shouldqueue(-100000000L))
{
if (Verbose)
(void) printf(MSGSTR(QU_SKIP, "Skipping queue run -- load average too high\n")); /*MSG*/
return;
}
setproctitle(MSGSTR(QU_RUNQ, "running queue: %s"), QueueDir); /*MSG*/
# ifdef LOG
if (LogLevel > 11)
syslog(LOG_DEBUG, "runqueue %s, pid=%d", QueueDir, getpid());
# endif LOG
/*
* Release any resources used by the daemon code.
*
* Right now this just assures that the daemon socket (in this
* instantiation of sendmail) is closed. If we have just been
* forked from the daemon, then our new instantiation is still
* doing the daemon thing and should be stopped.
*
* This code is either executed unforked (-q with no time specs)
* or forked (-q with time specs), but may or may not be from daemon.
*/
/**/ clrdaemon();
/*
** Make sure the alias database is open.
*/
if (i = openaliases(AliasFile))
{
ExitStat = i; /* inaccessible database */
#ifdef LOG
syslog(LOG_INFO, MSGSTR(QU_EALIAS, "alias file \"%s\" data base open failure"), AliasFile); /*MSG*/
#endif LOG
return;
}
/* order the existing work requests */
(void) orderq(FALSE);
/* process them one at a time */
while (WorkQ != NULL)
{
WORK *w = WorkQ;
WorkQ = WorkQ->w_next;
dowork(w);
(void) freewq (w);
}
}
/*
** ORDERQ -- order the work queue.
**
** Parameters:
** doall -- if set, include everything in the queue (even
** the jobs that cannot be run because the load
** average is too high). Otherwise, exclude those
** jobs.
**
** Returns:
** The number of request in the queue (not necessarily
** the number of requests in WorkQ however).
**
** Side Effects:
** Sets WorkQ to the queue of available work, in order.
*/
# define NEED_P 001
# define NEED_T 002
# define NEED_D 004
static
orderq(int doall)
{
struct dirent *dirent;
WORK we;
WORK *w, **wlist, **wl, *wq, **wqp;
DIR *fd;
register int i;
int wn, pass2, err;
int clean;
if (WorkQ != NULL)
{
syserr (MSGSTR(QU_WORK, "orderq: WorkQ exists!")); /*MSG*/
exit (EX_SOFTWARE);
}
/* open the queue directory */
fd = opendir (".");
if (fd == NULL)
{
syserr(MSGSTR(QU_EOPEN, "orderq: cannot open queue directory %s as \".\""), QueueDir); /*MSG*/
return (0);
}
/*
* See if we do cleanup at this time.
* Attempt to pick up all remaining available "wait"s in the
* semaphore. If successful, no one else is in the queue and
* we may clean. Furthermore, no one else may enter the queue
* till we are through. This also adjusts the process semadj
* value to automatically release all these "wait" counts upon
* process exit for any reason.
*
* It is assumed that once a semop fails, all subsequent semops
* will also fail. All failures are syserr'd, but don't
* otherwise interfere with execution.
*/
clean = 0; /* assume no cleanup */
if (semwait (Sid, Scount - 1, 1, 0) == EX_OK)
{
clean = 1; /* do cleanup */
if (Verbose)
message (0, MSGSTR(QU_CLEAN, "000 Queue Directory \"%s\" will be cleaned"), QueueDir); /*MSG*/
#ifdef DEBUG
if (tTd (2, 1))
(void) printf ("orderq: sema4 allows clean\n");
#endif DEBUG
/*
* You MUST restore the semaphore count before returning from
* this routine if clean is true! Also, exit () will do it
* automatically.
*/
}
#ifdef DEBUG /* sema4 testing */
if (tTd (2, 11))
while (1);
if (tTd (2, 10))
exit (0);
#endif DEBUG
/*
** Read the work directory.
*/
wn = 0;
pass2 = 0;
while (1)
{
err = 0; /* exit signal for inner loop below */
/*
* Position to start of directory (especially for pass 2).
*/
(void) rewinddir (fd);
while (1)
{
FILE *cf;
char lbuf[MAXNAME];
/*
* Read next directory entry
*/
dirent = readdir (fd);
if (dirent == NULL) /* end? */
break;
/*
* Zero inode means this slot is empty.
*/
if (!dirent->d_ino)
continue;
/*
* We are only concerned with files w/ 2nd char 'f'.
*/
if (dirent->d_name[1] != 'f')
continue;
#ifdef DEBUG
if (tTd (40, 1))
(void) printf ("orderq: file %s, pass2 = %d\n",
dirent->d_name, pass2);
#endif DEBUG
/*
* If pass 1.
*/
if (!pass2)
{
int eof;
if (clean) /* if clean mode */
{
/*
* Get rid of [lxt]f* files.
*/
if (dirent->d_name[0] == 'l' ||
dirent->d_name[0] == 'x' ||
dirent->d_name[0] == 't' )
{
#ifdef DEBUG
if (tTd (40, 1))
(void) printf ("orderq: unlink (%s), scratch\n",
dirent->d_name);
#endif DEBUG
(void) unlink (dirent->d_name);
continue;
}
}
/*
* Only continue to look at qf files.
*/
{
if (dirent->d_name[0] != 'q')
continue;
}
/*
* Open the qf* file. Pass 1 only.
*/
#ifdef DEBUG
if (tTd (40, 1))
(void) printf ("orderq: fopen (%s)\n", dirent->d_name);
#endif DEBUG
cf = fopen(dirent->d_name, "r");
if (cf == NULL)
{
syserr(MSGSTR(QU_EOPEN2, "orderq: cannot open %s"), dirent->d_name); /*MSG*/
continue;
}
/*
* Fill in the working entry.
*/
we.w_name = newstr (dirent->d_name);
/*
* Clear cells to be filled in, since corresponding
* items may not be read from the file. Some of this
* old code assumes that qf files may exist which are
* in a state of creation. I don't think this is
* possible any more, but the code and comments are
* left here.
*/
/* make sure jobs in creation don't clog queue */
we.w_pri = 0x7fffffff;
we.w_ctime = 0;
/* clear the 'D' name ptr */
we.w_df = NULL;
/* extract useful information */
i = NEED_P | NEED_T;
if (clean)
i |= NEED_D;
eof = 1;
while (i != 0 && fgets(lbuf, sizeof lbuf, cf) != NULL)
{
eof = 0;
switch (lbuf[0])
{
case 'P':
we.w_pri = atol(&lbuf[1]);
i &= ~NEED_P;
break;
case 'T':
we.w_ctime = atol(&lbuf[1]);
i &= ~NEED_T;
break;
case 'D':
we.w_df = newstr (&lbuf[1]);
we.w_df[strlen (we.w_df)-1] = '\0'; /* fix nl */
i &= ~NEED_D;
break;
}
}
(void) fclose(cf);
/*
* If file was empty, remove it. It can't be in process
* of creation by anybody.
*/
if (clean && eof)
{
#ifdef DEBUG
if (tTd (40, 1))
(void) printf ("orderq: unlink (%s), empty\n",
dirent->d_name);
#endif DEBUG
(void) unlink (dirent->d_name);
continue;
}
/*
* Include it in the work queue.
*/
w = (WORK *) xalloc (sizeof (WORK));
#ifdef DEBUG
if (tTd (40, 1))
(void) printf ("orderq: entry 0x%x: enqueue %s, pri %ld, time %ld, df %s\n",
w, we.w_name, we.w_pri, we.w_ctime, we.w_df);
#endif DEBUG
w->w_name = we.w_name;
w->w_pri = we.w_pri;
w->w_ctime = we.w_ctime;
w->w_df = we.w_df;
w->w_next = WorkQ;
WorkQ = w;
wn++;
}
else /* pass 2 -> clean */
{
/*
* If we are in pass2 of clean mode, make sure that
* any df file is mentioned in a qf entry.
*/
if (dirent->d_name[0] == 'd')
{
for (w = WorkQ; w != NULL; w = w->w_next)
{
if (strcmp (dirent->d_name, w->w_df) == 0)
break;
}
if (w == NULL)
{
#ifdef DEBUG
if (tTd (40, 1))
(void) printf ("orderq: unlink (%s), unused\n",
dirent->d_name);
#endif DEBUG
(void) unlink (dirent->d_name);
}
}
} /* end of else clause for pass 2 */
} /* end inner (dir pass) loop */
/*
* If inner loop broke on error instead of eof, just exit.
* Also, break if we just finished pass2 or don't need it.
*/
if (err || pass2 || !clean)
break;
pass2 = !pass2; /* go around again */
}
(void) closedir (fd);
/*
* If cleaned, remove excess semaphore lock counts. This allows
* other sendmail invocations to proceed. This will adjust
* the process semadj value back so that the single remaining
* lock due to us will be removed on process exit.
* Do nothing special if the unlock fails.
*
* All semop exceptions cause syserr.
*/
if (clean)
{
#ifdef DEBUG
if (tTd (40, 1))
(void) printf ("orderq: remove exclusive clean lock\n");
#endif DEBUG
(void) semsig (Sid, Scount - 1, 0);
}
/*
* Return if no entries were found.
*/
# ifdef DEBUG
if (tTd(40, 1))
(void) prtq ("orderq: preliminary WorkQ", wn);
# endif DEBUG
if (wn <= 0)
return (0);
/*
* Create a sort record pointer list in contiguous memory.
* Check shouldqueue ().
* Release all WorkQ entries that aren't used.
*/
wlist = (WORK **) xalloc (wn * (int) sizeof (WORK *));
wq = WorkQ;
wl = wlist;
while (wq != NULL)
{
if (doall || !shouldqueue (wq->w_pri))
{
*wl++ = wq; /* put entry addr on list */
wq = wq->w_next; /* get address of next one */
}
else
wq = freewq (wq); /* free entry; rtns addr of next one */
}
wn = wl - wlist;
if (wn <= 0)
WorkQ = NULL;
/*
* Return if there are no entries left after shouldqueue.
*/
#ifdef DEBUG
if (tTd (40, 1))
(void) plist ("orderq: wlist before sort", wlist, wn);
#endif DEBUG
if (wn <= 0)
return (0);
/*
* Sort what's there.
*/
qsort((char *) wlist, (unsigned) wn, sizeof (WORK *), workcmpf);
#ifdef DEBUG
if (tTd (40, 1))
(void) plist ("orderq: wlist after sort", wlist, wn);
#endif DEBUG
/*
* Copy sorted links back onto WorkQ entries.
*
* Should be turning it into a list of envelopes here perhaps.
*/
wqp = &WorkQ;
wl = wlist;
for (i = 0; i < wn ; i++)
{
*wqp = *wl++;
wqp = &(*wqp)->w_next;
}
*wqp = NULL; /* terminate the list */
# ifdef DEBUG
if (tTd(40, 1))
(void) prtq ("orderq: final WorkQ", wn);
# endif DEBUG
return (wn);
}
/*
* freewq - free work queue entry. Return link value.
*/
static WORK *
freewq (WORK *w)
{
WORK *nw;
#ifdef DEBUG
if (tTd (40, 1))
(void) printf ("freewq: free control name %s\n", w->w_name);
#endif DEBUG
free (w->w_name);
if (w->w_df != NULL)
{
#ifdef DEBUG
if (tTd (40, 1))
(void) printf ("freewq: free data name %s\n", w->w_df);
#endif DEBUG
free (w->w_df);
}
nw = w->w_next;
#ifdef DEBUG
if (tTd (40, 1))
(void) printf ("freewq: free workq entry 0x%x\n", w);
#endif DEBUG
free ((char *) w);
return (nw);
}
/*
* prtq - print the work queue (debug only)
*/
static int
prtq (char *s, int wn)
{
WORK *w;
(void) printf ("%s (%d entries):\n", s, wn);
for (w = WorkQ; w != NULL; w = w->w_next)
(void) printf("\t%s: pri=%ld\n", w->w_name, w->w_pri);
}
/*
* plist - print the sort list (debug only)
*/
static int
plist (char *s, WORK **wlist, int wn)
{
int i;
(void) printf ("%s (%d entries):\n", s, wn);
for (i=0; i<wn; i++)
(void) printf ("\t0x%x\n", wlist[i]);
}
/*
** WORKCMPF -- compare function for ordering work.
**
** Parameters:
** a -- the first argument.
** b -- the second argument.
**
** Returns:
** -1 if a < b
** 0 if a == b
** +1 if a > b
**
** Side Effects:
** none.
*/
workcmpf(a, b)
WORK **a;
WORK **b;
{
WORK *wa, *wb;
long pa, pb;
wa = *a;
wb = *b;
pa = wa->w_pri + wa->w_ctime;
pb = wb->w_pri + wb->w_ctime;
if (pa == pb)
return (0);
else if (pa > pb)
return (1);
else
return (-1);
}
/*
** DOWORK -- do a work request.
**
** Parameters:
** w -- the work request to be satisfied.
**
** Returns:
** none.
**
** Side Effects:
** The work request is satisfied if possible.
*/
dowork(w)
register WORK *w;
{
register int i;
# ifdef DEBUG
if (tTd(40, 1))
(void) printf("dowork: %s pri %ld\n", w->w_name, w->w_pri);
# endif DEBUG
/*
** Ignore jobs that are too expensive for the moment.
*/
if (shouldqueue(w->w_pri))
{
if (Verbose)
(void) printf(MSGSTR(QU_ESKIP, "\nSkipping %s\n"), w->w_name + 2); /*MSG*/
return;
}
/*
** Fork for work.
*/
/*
* Forking here makes each queue item into a separate process.
* However, we wait for the completion of each process, one-by-one.
* If the fork fails, then we might be in a mode where we go down
* the work queue and fail on fork each time. This is unnecessary,
* but that's how we do it now. No work is lost. It remains in the
* disk queue. Currently there is no error indication. However,
* SOME indication might be nice.
*
* The real purpose of this fork is to conserve memory. A long queue
* run can cause a lot of memory to be taken up because sendmail
* doesn't do a good job of free'ing all memory after use. (See the
* newstr function.) The idea here is that after each queue msg
* the forked child returns, thus releasing all the memory allocated
* to send the message. However, this is probably unnecessary on
* a virtual memory system like AIX. It has the added disadvantage
* of losing information in the symbol table about remote host
* status.
*
* Since the child status isn't used, we may continue in zombiless
* mode and merely perform a global wait to synchronize with child.
*/
i = 0; /* pseudochild */
if (ForkQueueRuns)
{
i = fork();
if (i < 0)
return; /* failed, just drop this one */
if (i == 0) /* child? */
{
/*
* "Wait" on semaphore to indicate that we are preparing to
* enter queue. Process exit releases our "wait" operation.
* This sets the semadj value in this child process so that
* the semaphore is adjusted properly upon process exit.
* This is necessary because the parent process may die and
* adjust the semaphore. We must have our count in place if
* that happens. There is no race condition here between the
* parent adjusting the semaphore and our wait, since no
* mail job has been opened yet.
*
* All semop failures cause syserr. It is assumed that once
* a semop fails, all subsequent semops fail. This means
* that orderq can't clean the queue.
*/
#ifdef DEBUG
if (tTd (40, 111)) /* sema4 test */
exit (99);
#endif DEBUG
(void) semwait (Sid, 1, 0, 0);
#ifdef DEBUG
if (tTd (40, 110)) /* sema4 test */
exit (99);
#endif DEBUG
}
}
if (i == 0)
{
/*
* (PSEUDO)CHILD
* Lock the control file to avoid duplicate deliveries.
* Then run the file as though we had just read it.
* We save an idea of the temporary name so we
* can recover on interrupt (what?).
*/
/* set basic modes, etc. */
/* stopping events unnecessary since none are ours */
stopevents (); /* stop event queue */
clearenvelope(CurEnv, FALSE);
QueueRun = TRUE;
ErrorMode = EM_MAIL;
CurEnv->e_id = &w->w_name[2];
# ifdef LOG
if (LogLevel > 11)
syslog(LOG_DEBUG, MSGSTR(QU_DOWORK, "%s: dowork, pid=%d"), CurEnv->e_id, getpid()); /*MSG*/
# endif LOG
/* don't use the headers from sendmail.cf... */
CurEnv->e_header = NULL;
/* lock the control file during processing */
if (link(w->w_name, queuename(CurEnv, 'l')) < 0)
{
#ifdef DEBUG
if (tTd(40, 1))
(void) printf("dowork: %s, locked\n", w->w_name);
#endif DEBUG
#ifdef LOG
if (LogLevel > 4)
syslog(LOG_DEBUG, MSGSTR(QU_LOCKED, "%s: locked"), CurEnv->e_id); /*MSG*/
#endif LOG
if (ForkQueueRuns)
exit(EX_OK);
else
{
CurEnv->e_id = NULL; /* logically detach frm qf */
return;
}
}
/* do basic system initialization */
initsys();
readqf(CurEnv, TRUE);
CurEnv->e_flags |= EF_INQUEUE;
eatheader(CurEnv);
/* do the delivery */
#ifdef DEBUG
if (tTd (41, 1))
(void) printf ("dowork: prepare to sendall\n");
#endif DEBUG
/* kill this test since immediate/fork modes don't use it */
/* if (!bitset(EF_FATALERRS, CurEnv->e_flags)) */
sendall(CurEnv, SM_DELIVER);
/* finish up and exit */
if (ForkQueueRuns)
finis();
else
dropenvelope(CurEnv);
}
else
{
/*
* Parent -- wait for all children to exit (there's only one).
* The argument one is simply a dummy in the pid numeric range.
* Since SIGCLD is SIG_IGN, no zombies are formed.
*/
(void) waitfor (1);
errno = 0;
}
}
/*
** READQF -- read queue file and set up environment.
**
** Parameters:
** e -- the envelope of the job to run.
** full -- if set, read in all information. Otherwise just
** read in info needed for a queue print.
**
** Returns:
** none.
**
** Side Effects:
** cf is read and created as the current job, as though
** we had been invoked by argument.
*/
readqf(e, full)
register ENVELOPE *e;
int full;
{
char *qf;
register FILE *qfp;
char buf[MAXFIELD];
int gotctluser = 0;
int fd;
/*
** Read and process the file.
*/
qf = queuename(e, 'q');
qfp = fopen(qf, "r");
if (qfp == NULL)
{
syserr(MSGSTR(QU_ECONTROL, "readqf: no control file %s"), qf); /*MSG*/
return;
}
FileName = qf;
LineNumber = 0;
if (Verbose && full)
(void) printf(MSGSTR(QU_RUN, "\nRunning %s\n"), e->e_id); /*MSG*/
while (fgetfolded(buf, sizeof buf, qfp) != NULL)
{
# ifdef DEBUG
if (tTd(40, 4))
(void) printf("+++++ %s\n", buf);
# endif DEBUG
switch (buf[0])
{
case 'C': /* specify controlling user */
setctluser(&buf[1]);
gotctluser = 1;
break;
case 'R': /* specify recipient */
sendtolist(&buf[1], (ADDRESS *) NULL, &e->e_sendqueue);
break;
case 'E': /* specify error recipient */
sendtolist(&buf[1], (ADDRESS *) NULL, &e->e_errorqueue);
break;
case 'H': /* header */
if (full)
(void) chompheader(&buf[1], FALSE);
break;
case 'M': /* message */
e->e_message = newstr(&buf[1]);
break;
case 'S': /* sender */
setsender(newstr(&buf[1]));
break;
case 'D': /* data file name */
if (!full)
break;
e->e_df = newstr(&buf[1]);
e->e_dfp = fopen(e->e_df, "r");
if (e->e_dfp == NULL)
syserr(MSGSTR(QU_EOPEN3, "readqf: cannot open %s"), e->e_df); /*MSG*/
break;
case 'T': /* init time */
e->e_ctime = atol(&buf[1]);
break;
case 'P': /* message priority */
e->e_msgpriority = atol(&buf[1]) + WkTimeFact;
break;
case 'B': /* body type */
if (buf[1] == 'e')
e->e_btype = BT_ESC;
else if (buf[1] == 'n')
e->e_btype = BT_NLS;
else if (buf[1] == 'j')
e->e_btype = BT_NC;
else if (buf[1] == 's')
e->e_btype = BT_MC;
case '\0': /* blank line; ignore */
break;
default:
syserr(MSGSTR(QU_ELINE, "readqf(%s:%d): bad line \"%s\""), e->e_id, LineNumber, buf); /*MSG*/
break;
}
/* The 'C' queue file command operates on the next line,
** so we use 'gotctluser' to maintain state as follows:
** 0 - no controlling user,
** 1 - controlling user has been set but not used,
** 2 - controlling user must be used on next iteration.
*/
if (gotctluser == 1)
gotctluser++;
else if (gotctluser == 2)
{
clrctluser();
gotctluser = 0;
}
}
/* clear controlling user in case we break out prematurely */
clrctluser();
(void) fclose(qfp);
FileName = NULL;
/*
** If we haven't read any lines, this queue file is empty.
** Arrange to remove it without referencing any null pointers.
*/
if (LineNumber == 0)
{
errno = 0;
e->e_flags |= EF_CLRQUEUE | EF_FATALERRS | EF_RESPONSE;
}
}
/*
** PRINTQUEUE -- print out a representation of the mail queue
**
** Parameters:
** none.
**
** Returns:
** none.
**
** Side Effects:
** Prints a listing of the mail queue on the standard output.
*/
printqueue()
{
register WORK *w;
FILE *f;
int nrequests;
char buf[MAXLINE];
/*
** Read and order the queue.
*/
nrequests = orderq(TRUE);
/*
** Print the work list that we have read.
*/
/* first see if there is anything */
if (nrequests <= 0)
{
(void) printf(MSGSTR(QU_EMPTY, "Mail queue is empty\n")); /*MSG*/
return;
}
if (nrequests == 1)
(void) printf(MSGSTR(QU_REQ, "\t\tMail Queue (1 request")); /*MSG*/
else
(void) printf(MSGSTR(QU_REQS, "\t\tMail Queue (%d requests"), nrequests); /*MSG*/
if (nrequests > QUEUESIZE)
(void) printf(MSGSTR(QU_PRINT, ", only %d printed"), QUEUESIZE); /*MSG*/
if (Verbose)
(void) printf(MSGSTR(QU_HDR, ")\n---QID---- --Size-- -Priority- ---Q-Time--- ----------Sender/Recipient----------\n")); /*MSG*/
else
(void) printf(MSGSTR(QU_HDR2, ")\n---QID---- --Size-- -----Q-Time----- --------------Sender/Recipient-------------\n")); /*MSG*/
for (w = WorkQ; w != NULL; w = w->w_next)
{
struct stat st;
long submittime = 0;
long dfsize = -1;
char lf[20];
char line[MAXLINE];
char cbuf[MAXLINE];
f = fopen(w->w_name, "r");
if (f == NULL)
{
errno = 0;
continue;
}
(void) printf("%-10s", w->w_name + 2);
(void) strcpy(lf, w->w_name);
lf[0] = 'l';
if (stat(lf, &st) >= 0)
(void) printf("*");
else if (shouldqueue(w->w_pri))
(void) printf("X");
else
(void) printf(" ");
errno = 0;
line[0] = '\0';
cbuf[0] = '\0';
while (fgets(buf, sizeof buf, f) != NULL)
{
fixcrlf(buf, TRUE);
switch (buf[0])
{
case 'M': /* error message */
(void) strcpy(line, &buf[1]);
break;
case 'S': /* sender name */
if (Verbose)
(void) printf ("%8ld %10ld %.12s %.36s",
dfsize, w->w_pri, ctime(&submittime) + 4,
&buf[1]);
else
(void) printf ("%8ld %.16s %.43s", dfsize,
ctime(&submittime), &buf[1]);
if (line[0] != '\0') /* error: indent it */
(void) printf ("\n%*s(%.59s)",
11, "", line);
break;
case 'C': /* controlling user */
if (strlen(buf) < MAXLINE - 3) /* sanity */
(void) strcat(buf, ") ");
cbuf[0] = cbuf[1] = '(';
(void) strncpy(&cbuf[2], &buf[1], MAXLINE-1);
cbuf[MAXLINE-1] = '\0';
break;
case 'R': /* recipient name: indent and print */
if (cbuf[0] != '\0')
{
/* prepend controlling user to 'buf' */
(void) strncat(cbuf, &buf[1],
MAXLINE-strlen(cbuf));
cbuf[MAXLINE-1] = '\0';
(void) strcpy(buf, cbuf);
cbuf[0] = '\0';
}
if (Verbose)
(void) printf ("\n%*s%.36s", 44, "", &buf[1]);
else
(void) printf ("\n%*s%.43s", 37, "", &buf[1]);
break;
case 'T': /* creation time */
submittime = atol(&buf[1]);
break;
case 'D': /* data file name */
if (stat(&buf[1], &st) >= 0)
dfsize = st.st_size;
break;
}
}
if (submittime == (long) 0)
(void) printf (MSGSTR(QU_ECONTR, " (no control file)")); /*MSG*/
(void) printf ("\n");
(void) fclose(f);
}
}
/*
** QUEUENAME -- build a file name in the queue directory for this envelope.
**
** Assigns an id code if one does not already exist.
** This code is very careful to avoid trashing existing files
** under any circumstances.
**
** Parameters:
** e -- envelope to build it in/from.
** type -- the file type, used as the first character
** of the file name.
**
** Returns:
** a pointer to the new file name (in a static buffer).
**
** Side Effects:
** Will create the qf files if no id code is
** already assigned. This will cause the envelope
** to be modified.
*/
char *
queuename(e, type)
register ENVELOPE *e;
char type;
{
static char buf[MAXNAME];
static int pid = -1;
char c1 = 'A';
char c2 = 'A';
if (e->e_id == NULL)
{
char qf[20];
char lf[20];
/* find a unique id */
if (pid != getpid())
{
/* new process -- start back at "AA" */
pid = getpid();
c1 = 'A';
c2 = 'A' - 1;
}
(void) sprintf(qf, "qfAA%05d", pid);
(void) strcpy(lf, qf);
lf[0] = 'l';
while (c1 < '~' || c2 < 'Z')
{
int i;
if (c2 >= 'Z')
{
c1++;
c2 = 'A' - 1;
}
lf[2] = qf[2] = c1;
lf[3] = qf[3] = ++c2;
# ifdef DEBUG
if (tTd(7, 20))
(void) printf("queuename: trying \"%s\"\n", qf);
# endif DEBUG
/*
* Test for existence of either of the pair.
* No other active invocation of sendmail has our
* pid, so any true access refers to a queue file
* created by a previous invocation of sendmail
* using the same pid. No one else can be
* attempting to create links using our name,
* since the name contains the pid.
*/
if (access(lf, F_OK) >= 0 || access(qf, F_OK) >= 0)
continue;
i = creat(lf, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
if (i < 0)
{
i = errno; /* save error */
(void) unlink(lf); /* kernel bug */
if (i == ENOSPC)
{
syserr(MSGSTR(QU_ECREAT, "queuename: Cannot create \"%s\" in \"%s\""), lf, QueueDir); /*MSG*/
exit(EX_UNAVAILABLE);
}
continue;
}
(void) close(i); /* we don't need it open */
/*
* Link qf to the same inode and we are through.
*/
if (link(lf, qf) >= 0)
break;
/*
* Link failed. Clean up remaining file and try
* the next one.
*/
(void) unlink(lf);
}
errno = 0;
if (c1 >= '~' && c2 >= 'Z')
{
syserr(MSGSTR(QU_ECREAT, "queuename: Cannot create \"%s\" in \"%s\""), qf, QueueDir); /*MSG*/
exit(EX_OSERR);
}
e->e_id = newstr(&qf[2]);
define('i', e->e_id, e);
# ifdef DEBUG
if (tTd(7, 1))
(void) printf("queuename: assigned id %s, env=%x\n",
e->e_id, e);
# ifdef LOG
if (LogLevel > 16)
syslog(LOG_DEBUG, "%s: assigned id", e->e_id);
# endif LOG
# endif DEBUG
}
if (type == '\0')
return (NULL);
(void) sprintf(buf, "%cf%s", type, e->e_id);
# ifdef DEBUG
if (tTd(7, 2))
(void) printf("queuename: %s\n", buf);
# endif DEBUG
return (buf);
}
/*
** UNLOCKQUEUE -- unlock the queue entry for a specified envelope
**
** Parameters:
** e -- the envelope to unlock.
**
** Returns:
** none
**
** Side Effects:
** unlocks the queue for `e'.
*/
unlockqueue(e)
ENVELOPE *e;
{
/* remove the transcript */
#ifdef DEBUG
# ifdef LOG
if (LogLevel > 19)
syslog(LOG_DEBUG, "%s: unlock", e->e_id);
# endif LOG
if (!tTd(51, 4))
#endif DEBUG
xunlink(queuename(e, 'x'));
/* last but not least, remove the lock */
xunlink(queuename(e, 'l'));
}
/*
** GETCTLUSER -- return controlling user if mailing to prog or file
**
** Check for a "|" or "/" at the beginning of the address. If
** found, return a controlling username.
**
** Parameters:
** a - the address to check out
**
** Returns:
** Either NULL, if we weren't mailing to a program or file,
** or a controlling user name (possibly in getpwuid's
** static buffer).
**
** Side Effects:
** none.
*/
char *
getctluser(a)
ADDRESS *a;
{
extern ADDRESS *getctladdr();
struct passwd *pw;
char *retstr = NULL;
/*
** Get unquoted user for files, program or user.name check.
** N.B. remove this code block to always emit controlling
** addresses (at the expense of backward compatibility).
*/
{
char buf[MAXNAME];
if (a->q_alias == NULL && !bitset(QGOODUID, a->q_flags))
return((char *)NULL);
(void) strncpy(buf, a->q_paddr, MAXNAME);
buf[MAXNAME-1] = '\0';
stripquotes(buf, TRUE);
if (buf[0] != '|' && buf[0] != '/')
return ((char *)NULL);
}
a = getctladdr(a); /* find controlling address */
if (a != NULL && a->q_uid != 0 && (pw = getpwuid(a->q_uid)) != NULL)
retstr = pw->pw_name;
else /* use default user */
retstr = DefUser;
# ifdef DEBUG
if (tTd(40, 5))
printf("Set controlling user for '%s' to '%s'\n",
(a == NULL) ? "<null>": a->q_paddr, retstr);
# endif DEBUG
return(retstr);
}
/*
** SETCTLUSER - sets 'CtlUser' to controlling user
** CLRCTLUSER - clears controlling user (no params, nothing returned)
**
** These routines manipulate 'CtlUser'
**
** Parameters:
** str - controlling user as passed to setctluser()
**
** Returns:
** None.
**
** Side Effects:
** 'CtlUser' is changed
*/
static char CtlUser[MAXNAME];
setctluser(str)
register char *str;
{
(void) strncpy(CtlUser, str, MAXNAME);
CtlUser[MAXNAME-1] = '\0';
}
clrctluser()
{
CtlUser[0] = '\0';
}
/*
** SETCTLADDR -- create a controlling address
**
** If global variable 'CtlUser' is set and we are given a valid
** address, make that address a controlling address; change the
** 'q_uid', 'q_gid', and 'q_ruser' fields and set QGOODUID.
**
** Parameters:
** a - address for which control uid/gid info may apply
**
** Returns:
** None.
**
** Side Effects:
** Fills in uid/gid fields in address and sets QGOODUID
** flag if appropriate.
*/
setctladdr(a)
ADDRESS *a;
{
struct passwd *pw;
/*
** If there is no current controlling user, or we were passed a
** NULL addr ptr or we already have a controlling user, return.
*/
if (CtlUser[0] == '\0' || a == NULL || a->q_ruser)
return;
/*
** Set up addr fields for controlling user. If 'CtlUser' is no
** longer valid, use the default user/group.
*/
if ((pw = _getpwnam_shadow(CtlUser,0)) != NULL)
{
if (a->q_home)
free(a->q_home);
a->q_home = newstr(pw->pw_dir);
a->q_uid = pw->pw_uid;
a->q_gid = pw->pw_gid;
a->q_ruser = newstr(CtlUser);
}
else
{
a->q_uid = DefUid;
a->q_gid = DefGid;
a->q_ruser = newstr(DefUser);
}
a->q_flags |= QGOODUID; /* flag as a "ctladdr" */
# ifdef DEBUG
if (tTd(40, 5))
(void) printf("Restored controlling user for '%s' to '%s'\n",
a->q_paddr, a->q_ruser);
# endif DEBUG
}