* Copyright (c) 1983 Eric P. Allman
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
static char sccsid
[] = "@(#)queue.c 8.3 (Berkeley) 7/13/93 (with queueing)";
static char sccsid
[] = "@(#)queue.c 8.3 (Berkeley) 7/13/93 (without queueing)";
char *w_name
; /* name of control file */
long w_pri
; /* priority of message, see below */
time_t w_ctime
; /* creation time of message */
struct work
*w_next
; /* next in queue */
typedef struct work WORK
;
WORK
*WorkQ
; /* queue of things to be done */
** QUEUEUP -- queue a message up for future transmission.
** 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.
** The current request are saved in a control file.
** The queue file is left locked.
queueup(e
, queueall
, announce
)
char buf
[MAXLINE
], tf
[MAXLINE
];
newid
= (e
->e_id
== NULL
);
strcpy(tf
, queuename(e
, 't'));
/* get a locked tf file */
for (i
= 100; --i
>= 0; )
fd
= open(tf
, O_CREAT
|O_WRONLY
|O_EXCL
, FileMode
);
syserr("!queueup: cannot create temp file %s", tf
);
if (lockfile(fd
, tf
, LOCK_EX
|LOCK_NB
))
printf("queueing %s\n", e
->e_id
);
** If there is no data file yet, create one.
e
->e_df
= newstr(queuename(e
, 'd'));
fd
= open(e
->e_df
, O_WRONLY
|O_CREAT
, FileMode
);
syserr("!queueup: cannot create %s", e
->e_df
);
(*e
->e_putbody
)(dfp
, FileMailer
, e
, NULL
);
(void) xfclose(dfp
, "queueup dfp", e
->e_id
);
** Output future work requests.
** Priority and creation time should be first, since
** they are required by orderq.
/* output message priority */
fprintf(tfp
, "P%ld\n", e
->e_msgpriority
);
/* output creation time */
fprintf(tfp
, "T%ld\n", e
->e_ctime
);
/* output type and name of data file */
if (e
->e_bodytype
!= NULL
)
fprintf(tfp
, "B%s\n", e
->e_bodytype
);
fprintf(tfp
, "D%s\n", e
->e_df
);
/* message from envelope, if it exists */
if (e
->e_message
!= NULL
)
fprintf(tfp
, "M%s\n", e
->e_message
);
/* send various flag bits through */
if (bitset(EF_WARNING
, e
->e_flags
))
if (bitset(EF_RESPONSE
, e
->e_flags
))
fprintf(tfp
, "F%s\n", buf
);
/* $r and $s and $_ macro values */
if ((p
= macvalue('r', e
)) != NULL
)
fprintf(tfp
, "$r%s\n", p
);
if ((p
= macvalue('s', e
)) != NULL
)
fprintf(tfp
, "$s%s\n", p
);
if ((p
= macvalue('_', e
)) != NULL
)
fprintf(tfp
, "$_%s\n", p
);
/* output name of sender */
fprintf(tfp
, "S%s\n", e
->e_from
.q_paddr
);
/* output list of error recipients */
printctladdr(NULL
, NULL
);
for (q
= e
->e_errorqueue
; q
!= NULL
; q
= q
->q_next
)
if (!bitset(QDONTSEND
|QBADADDR
, q
->q_flags
))
fprintf(tfp
, "E%s\n", q
->q_paddr
);
/* output list of recipient addresses */
for (q
= e
->e_sendqueue
; q
!= NULL
; q
= q
->q_next
)
if (bitset(QQUEUEUP
, q
->q_flags
) ||
(queueall
&& !bitset(QDONTSEND
|QBADADDR
|QSENT
, q
->q_flags
)))
fprintf(tfp
, "R%s\n", q
->q_paddr
);
logdelivery(NULL
, NULL
, "queued", e
);
** 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
** We set up a "null mailer" -- i.e., a mailer that will have
** no effect on the addresses as they are output.
bzero((char *) &nullmailer
, sizeof nullmailer
);
nullmailer
.m_re_rwset
= nullmailer
.m_rh_rwset
=
nullmailer
.m_se_rwset
= nullmailer
.m_sh_rwset
= -1;
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')
/* don't output resent headers on non-resent messages */
if (bitset(H_RESENT
, h
->h_flags
) && !bitset(EF_RESENT
, e
->e_flags
))
/* if conditional, output the set of conditions */
if (!bitzerop(h
->h_mflags
) && bitset(H_CHECK
|H_ACHECK
, h
->h_flags
))
for (j
= '\0'; j
<= '\177'; j
++)
if (bitnset(j
, h
->h_mflags
))
/* 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
))
bool oldstyle
= bitset(EF_OLDSTYLE
, e
->e_flags
);
FILE *savetrace
= TrafficLogFile
;
if (bitset(H_FROM
, h
->h_flags
))
commaize(h
, h
->h_value
, tfp
, oldstyle
,
TrafficLogFile
= savetrace
;
fprintf(tfp
, "%s: %s\n", h
->h_field
, h
->h_value
);
syserr("!552 Error writing control file %s", tf
);
syserr("!452 Error writing control file %s", tf
);
syserr("cannot rename(%s, %s), df=%s", tf
, qf
, e
->e_df
);
(void) xfclose(e
->e_lockfp
, "queueup lockfp", e
->e_id
);
syslog(LOG_DEBUG
, "%s: queueup, qf=%s, df=%s\n", e
->e_id
, qf
, e
->e_df
);
register struct passwd
*pw
;
static ADDRESS
*lastctladdr
;
if (a
== NULL
|| tfp
== NULL
)
if (lastctladdr
!= NULL
&& tfp
!= NULL
)
/* find the active uid */
/* if a is an alias, use that for printing */
/* check to see if this is the same as last time */
if (lastctladdr
!= NULL
&& uid
== lastuid
&&
strcmp(lastctladdr
->q_paddr
, a
->q_paddr
) == 0)
if (uid
== 0 || (pw
= getpwuid(uid
)) == NULL
)
fprintf(tfp
, "C%s:%s\n", uname
, a
->q_paddr
);
** RUNQUEUE -- run the jobs in the queue.
** Gets the stuff out of the queue in some presumably logical
** order and processes them.
** forkflag -- TRUE if the queue scanning should be done in
** a child process. We double-fork so it is not our
** child and we don't have to clean up after it.
** runs things in the mail queue.
ENVELOPE QueueEnvelope
; /* the queue run envelope */
extern ENVELOPE BlankEnvelope
;
** If no work will ever be selected, don't even bother reading
CurrentLA
= getla(); /* get load average */
if (shouldqueue(0L, curtime()))
printf("Skipping queue run -- load average too high\n");
if (forkflag
&& QueueIntvl
!= 0)
(void) setevent(QueueIntvl
, runqueue
, TRUE
);
** See if we want to go off and do other useful work.
/* parent -- pick up intermediate zombie */
(void) signal(SIGCHLD
, reapchild
);
(void) setevent(QueueIntvl
, runqueue
, TRUE
);
/* child -- double fork */
(void) signal(SIGCHLD
, SIG_DFL
);
setproctitle("running queue: %s", QueueDir
);
syslog(LOG_DEBUG
, "runqueue %s, pid=%d, forkflag=%d",
QueueDir
, getpid(), forkflag
);
** Release any resources used by the daemon code.
** Create ourselves an envelope
e
= newenvelope(&QueueEnvelope
, CurEnv
);
e
->e_flags
= BlankEnvelope
.e_flags
;
** Make sure the alias database is open.
** Start making passes through the queue.
** First, read and sort the entire queue.
** Then, process the work in that order.
** But if you take too long, start over.
/* order the existing work requests */
/* process them once at a time */
** Ignore jobs that are too expensive for the moment.
if (shouldqueue(w
->w_pri
, w
->w_ctime
))
printf("\nSkipping %s\n", w
->w_name
+ 2);
dowork(w
->w_name
+ 2, ForkQueueRuns
, FALSE
, e
);
/* exit without the usual cleanup */
** ORDERQ -- order the work queue.
** 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
** The number of request in the queue (not necessarily
** the number of requests in WorkQ however).
** Sets WorkQ to the queue of available work, in order.
register struct dirent
*d
;
if (QueueLimitId
!= NULL
)
printf("\tQueueLimitId = %s\n", QueueLimitId
);
if (QueueLimitSender
!= NULL
)
printf("\tQueueLimitSender = %s\n", QueueLimitSender
);
if (QueueLimitRecipient
!= NULL
)
printf("\tQueueLimitRecipient = %s\n", QueueLimitRecipient
);
/* clear out old WorkQ */
for (w
= WorkQ
; w
!= NULL
; )
register WORK
*nw
= w
->w_next
;
/* open the queue directory */
syserr("orderq: cannot open \"%s\" as \".\"", QueueDir
);
** Read the work directory.
while ((d
= readdir(f
)) != NULL
)
extern bool strcontainedin();
/* is this an interesting entry? */
if (d
->d_name
[0] != 'q' || d
->d_name
[1] != 'f')
if (QueueLimitId
!= NULL
&&
!strcontainedin(QueueLimitId
, d
->d_name
))
** Check queue name for plausibility. This handles
** both old and new type ids.
printf("orderq: bogus qf name %s\n", d
->d_name
);
syslog(LOG_CRIT
, "orderq: bogus qf name %s",
if (strlen(d
->d_name
) >= MAXNAME
)
d
->d_name
[MAXNAME
- 1] = '\0';
(void) rename(d
->d_name
, lbuf
);
/* yes -- open control file (if not too many files) */
cf
= fopen(d
->d_name
, "r");
/* this may be some random person sending hir msgs */
/* syserr("orderq: cannot open %s", cbuf); */
printf("orderq: cannot open %s (%d)\n",
w
->w_name
= newstr(d
->d_name
);
/* make sure jobs in creation don't clog queue */
/* extract useful information */
if (QueueLimitSender
!= NULL
)
if (QueueLimitRecipient
!= NULL
)
while (i
!= 0 && fgets(lbuf
, sizeof lbuf
, cf
) != NULL
)
extern bool strcontainedin();
w
->w_pri
= atol(&lbuf
[1]);
w
->w_ctime
= atol(&lbuf
[1]);
if (QueueLimitRecipient
!= NULL
&&
strcontainedin(QueueLimitRecipient
, &lbuf
[1]))
if (QueueLimitSender
!= NULL
&&
strcontainedin(QueueLimitSender
, &lbuf
[1]))
if ((!doall
&& shouldqueue(w
->w_pri
, w
->w_ctime
)) ||
bitset(NEED_R
|NEED_S
, i
))
/* don't even bother sorting this job in */
** Sort the work directory.
qsort((char *) wlist
, min(wn
, QUEUESIZE
), sizeof *wlist
, workcmpf
);
** Convert the work list into canonical form.
** Should be turning it into a list of envelopes here perhaps.
for (i
= min(wn
, QUEUESIZE
); --i
>= 0; )
w
= (WORK
*) xalloc(sizeof *w
);
w
->w_name
= wlist
[i
].w_name
;
w
->w_pri
= wlist
[i
].w_pri
;
w
->w_ctime
= wlist
[i
].w_ctime
;
for (w
= WorkQ
; w
!= NULL
; w
= w
->w_next
)
printf("%32s: pri=%ld\n", w
->w_name
, w
->w_pri
);
** WORKCMPF -- compare function for ordering work.
** a -- the first argument.
** b -- the second argument.
** DOWORK -- do a work request.
** id -- the ID of the job to run.
** forkflag -- if set, run this in background.
** requeueflag -- if set, reinstantiate the queue quickly.
** This is used when expanding aliases in the queue.
** If forkflag is also set, it doesn't wait for the
** e - the envelope in which to run it.
** The work request is satisfied if possible.
dowork(id
, forkflag
, requeueflag
, e
)
printf("dowork(%s)\n", id
);
syserr("dowork: cannot fork");
** 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.
/* set basic modes, etc. */
e
->e_flags
|= EF_QUEUERUN
;
e
->e_errormode
= EM_MAIL
;
syslog(LOG_DEBUG
, "%s: dowork, pid=%d", e
->e_id
,
/* don't use the headers from sendmail.cf... */
/* read the queue control file -- return if locked */
printf("readqf(%s) failed\n", e
->e_id
);
e
->e_flags
|= EF_INQUEUE
;
eatheader(e
, requeueflag
);
** Parent -- pick up results.
** READQF -- read queue file and set up environment.
** e -- the envelope of the job to run.
** TRUE if it successfully read the queue file.
** The queue file is returned locked.
extern ADDRESS
*setctluser();
** Read and process the file.
strcpy(qf
, queuename(e
, 'q'));
printf("readqf(%s): fopen failure (%s)\n",
syserr("readqf: no control file %s", qf
);
** Check the queue file for plausibility to avoid attacks.
if (fstat(fileno(qfp
), &st
) < 0)
/* must have been being processed by someone else */
printf("readqf(%s): fstat failure (%s)\n",
if (st
.st_uid
!= geteuid() || (st
.st_mode
& 07777) != FileMode
)
syslog(LOG_ALERT
, "%s: bogus queue file, uid=%d, mode=%o",
e
->e_id
, st
.st_uid
, st
.st_mode
);
printf("readqf(%s): bogus file\n", qf
);
rename(qf
, queuename(e
, 'Q'));
if (!lockfile(fileno(qfp
), qf
, LOCK_EX
|LOCK_NB
))
/* being processed by another queuer */
printf("readqf(%s): locked\n", qf
);
printf("%s: locked\n", e
->e_id
);
syslog(LOG_DEBUG
, "%s: locked", e
->e_id
);
/* must be a bogus file -- just remove it */
/* do basic system initialization */
printf("\nRunning %s\n", e
->e_id
);
while ((bp
= fgetfolded(buf
, sizeof buf
, qfp
)) != NULL
)
printf("+++++ %s\n", bp
);
case 'C': /* specify controlling user */
ctladdr
= setctluser(&bp
[1]);
case 'R': /* specify recipient */
(void) sendtolist(&bp
[1], ctladdr
, &e
->e_sendqueue
, e
);
case 'E': /* specify error recipient */
(void) sendtolist(&bp
[1], ctladdr
, &e
->e_errorqueue
, e
);
(void) chompheader(&bp
[1], FALSE
, e
);
e
->e_message
= newstr(&bp
[1]);
setsender(newstr(&bp
[1]), e
, NULL
, TRUE
);
case 'B': /* body type */
e
->e_bodytype
= newstr(&bp
[1]);
case 'D': /* data file name */
e
->e_df
= newstr(&bp
[1]);
e
->e_dfp
= fopen(e
->e_df
, "r");
syserr("readqf: cannot open %s", e
->e_df
);
else if (fstat(fileno(e
->e_dfp
), &st
) >= 0)
e
->e_msgsize
= st
.st_size
;
case 'T': /* init time */
e
->e_ctime
= atol(&bp
[1]);
case 'P': /* message priority */
e
->e_msgpriority
= atol(&bp
[1]) + WkTimeFact
;
case 'F': /* flag bits */
for (p
= &bp
[1]; *p
!= '\0'; p
++)
case 'w': /* warning sent */
e
->e_flags
|= EF_WARNING
;
e
->e_flags
|= EF_RESPONSE
;
case '$': /* define macro */
define(bp
[1], newstr(&bp
[2]), e
);
case '\0': /* blank line; ignore */
syserr("readqf: bad line \"%s\"", e
->e_id
,
rename(qf
, queuename(e
, 'Q'));
** If we haven't read any lines, this queue file is empty.
** Arrange to remove it without referencing any null pointers.
e
->e_flags
|= EF_CLRQUEUE
| EF_FATALERRS
| EF_RESPONSE
;
** PRINTQUEUE -- print out a representation of the mail queue
** Prints a listing of the mail queue on the standard output.
** Check for permission to print the queue
if (bitset(PRIV_RESTRMAILQ
, PrivacyFlags
) && RealUid
!= 0)
if (stat(QueueDir
, &st
) < 0)
syserr("Cannot stat %s", QueueDir
);
n
= getgroups(NGROUPS
, gidset
);
if (gidset
[n
] == st
.st_gid
)
if (RealGid
!= st
.st_gid
)
usrerr("510 You are not permitted to see the queue");
** Read and order the queue.
nrequests
= orderq(TRUE
);
** Print the work list that we have read.
/* first see if there is anything */
printf("Mail queue is empty\n");
CurrentLA
= getla(); /* get load average */
printf("\t\tMail Queue (%d request%s", nrequests
, nrequests
== 1 ? "" : "s");
if (nrequests
> QUEUESIZE
)
printf(", only %d printed", QUEUESIZE
);
printf(")\n--Q-ID-- --Size-- -Priority- ---Q-Time--- -----------Sender/Recipient-----------\n");
printf(")\n--Q-ID-- --Size-- -----Q-Time----- ------------Sender/Recipient------------\n");
for (w
= WorkQ
; w
!= NULL
; w
= w
->w_next
)
auto time_t submittime
= 0;
f
= fopen(w
->w_name
, "r");
printf("%8s", w
->w_name
+ 2);
if (!lockfile(fileno(f
), w
->w_name
, LOCK_SH
|LOCK_NB
))
else if (shouldqueue(w
->w_pri
, w
->w_ctime
))
message
[0] = bodytype
[0] = '\0';
while (fgets(buf
, sizeof buf
, f
) != NULL
)
case 'M': /* error message */
if ((i
= strlen(&buf
[1])) >= sizeof message
)
bcopy(&buf
[1], message
, i
);
case 'B': /* body type */
if ((i
= strlen(&buf
[1])) >= sizeof bodytype
)
bcopy(&buf
[1], bodytype
, i
);
case 'S': /* sender name */
printf("%8ld %10ld%c%.12s %.38s",
bitset(EF_WARNING
, flags
) ? '+' : ' ',
printf("%8ld %.16s %.45s", dfsize
,
ctime(&submittime
), &buf
[1]);
if (message
[0] != '\0' || bodytype
[0] != '\0')
printf("\n %10.10s", bodytype
);
printf(" (%.60s)", message
);
case 'C': /* controlling user */
printf("\n\t\t\t\t (---%.34s---)",
case 'R': /* recipient name */
printf("\n\t\t\t\t\t %.38s", &buf
[1]);
printf("\n\t\t\t\t %.45s", &buf
[1]);
case 'T': /* creation time */
submittime
= atol(&buf
[1]);
case 'D': /* data file name */
if (stat(&buf
[1], &st
) >= 0)
case 'F': /* flag bits */
for (p
= &buf
[1]; *p
!= '\0'; p
++)
if (submittime
== (time_t) 0)
printf(" (no control file)");
** 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.
** e -- envelope to build it in/from.
** type -- the file type, used as the first character
** a pointer to the new file name (in a static buffer).
** If no id code is already assigned, queuename will
** assign an id code, create a qf file, and leave a
** locked, open-for-write file pointer in the envelope.
static char buf
[MAXNAME
];
/* new process -- start back at "AA" */
(void) sprintf(qf
, "qf%cAA%05d", c0
, pid
);
while (c1
< '~' || c2
< 'Z')
printf("queuename: trying \"%s\"\n", qf
);
i
= open(qf
, O_WRONLY
|O_CREAT
|O_EXCL
, FileMode
);
syserr("queuename: Cannot create \"%s\" in \"%s\"",
if (lockfile(i
, qf
, LOCK_EX
|LOCK_NB
))
e
->e_lockfp
= fdopen(i
, "w");
/* a reader got the file; abandon it and try again */
if (c1
>= '~' && c2
>= 'Z')
syserr("queuename: Cannot create \"%s\" in \"%s\"",
e
->e_id
= newstr(&qf
[2]);
printf("queuename: assigned id %s, env=%x\n", e
->e_id
, e
);
syslog(LOG_DEBUG
, "%s: assigned id", e
->e_id
);
(void) sprintf(buf
, "%cf%s", type
, e
->e_id
);
printf("queuename: %s\n", buf
);
** UNLOCKQUEUE -- unlock the queue entry for a specified envelope
** e -- the envelope to unlock.
** unlocks the queue for `e'.
printf("unlockqueue(%s)\n", e
->e_id
);
/* if there is a lock file in the envelope, close it */
xfclose(e
->e_lockfp
, "unlockqueue", e
->e_id
);
/* don't create a queue id if we don't already have one */
/* remove the transcript */
syslog(LOG_DEBUG
, "%s: unlock", e
->e_id
);
xunlink(queuename(e
, 'x'));
** SETCTLUSER -- create a controlling address
** Create a fake "address" given only a local login name; this is
** used as a "controlling user" for future recipient addresses.
** user -- the user name of the controlling user.
** An address descriptor for the controlling user.
** See if this clears our concept of controlling user.
** Set up addr fields for controlling user.
a
= (ADDRESS
*) xalloc(sizeof *a
);
bzero((char *) a
, sizeof *a
);
if (*user
!= '\0' && (pw
= getpwnam(user
)) != NULL
)
a
->q_home
= newstr(pw
->pw_dir
);
a
->q_user
= newstr(user
);
a
->q_user
= newstr(DefUser
);
a
->q_flags
|= QPRIMARY
; /* flag as a "ctladdr" */
a
->q_mailer
= LocalMailer
;