1N/A * Copyright (c) 1998-2009, 2011 Sendmail, Inc. and its suppliers. 1N/A * All rights reserved. 1N/A * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. 1N/A * Copyright (c) 1988, 1993 1N/A * The Regents of the University of California. All rights reserved. 1N/A * By using this file, you agree to the terms and conditions set 1N/A * forth in the LICENSE file which can be found at the top level of 1N/A * the sendmail distribution. 1N/A#
else /* HASFLOCK && defined(O_EXLOCK) */ 1N/A#
endif /* HASFLOCK && defined(O_EXLOCK) */ 1N/A#
endif /* ! SM_OPEN_EXLOCK */ 1N/A** QF_VERSION == 4 was sendmail 8.10/8.11 without _FFR_QUEUEDELAY 1N/A** QF_VERSION == 5 was sendmail 8.10/8.11 with _FFR_QUEUEDELAY 1N/A** QF_VERSION == 6 was sendmail 8.12 without _FFR_QUEUEDELAY 1N/A** QF_VERSION == 7 was sendmail 8.12 with _FFR_QUEUEDELAY 1N/A** QF_VERSION == 8 is sendmail 8.13 1N/A/* Naming convention: qgrp: index of queue group, qg: QUEUEGROUP */ 1N/A long w_pri;
/* priority of message, see below */ 1N/A/* Get new load average every 30 seconds. */ 1N/A** DoQueueRun indicates that a queue run is needed. 1N/A** Notice: DoQueueRun is modified in a signal handler! 1N/Astatic bool volatile DoQueueRun;
/* non-interrupt time queue run needed */ 1N/A** Work group definition structure. 1N/A** Each work group contains one or more queue groups. This is done 1N/A** to manage the number of queue group runners active at the same time 1N/A** to be within the constraints of MaxQueueChildren (if it is set). 1N/A** The number of queue groups that can be run on the next work run 1N/A** is kept track of. The queue groups are run in a round robin. 1N/A "@(#)$Debug: leak_q - trace memory leaks during queue processing $");
1N/A#
endif /* SM_HEAP_CHECK */ 1N/A** We use EmptyString instead of "" to avoid 1N/A** 'zero-length format string' warnings from gcc 1N/A#
endif /* _FFR_RHS */ 1N/A** Note: workcmpf?() don't use a prototype because it will cause a conflict 1N/A** with the qsort() call (which expects something like 1N/A** int (*compar)(const void *, const void *), not (WORK *, WORK *)) 1N/Astatic int randi =
3;
/* index for workcmpf5() */ 1N/A#
endif /* _FFR_RHS */ 1N/A#
else /* RANDOMSHIFT */ 1N/A#
endif /* RANDOMSHIFT */ 1N/A** File system definition. 1N/A** Used to keep track of how much free space is available 1N/A** on a file system in which one or more queue directories reside. 1N/A/* probably kept in shared memory */ 1N/A** Shared memory data 1N/A** size -- size of shared memory segment 1N/A** pid -- pid of owner, should be a unique id to avoid misinterpretations 1N/A** by other processes. 1N/A** tag -- should be a unique id to avoid misinterpretations by others. 1N/A** idea: hash over configuration data that will be stored here. 1N/A** NumFileSys -- number of file systems. 1N/A** FileSys -- (array of) structure for used file systems. 1N/A** RSATmpCnt -- counter for number of uses of ephemeral RSA key. 1N/A** QShm -- (array of) structure for information about queue directories. 1N/A** Queue data in shared memory 1N/A /* XXX more to follow? */ 1N/Astatic void *
Pshm;
/* pointer to shared memory */ 1N/A/* how to access FileSys */ 1N/A/* first entry is a tag, for now just the size */ 1N/A/* offset for PNumFileSys */ 1N/A/* offset for PRSATmpCnt */ 1N/A/* offset for queue_shm */ 1N/A/* basic size of shared memory segment */ 1N/A** HASH_Q -- simple hash function 1N/A** p -- string to hash. 1N/A** h -- hash start value (from previous run). 1N/A h += (c<<
11) ^ (c>>
1);
1N/A h ^= (d<<
14) + (d<<
7) + (d<<
4) + d;
1N/A#
else /* SM_CONF_SHM */ 1N/A#
endif /* SM_CONF_SHM */ 1N/A/* access to the various components of file system data */ 1N/A** Current qf file field assignments: 1N/A** C controlling user 1N/A** d data file directory name (added in 8.12) 1N/A** G free (was: queue delay algorithm if _FFR_QUEUEDELAY) 1N/A** I data file's inode number 1N/A** K time of last delivery attempt 1N/A** L Solaris Content-Length: header (obsolete) 1N/A** N number of delivery attempts 1N/A** P message priority 1N/A** q quarantine reason 1N/A** Q original recipient (ORCPT=) 1N/A** r final recipient (Final-Recipient: DSN field) 1N/A** V queue file version 1N/A** X free (was: character set if _FFR_SAVE_CHARSET) 1N/A** Y free (was: current delay if _FFR_QUEUEDELAY) 1N/A** Z original envelope id from ESMTP 1N/A** ! deliver by (added in 8.12) 1N/A** QUEUEUP -- queue a message up for future transmission. 1N/A** e -- the envelope to queue up. 1N/A** announce -- if true, tell when you are queueing up. 1N/A** msync -- if true, then fsync() if SuperSafe interactive mode. 1N/A** The current request is saved in a control file. 1N/A** The queue file is left locked. 1N/A ** Create control file. 1N/A ** open qf file directly: this will give an error if the file 1N/A ** already exists and hence prevent problems if a queue-id 1N/A ** is reused (e.g., because the clock is set back). 1N/A#
endif /* !SM_OPEN_EXLOCK */ 1N/A syserr(
"!queueup: cannot create queue file %s, euid=%d, fd=%d, fp=%p",
1N/A /* if newid, write the queue file directly (instead of temp file) */ 1N/A /* get a locked tf file */ 1N/A for (i = 0; i <
128; i++)
1N/A "queueup: cannot create %s, euid=%d: %s",
1N/A#
endif /* SM_OPEN_EXLOCK */ 1N/A /* file is locked by open() */ 1N/A#
else /* SM_OPEN_EXLOCK */ 1N/A#
endif /* SM_OPEN_EXLOCK */ 1N/A "queueup: cannot lock %s: %s",
1N/A /* save the old temp file away */ 1N/A syserr(
"!queueup: cannot create queue temp file %s, uid=%d",
1N/A ** If there is no data file yet, create one. 1N/A syserr(
"!queueup: cannot commit data file %s, uid=%d",
1N/A "queueup: fsync(e->e_dfp)");
1N/A syserr(
"!queueup: cannot create data temp file %s, uid=%d",
1N/A "queueup: fsync(dfp)");
1N/A syserr(
"!queueup: cannot save data temp file %s, uid=%d",
1N/A ** Output future work requests. 1N/A ** Priority and creation time should be first, since 1N/A ** they are required by gatherq. 1N/A /* output queue version number (must be first!) */ 1N/A /* output creation time */ 1N/A /* output last delivery time */ 1N/A /* output number of delivery attempts */ 1N/A /* output message priority */ 1N/A ** If data file is in a different directory than the queue file, 1N/A ** output a "d" record naming the directory of the data file. 1N/A /* output inode number of data file */ 1N/A /* output body type */ 1N/A /* quarantine reason */ 1N/A /* message from envelope, if it exists */ 1N/A /* send various flag bits through */ 1N/A /* save $={persistentMacros} macro values */ 1N/A /* output name of sender */ 1N/A /* output ESMTP-supplied "original" information */ 1N/A /* output AUTH= parameter */ 1N/A /* output list of recipient addresses */ 1N/A /* message for this recipient, if it exists */ 1N/A ** Output headers for this message. 1N/A ** Expand macros completely here. Queue run will deal with 1N/A ** everything as absolute headers. 1N/A ** All headers that must be relative to the recipient 1N/A ** can be cracked later. 1N/A ** We set up a "null mailer" -- i.e., a mailer that will have 1N/A ** no effect on the addresses as they are output. 1N/A /* don't output resent headers on non-resent messages */ 1N/A /* expand macros; if null, don't output header at all */ 1N/A /* output this header */ 1N/A /* output conditional macro if present */ 1N/A /* if conditional, output the set of conditions */ 1N/A for (j =
'\0'; j <=
'\177'; j++)
1N/A /* output the header: expand macros, convert addresses */ 1N/A ** Write a terminator record -- this is to prevent 1N/A ** scurrilous crackers from appending any data. 1N/A /* rename (locked) tf to be (locked) [qh]f */ 1N/A ** Check if type has changed and only 1N/A ** remove the old item if the rename above 1N/A /* XXX: something more drastic? */ 1N/A "queueup: unlink(%s) failed: %s",
1N/A ** fsync() after renaming to make sure metadata is 1N/A ** written to disk on filesystems in which renames are 1N/A /* for softupdates */ 1N/A syserr(
"!queueup: cannot fsync queue temp file %s",
1N/A /* close and unlock old (locked) queue file */ 1N/A** PRINTCTLADDR -- print control address to file. 1N/A** tfp -- file pointer. 1N/A** The control address (if changed) is printed to the file. 1N/A** The last control address and uid are saved. 1N/A /* initialization */ 1N/A /* find the active uid */ 1N/A /* check to see if this is the same as last time */ 1N/A** RUNNERS_SIGTERM -- propagate a SIGTERM to queue runner process 1N/A** This propagates the signal to the child processes that are queue 1N/A** runners. This is for a queue runner "cleanup". After all of the 1N/A** child queue runner processes are signaled (it should be SIGTERM 1N/A** being the sig) then the old signal handler (Oldsh) is called 1N/A** to handle any cleanup set for this process (provided it is not 1N/A** SIG_DFL or SIG_IGN). The signal may not be handled immediately 1N/A** if the BlockOldsh flag is set. If the current process doesn't 1N/A** have a parent then handle the signal immediately, regardless of 1N/A** sig -- the signal number being sent 1N/A** Sets the NoMoreRunners boolean to true to stop more runners 1N/A** from being started in runqueue(). 1N/A** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD 1N/A** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE 1N/A /* Check that a valid 'old signal handler' is callable */ 1N/A** RUNNERS_SIGHUP -- propagate a SIGHUP to queue runner process 1N/A** This propagates the signal to the child processes that are queue 1N/A** runners. This is for a queue runner "cleanup". After all of the 1N/A** child queue runner processes are signaled (it should be SIGHUP 1N/A** being the sig) then the old signal handler (Oldsh) is called to 1N/A** handle any cleanup set for this process (provided it is not SIG_DFL 1N/A** or SIG_IGN). The signal may not be handled immediately if the 1N/A** BlockOldsh flag is set. If the current process doesn't have 1N/A** a parent then handle the signal immediately, regardless of 1N/A** sig -- the signal number being sent 1N/A** Sets the NoMoreRunners boolean to true to stop more runners 1N/A** from being started in runqueue(). 1N/A** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD 1N/A** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE 1N/A /* Check that a valid 'old signal handler' is callable */ 1N/A** MARK_WORK_GROUP_RESTART -- mark a work group as needing a restart 1N/A** Sets a workgroup for restarting. 1N/A** wgrp -- the work group id to restart. 1N/A** reason -- why (signal?), -1 to turn off restart 1N/A** May set global RestartWorkGroup to true. 1N/A** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD 1N/A** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE 1N/A** RESTART_MARKED_WORK_GROUPS -- restart work groups marked as needing restart 1N/A** Restart any workgroup marked as needing a restart provided more 1N/A** runners are allowed. 1N/A** Sets global RestartWorkGroup to false. 1N/A /* Block SIGCHLD so reapchild() doesn't mess with us */ 1N/A "restart queue runner=%d due to signal 0x%x",
1N/A** RESTART_WORK_GROUP -- restart a specific work group 1N/A** Restart a specific workgroup provided more runners are allowed. 1N/A** If the requested work group has been restarted too many times log 1N/A** this and refuse to restart. 1N/A** wgrp -- the work group id to restart 1N/A** starts another process doing the work of wgrp 1N/A /* avoid overflow; increment here */ 1N/A "ERROR: persistent queue runner=%d restarted too many times, queue runner lost",
1N/A** SCHEDULE_QUEUE_RUNS -- schedule the next queue run for a work group. 1N/A** runall -- schedule even if individual bit is not set. 1N/A** wgrp -- the work group id to schedule. 1N/A** didit -- the queue run was performed for this work group. 1N/A#
endif /* _FFR_QUEUE_SCHED_DBG */ 1N/A ** This is a bit ugly since we have to duplicate the 1N/A ** code that "walks" through a work queue group. 1N/A#
endif /* _FFR_QUEUE_SCHED_DBG */ 1N/A#
endif /* _FFR_QUEUE_SCHED_DBG */ 1N/A#
endif /* _FFR_QUEUE_SCHED_DBG */ 1N/A ** Only set a new time if a queue run was performed 1N/A ** for this queue group. If the queue was not run, 1N/A ** we could starve it by setting a new time on each 1N/A "sqr: wgrp=%d, cgrp=%d, qgrp=%d, intvl=%ld, QI=%ld, runall=%d, lastrun=%ld, nextrun=%ld, sched=%d",
1N/A#
endif /* _FFR_QUEUE_SCHED_DBG */ 1N/A** CHECKQUEUERUNNER -- check whether a queue group hasn't been run. 1N/A** Use this if events may get lost and hence queue runners may not 1N/A** be started and mail will pile up in a queue. 1N/A** true if a queue run is necessary. 1N/A** may schedule a queue run. 1N/A "checkqueuerunner: queue %d should have been run at %s, queue interval %ld",
1N/A#
endif /* _FFR_QUEUE_RUN_PARANOIA */ 1N/A** RUNQUEUE -- run the jobs in the queue. 1N/A** Gets the stuff out of the queue in some presumably logical 1N/A** order and processes them. 1N/A** forkflag -- true if the queue scanning should be done in 1N/A** a child process. We double-fork so it is not our 1N/A** child and we don't have to clean up after it. 1N/A** false can be ignored if we have multiple queues. 1N/A** verbose -- if true, print out status information. 1N/A** persistent -- persistent queue runner? 1N/A** runall -- run all groups or only a subset (DoQueueRun)? 1N/A** true if the queue run successfully began. 1N/A** runs things in the mail queue using run_work_group(). 1N/A** maybe schedules next queue run. 1N/A/* values for qp_supdirs */ 1N/A#
endif /* SM_HEAP_CHECK */ 1N/A /* queue run has been started, don't do any more this time */ 1N/A /* more than one queue or more than one directory per queue */ 1N/A ** For controlling queue runners via signals sent to this process. 1N/A ** Oldsh* will get called too by runners_sig* (if it is not SIG_IGN 1N/A ** or SIG_DFL) to preserve cleanup behavior. Now that this process 1N/A ** will have children (and perhaps grandchildren) this handler will 1N/A ** be left in place. This is because this process, once it has 1N/A ** finished spinning off queue runners, may go back to doing something 1N/A ** else (like being a daemon). And we still want on a SIG{TERM,HUP} to 1N/A ** clean up the child queue runners. Only install 'runners_sig*' once 1N/A ** else we'll get stuck looping forever. 1N/A ** If MaxQueueChildren active then test whether the start 1N/A ** of the next queue group's additional queue runners (maximum) 1N/A ** will result in MaxQueueChildren being exceeded. 1N/A ** Note: do not use continue; even though another workgroup 1N/A ** may have fewer queue runners, this would be "unfair", 1N/A ** i.e., this work group might "starve" then. 1N/A "rq: curnum=%d, MaxQueueChildren=%d, CurRunners=%d, WorkGrp[curnum].wg_maxact=%d",
1N/A#
endif /* _FFR_QUEUE_SCHED_DBG */ 1N/A ** Pick up where we left off (curnum), in case we 1N/A ** used up all the children last time without finishing. 1N/A ** This give a round-robin fairness to queue runs. 1N/A ** Increment CurRunners before calling run_work_group() 1N/A ** to avoid a "race condition" with proc_list_drop() which 1N/A ** decrements CurRunners if the queue runners terminate. 1N/A ** Notice: CurRunners is an upper limit, in some cases 1N/A ** (too few jobs in the queue) this value is larger than 1N/A ** the actual number of queue runners. The discrepancy can 1N/A ** increase if some queue runners "hang" for a long time. 1N/A ** Failure means a message was printed for ETRN 1N/A ** and subsequent queues are likely to fail as well. 1N/A ** Decrement CurRunners in that case because 1N/A ** none have been started. 1N/A /* schedule left over queue runs */ 1N/A#
endif /* SM_HEAP_CHECK */ 1N/A** SKIP_DOMAINS -- Skip 'skip' number of domains in the WorkQ. 1N/A** Added by Stephen Frost <sfrost@snowman.net> to support 1N/A** having each runner process every N'th domain instead of 1N/A** every N'th message. 1N/A** skip -- number of domains in WorkQ to skip. 1N/A** total number of messages skipped. 1N/A#
endif /* _FFR_SKIP_DOMAINS */ 1N/A** RUNNER_WORK -- have a queue runner do its work 1N/A** Have a queue runner do its work a list of entries. 1N/A** When work isn't directly being done then this process can take a signal 1N/A** and terminate immediately (in a clean fashion of course). 1N/A** When work is directly being done, it's not to be interrupted 1N/A** immediately: the work should be allowed to finish at a clean point 1N/A** before termination (in a clean fashion of course). 1N/A** sequenceno -- 'th process to run WorkQ. 1N/A** didfork -- did the calling process fork()? 1N/A** skip -- process only each skip'th item. 1N/A** njobs -- number of jobs in WorkQ. 1N/A** runs things in the mail queue. 1N/A ** Here we temporarily block the second calling of the handlers. 1N/A ** This allows us to handle the signal without terminating in the 1N/A ** middle of direct work. If a signal does come, the test for 1N/A ** NoMoreRunners will find it. 1N/A /* process them once at a time */ 1N/A#
endif /* SM_HEAP_CHECK */ 1N/A /* do no more work */ 1N/A /* Check that a valid signal handler is callable */ 1N/A w =
WorkQ;
/* assign current work item */ 1N/A ** Set the head of the WorkQ to the next work item. 1N/A ** It is set 'skip' ahead (the number of parallel queue 1N/A ** runners working on WorkQ together) since each runner 1N/A ** works on every 'skip'th (N-th) item. 1N/A#if _FFR_SKIP_DOMAINS 1N/A ** In the case of the BYHOST Queue Sort Order, the 'item' 1N/A ** is a domain, so we work on every 'skip'th (N-th) domain. 1N/A#endif * _FFR_SKIP_DOMAINS * 1N/A#
endif /* _FFR_SKIP_DOMAINS */ 1N/A ** Ignore jobs that are too expensive for the moment. 1N/A ** Get new load average every GET_NEW_LA_TIME seconds. 1N/A char *
msg =
"Aborting queue run: load average too high";
1N/A message(
"Skipping %s/%s (sequence %d of %d) and flushing rest of queue",
1N/A "runqueue: Flushing queue from %s/%s (pri %ld, LA %d, %d of %d)",
1N/A "runqueue %s dowork(%s)",
1N/A#
endif /* SM_HEAP_CHECK */ 1N/A /* check the signals didn't happen during the revert */ 1N/A /* Check that a valid signal handler is callable */ 1N/A** RUN_WORK_GROUP -- run the jobs in a queue group from a work group. 1N/A** Gets the stuff out of the queue in some presumably logical 1N/A** order and processes them. 1N/A** wgrp -- work group to process. 1N/A** flags -- RWG_* flags 1N/A** true if the queue run successfully began. 1N/A** runs things in the mail queue. 1N/A/* Minimum sleep time for persistent queue runners */ 1N/A ** If no work will ever be selected, don't even bother reading 1N/A char *
msg =
"Skipping queue run -- load average too high";
1N/A ** See if we already have too many children. 1N/A char *
msg =
"Skipping queue run -- too many children";
1N/A ** See if we want to go off and do other useful work. 1N/A const char *
msg =
"Skipping queue run -- fork() failed";
1N/A /* parent -- pick up intermediate zombie */ 1N/A /* wgrp only used when queue runners are persistent */ 1N/A /* child -- clean up signals */ 1N/A /* Reset global flags */ 1N/A ** Initialize exception stack and default exception 1N/A ** handler for child process. 1N/A /* Add parent process as first child item */ 1N/A ** Release any resources used by the daemon code. 1N/A /* force it to run expensive jobs */ 1N/A /* drop privileges */ 1N/A ** Create ourselves an envelope 1N/A /* make sure we have disconnected from parent */ 1N/A ** If we are running part of the queue, always ignore stored 1N/A ** Here is where we choose the queue group from the work group. 1N/A ** The caller of the "domorework" label must setup a new envelope. 1N/A ** Run a queue group if: 1N/A ** RWG_RUNALL bit is set or the bit for this group is set. 1N/A ** Find the next queue group within the work group that 1N/A ** has been marked as needing a run. 1N/A return true;
/* we're done */ 1N/A "rwg: wgrp=%d, qgrp=%d, qdir=%d, name=%s, curqgrp=%d, numgrps=%d",
1N/A#
endif /* _FFR_QUEUE_SCHED_DBG */ 1N/A /* tweak niceness of queue runs */ 1N/A /* XXX running queue group... */ 1N/A "runqueue %s, pid=%d, forkflag=%d",
1N/A ** Start making passes through the queue. 1N/A ** First, read and sort the entire queue. 1N/A ** Then, process the work in that order. 1N/A ** But if you take too long, start over. 1N/A#
endif /* SM_CONF_SHM */ 1N/A /* If there are no more items in this queue advance */ 1N/A /* A round-robin advance */ 1N/A /* Has the WorkList reached the limit? */ 1N/A break;
/* don't try to gather more */ 1N/A /* order the existing work requests */ 1N/A ** For this WorkQ we want to fork off N children (maxrunners) 1N/A ** at this point. Each child has a copy of WorkQ. Each child 1N/A ** will process every N-th item. The parent will wait for all 1N/A ** of the children to finish before moving on to the next 1N/A ** queue group within the work group. This saves us forking 1N/A ** a new runner-child for each work item. 1N/A ** It's valid for qg_maxqrun == 0 since this may be an 1N/A ** explicit "don't run this queue" setting. 1N/A ** If no runners are configured for this group but 1N/A ** the queue is "forced" then lets use 1 runner. 1N/A /* No need to have more runners then there are jobs */ 1N/A ** Since the delivery may happen in a child and the 1N/A ** parent does not wait, the parent may close the 1N/A ** maps thereby removing any shared memory used by 1N/A ** the map. Therefore, close the maps now so the 1N/A ** child will dynamically open them if necessary. 1N/A /* parent -- clean out connection cache */ 1N/A#
endif /* _FFR_SKIP_DOMAINS */ 1N/A /* No additional work, no additional runners */ 1N/A /* child -- Reset global flags */ 1N/A ** Initialize exception stack and default 1N/A ** exception handler for child process. 1N/A ** When fork()'d the child now has a private 1N/A ** copy of WorkQ at its current position. 1N/A ** SMTP processes (whether -bd or -bs) set 1N/A ** SIGCHLD to reapchild to collect 1N/A ** children status. However, at delivery 1N/A ** time, that status must be collected 1N/A ** by sm_wait() to be dealt with properly 1N/A ** (check success of delivery based 1N/A ** on status code, etc). Therefore, if we 1N/A ** are an SMTP process, reset SIGCHLD 1N/A ** back to the default so reapchild 1N/A ** doesn't collect status before 1N/A /* child -- error messages to the transcript */ 1N/A /* This child is done */ 1N/A ** Wait until all of the runners have completed before 1N/A ** seeing if there is another queue group in the 1N/A ** work group to process. 1N/A ** XXX Future enhancement: don't wait() for all children 1N/A ** here, just go ahead and make sure that overall the number 1N/A ** of children is not exceeded. 1N/A ** When current process will not fork children to do the work, 1N/A ** it will do the work itself. The 'skip' will be 1 since 1N/A ** there are no child runners to divide the work across. 1N/A /* free memory allocated by newenvelope() above */ 1N/A /* Are there still more queues in the work group to process? */ 1N/A /* No more queues in work group to process. Now check persistent. */ 1N/A ** close bogus maps, i.e., maps which caused a tempfail, 1N/A ** so we get fresh map connections on the next lookup. 1N/A ** closemaps() is also called when children are started. 1N/A /* Close any cached connections. */ 1N/A /* Clean out expired related entries. */ 1N/A /* Update MX records for FallbackMX. */ 1N/A#
endif /* NAMED_BIND */ 1N/A /* close UserDatabase */ 1N/A#
endif /* SM_HEAP_CHECK */ 1N/A /* let me rest for a second to catch my breath */ 1N/A ** Get the LA outside the WorkQ loop if necessary. 1N/A ** In a persistent queue runner the code is repeated over 1N/A ** and over but gatherq() may ignore entries due to 1N/A ** shouldqueue() (do we really have to do this twice?). 1N/A ** Hence the queue runners would just idle around when once 1N/A ** CurrentLA caused all entries in a queue to be ignored. 1N/A /* exit without the usual cleanup */ 1N/A** DOQUEUERUN -- do a queue run? 1N/A** RUNQUEUEEVENT -- Sets a flag to indicate that a queue run should be done. 1N/A** The invocation of this function via an alarm may interrupt 1N/A** a set of actions. Thus errno may be set in that context. 1N/A** We need to restore errno at the end of this function to ensure 1N/A** that any work done here that sets errno doesn't return a 1N/A** API was active. Iff this is true we will override errno as 1N/A** a timeout (as a more accurate error message). 1N/A** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD 1N/A** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE 1N/A ** Set the general bit that we want a queue run, 1N/A ** tested in doqueuerun() 1N/A#
endif /* _FFR_QUEUE_SCHED_DBG */ 1N/A** GATHERQ -- gather messages from the message queue(s) the work queue. 1N/A** qgrp -- the index of the queue group. 1N/A** qdir -- the index of the queue directory. 1N/A** doall -- if set, include everything in the queue (even 1N/A** the jobs that cannot be run because the load 1N/A** average is too high, or MaxQueueRun is reached). 1N/A** Otherwise, exclude those jobs. 1N/A** full -- (optional) to be set 'true' if WorkList is full 1N/A** more -- (optional) to be set 'true' if there are still more 1N/A** messages in this queue not added to WorkList 1N/A** pnentries -- (optional) total nuber of entries in queue 1N/A** The number of request in the queue (not necessarily 1N/A** the number of requests in WorkList however). 1N/A** prepares available work into WorkList 1N/A /* open the queue directory */ 1N/A ** Read the work directory. 1N/A /* is this an interesting entry? */ 1N/A "gatherq: %s too long, %d max characters\n",
1N/A "gatherq: %s too long, %d max characters",
1N/A /* grow work list if necessary */ 1N/A "WorkList for %s maxed out at %d",
1N/A continue;
/* just count entries */ 1N/A "gatherq: can't stat %s/%s",
1N/A /* Yikes! Skip it or we will hang on open! */ 1N/A syserr(
"gatherq: %s/%s is not a regular file",
1N/A /* avoid work if possible */ 1N/A /* open control file */ 1N/A /* this may be some random person sending hir msgs */ 1N/A /* make sure jobs in creation don't clog queue */ 1N/A /* extract useful information */ 1N/A#
endif /* _FFR_RHS */ 1N/A /* need w_host set for host sort order */ 1N/A /* flush rest of overly long line */ 1N/A#
endif /* _FFR_RHS */ 1N/A ++p;
/* skip over ':' */ 1N/A#
endif /* _FFR_EXPDELAY */ 1N/A /* don't even bother sorting this job in */ 1N/A** SORTQ -- sort the work list 1N/A** First the old WorkQ is cleared away. Then the WorkList is sorted 1N/A** for all items so that important (higher sorting value) items are not 1N/A** trunctated off. Then the most important items are moved from 1N/A** WorkList to WorkQ. The lower count of 'max' or MaxListCount items 1N/A** max -- maximum number of items to be placed in WorkQ 1N/A** the number of items in WorkQ 1N/A** WorkQ gets released and filled with new work. WorkList 1N/A** gets released. Work items get sorted in order. 1N/A register int i;
/* local counter */ 1N/A register WORK *w;
/* tmp item pointer */ 1N/A /* Clear out old WorkQ. */ 1N/A ** The sort now takes place using all of the items in WorkList. 1N/A ** The list gets trimmed to the most important items after the sort. 1N/A ** If the trim were to happen before the sort then one or more 1N/A ** important items might get truncated off -- not what we want. 1N/A ** Sort the work directory for the first time, 1N/A ** based on host name, lock status, and priority. 1N/A ** If one message to host is locked, "lock" all messages 1N/A ** Sort the work directory for the second time, 1N/A ** based on lock status, host name, and priority. 1N/A ** Simple sort based on submission time only. 1N/A ** Sort based on queue filename. 1N/A ** Sort randomly. To avoid problems with an instable sort, 1N/A ** use a random index into the queue file name to start 1N/A ** Simple sort based on modification time of queue file. 1N/A ** This puts the oldest items first. 1N/A ** Simple sort based on shuffled host name. 1N/A#
endif /* _FFR_RHS */ 1N/A ** Simple sort based on queue priority only. 1N/A /* else don't sort at all */ 1N/A /* Check if the per queue group item limit will be exceeded */ 1N/A ** Convert the work list into canonical form. 1N/A ** Should be turning it into a list of envelopes here perhaps. 1N/A ** Only take the most important items up to the per queue group 1N/A /* free the rest of the list */ 1N/A return wc;
/* return number of WorkQ items */ 1N/A** GROW_WLIST -- make the work list larger 1N/A** qgrp -- the index for the queue group. 1N/A** qdir -- the index for the queue directory. 1N/A** Adds another QUEUESEGSIZE entries to WorkList if possible. 1N/A** It can fail if there isn't enough memory, so WorkListSize 1N/A** should be checked again upon return. 1N/A "grew WorkList for %s to %d",
1N/A "FAILED to grow WorkList for %s to %d",
1N/A** WORKCMPF0 -- simple priority-only compare function. 1N/A** a -- the first argument. 1N/A** b -- the second argument. 1N/A** WORKCMPF1 -- first compare function for ordering work based on host name. 1N/A** Sorts on host name, lock status, and priority in that order. 1N/A** a -- the first argument. 1N/A** b -- the second argument. 1N/A** WORKCMPF2 -- second compare function for ordering work based on host name. 1N/A** Sorts on lock status, host name, and priority in that order. 1N/A** a -- the first argument. 1N/A** b -- the second argument. 1N/A** WORKCMPF3 -- simple submission-time-only compare function. 1N/A** a -- the first argument. 1N/A** b -- the second argument. 1N/A** WORKCMPF4 -- compare based on file name 1N/A** a -- the first argument. 1N/A** b -- the second argument. 1N/A** WORKCMPF5 -- compare based on assigned random number 1N/A** a -- the first argument. 1N/A** b -- the second argument. 1N/A** WORKCMPF6 -- simple modification-time-only compare function. 1N/A** a -- the first argument. 1N/A** b -- the second argument. 1N/A** WORKCMPF7 -- compare function for ordering work based on shuffled host name. 1N/A** Sorts on lock status, host name, and priority in that order. 1N/A** a -- the first argument. 1N/A** b -- the second argument. 1N/A#
endif /* _FFR_RHS */ 1N/A** STRREV -- reverse string 1N/A** Returns a pointer to a new string that is the reverse of 1N/A** the string pointed to by fwd. The space for the new 1N/A** string is obtained using xalloc(). 1N/A** fwd -- the string to reverse. 1N/A** the reversed string. 1N/A /* fill the ShuffledAlphabet */ 1N/A /* make it case insensitive */ 1N/A for (i =
'A'; i <=
'Z'; i++)
1N/A /* fill the upper part */ 1N/A const unsigned char *
us1 = (
const unsigned char *) a;
1N/A const unsigned char *
us2 = (
const unsigned char *) b;
1N/A#
endif /* _FFR_RHS */ 1N/A** DOWORK -- do a work request. 1N/A** qgrp -- the index of the queue group for the job. 1N/A** qdir -- the index of the queue directory for the job. 1N/A** id -- the ID of the job to run. 1N/A** forkflag -- if set, run this in background. 1N/A** requeueflag -- if set, reinstantiate the queue quickly. 1N/A** This is used when expanding aliases in the queue. 1N/A** If forkflag is also set, it doesn't wait for the 1N/A** e - the envelope in which to run it. 1N/A** process id of process that is running the queue job. 1N/A** The work request is satisfied if possible. 1N/A ** Since the delivery may happen in a child and the 1N/A ** parent does not wait, the parent may close the 1N/A ** maps thereby removing any shared memory used by 1N/A ** the map. Therefore, close the maps now so the 1N/A ** child will dynamically open them if necessary. 1N/A /* parent -- clean out connection cache */ 1N/A ** Initialize exception stack and default exception 1N/A ** handler for child process. 1N/A /* Reset global flags */ 1N/A ** See note above about SMTP processes and SIGCHLD. 1N/A /* child -- error messages to the transcript */ 1N/A ** Lock the control file to avoid duplicate deliveries. 1N/A ** Then run the file as though we had just read it. 1N/A ** We save an idea of the temporary name so we 1N/A ** can recover on interrupt. 1N/A /* Reset global flags */ 1N/A /* set basic modes, etc. */ 1N/A /* read the queue control file -- return if locked */ 1N/A /* adding this frees 8 bytes */ 1N/A /* adding this frees 12 bytes */ 1N/A /* do the delivery */ 1N/A /* finish up and exit */ 1N/A** DOWORKLIST -- process a list of envelopes as work requests 1N/A** Similar to dowork(), except that after forking, it processes an 1N/A** envelope and its siblings, treating each envelope as a work request. 1N/A** el -- envelope to be processed including its siblings. 1N/A** forkflag -- if set, run this in background. 1N/A** requeueflag -- if set, reinstantiate the queue quickly. 1N/A** This is used when expanding aliases in the queue. 1N/A** If forkflag is also set, it doesn't wait for the 1N/A** process id of process that is running the queue job. 1N/A** The work request is satisfied if possible. 1N/A ** Since the delivery may happen in a child and the 1N/A ** parent does not wait, the parent may close the 1N/A ** maps thereby removing any shared memory used by 1N/A ** the map. Therefore, close the maps now so the 1N/A ** child will dynamically open them if necessary. 1N/A /* parent -- clean out connection cache */ 1N/A ** Initialize exception stack and default exception 1N/A ** handler for child process. 1N/A /* Reset global flags */ 1N/A ** See note above about SMTP processes and SIGCHLD. 1N/A /* child -- error messages to the transcript */ 1N/A ** Lock the control file to avoid duplicate deliveries. 1N/A ** Then run the file as though we had just read it. 1N/A ** We save an idea of the temporary name so we 1N/A ** can recover on interrupt. 1N/A /* Reset global flags */ 1N/A /* set basic modes, etc. */ 1N/A /* read the queue control file -- return if locked */ 1N/A /* do the delivery */ 1N/A /* restore CurEnv */ 1N/A /* finish up and exit */ 1N/A** READQF -- read queue file and set up environment. 1N/A** e -- the envelope of the job to run. 1N/A** openonly -- only open the qf (returned as e_lockfp) 1N/A** true if it successfully read the queue file. 1N/A** The queue file is returned locked. 1N/A ** Read and process the file. 1N/A /* being processed by another queuer */ 1N/A ** Prevent locking race condition. 1N/A ** Process A: readqf(): qfp = fopen(qffile) 1N/A ** Process B: queueup(): rename(tf, qf) 1N/A ** Process B: unlocks(tf) 1N/A ** Process A: lockfile(qf); 1N/A ** Process A (us) has the old qf file (before the rename deleted 1N/A ** the directory entry) and will be delivering based on old data. 1N/A ** This can lead to multiple deliveries of the same recipients. 1N/A ** Catch this by checking if the underlying qf file has changed 1N/A ** *after* acquiring our lock and if so, act as though the file 1N/A ** was still locked (i.e., just return like the lockfile() case 1N/A /* must have been being processed by someone else */ 1N/A#
endif /* HAS_ST_GEN && 0 */ 1N/A /* changed after opened */ 1N/A ** Check the queue file for plausibility to avoid attacks. 1N/A ** If this qf file results from a set-group-ID binary, then 1N/A ** we check whether the directory is group-writable, 1N/A ** the queue file mode contains the group-writable bit, and 1N/A ** the groups are the same. 1N/A ** Notice: this requires that the set-group-ID binary is used to 1N/A "bogus queue file, uid=%d, gid=%d, mode=%o",
1N/A /* must be a bogus file -- if also old, just remove it */ 1N/A ** Race condition -- we got a file just as it was being 1N/A ** unlinked. Just assume it is zero length. 1N/A ** If we don't own the file mark it as unsafe. 1N/A ** However, allow TrustedUser to own it as well 1N/A ** in case TrustedUser manipulates the queue. 1N/A#
else /* _FFR_TRUSTED_QF */ 1N/A /* If we don't own the file mark it as unsafe */ 1N/A#
endif /* _FFR_TRUSTED_QF */ 1N/A /* good file -- save this lock */ 1N/A /* Just wanted the open file */ 1N/A /* do basic system initialization */ 1N/A#
endif /* _FFR_QUEUE_MACRO */ 1N/A syserr(
"SECURITY ALERT: extra or bogus data in queue file: %s",
1N/A case 'A':
/* AUTH= parameter */ 1N/A case 'B':
/* body type */ 1N/A case 'C':
/* specify controlling user */ 1N/A case 'D':
/* data file name */ 1N/A /* obsolete -- ignore */ 1N/A case 'd':
/* data file directory name */ 1N/A /* forbid queue groups in MSP? */ 1N/A#
endif /* _FFR_MSP_PARANOIA */ 1N/A err =
"bogus queue file directory";
1N/A case 'E':
/* specify error recipient */ 1N/A /* no longer used */ 1N/A case 'F':
/* flag bits */ 1N/A /* we are being spoofed! */ 1N/A for (p = &
bp[
1]; *p !=
'\0'; p++)
1N/A case '8':
/* has 8 bit data */ 1N/A case 'b':
/* delete Bcc: header */ 1N/A case 'd':
/* envelope has DSN RET= */ 1N/A case 'n':
/* don't return body */ 1N/A case 'r':
/* response */ 1N/A case 's':
/* split */ 1N/A case 'w':
/* warning sent */ 1N/A case 'q':
/* quarantine reason */ 1N/A case 'H':
/* header */ 1N/A ** count size before chompheader() destroys the line. 1N/A ** this isn't accurate due to macro expansion, but 1N/A ** better than before. "-3" to skip H?? at least. 1N/A case 'I':
/* data file's inode number */ 1N/A /* regenerated below */ 1N/A case 'K':
/* time of last delivery attempt */ 1N/A case 'L':
/* Solaris Content-Length: */ 1N/A case 'M':
/* message */ 1N/A /* ignore this; we want a new message next time */ 1N/A case 'N':
/* number of delivery attempts */ 1N/A /* if this has been tried recently, let it be */ 1N/A "%s: too young (%s)\n",
1N/A /* adjust BIND parameters immediately */ 1N/A#
endif /* NAMED_BIND */ 1N/A case 'P':
/* message priority */ 1N/A case 'Q':
/* original recipient */ 1N/A case 'r':
/* final recipient */ 1N/A case 'R':
/* specify recipient */ 1N/A while (*++p !=
'\0' && *p !=
':')
1N/A default:
/* ignore or complain? */ 1N/A /* make sure we keep the current qgrp */ 1N/A case 'S':
/* sender */ 1N/A case 'T':
/* init time */ 1N/A case 'V':
/* queue file version number */ 1N/A syserr(
"Version number in queue file (%d) greater than max (%d)",
1N/A err =
"unsupported queue file version";
1N/A case 'Z':
/* original envelope id from ESMTP */ 1N/A case '!':
/* deliver by */ 1N/A /* format: flag (1 char) space long-integer */ 1N/A case '$':
/* define macro */ 1N/A /* XXX elimate p? */ 1N/A case '.':
/* terminate file */ 1N/A ** Maintain backward compatibility for 1N/A ** users who defined _FFR_QUEUEDELAY in 1N/A ** previous releases. Remove this 1N/A ** code in 8.14 or 8.15. 1N/A /* If not qfver 5 or 7, then 'G' or 'Y' is invalid */ 1N/A#
endif /* _FFR_QUEUEDELAY */ 1N/A syserr(
"readqf: %s: line %d: bad line \"%s\"",
1N/A ** If we haven't read any lines, this queue file is empty. 1N/A ** Arrange to remove it without referencing any null pointers. 1N/A /* Check to make sure we have a complete queue file read */ 1N/A /* Check to make sure key fields were read */ 1N/A syserr(
"readqf: %s: sender not specified in queue file",
qf);
1N/A#
endif /* _FFR_QF_PARANOIA */ 1N/A /* possibly set ${dsn_ret} macro */ 1N/A ** Arrange to read the data file. 1N/A ** There was some error reading the qf file (reason is in err var.) 1N/A ** close file; clear e_lockfp since it is the same as qfp, 1N/A ** hence it is invalid (as file) after qfp is closed; 1N/A ** the qf file is on disk, so set the flag to avoid calling 1N/A ** queueup() with bogus data. 1N/A** PRTSTR -- print a string, "unprintable" characters are shown as \oct 1N/A** s -- string to print 1N/A** ml -- maximum length of output 1N/A** Prints a string on stdout. 1N/A while (
ml-- > 0 && ((c = *s++) !=
'\0'))
1N/A "\\%03o", c &
0xFF);
1N/A** PRINTNQE -- print out number of entries in the mail queue 1N/A** out -- output file pointer. 1N/A** prefix -- string to output in front of each line. 1N/A "Data unavailable: shared memory not updated\n");
1N/A "%s: unknown number of entries\n",
1N/A "\t\tTotal requests: %d%s\n",
1N/A#
else /* SM_CONF_SHM */ 1N/A "Data unavailable without shared memory support\n");
1N/A#
endif /* SM_CONF_SHM */ 1N/A** PRINTQUEUE -- print out a representation of the mail queue 1N/A** Prints a listing of the mail queue on the standard output. 1N/A "\t\tTotal requests: %d\n",
1N/A** PRINT_SINGLE_QUEUE -- print out a representation of a single mail queue 1N/A** qgrp -- the index of the queue group. 1N/A** qdir -- the queue directory. 1N/A** number of requests in mail queue. 1N/A** Prints a listing of the mail queue on the standard output. 1N/A ** Check for permission to print the queue 1N/A#
endif /* NGROUPS_MAX */ 1N/A#
else /* NGROUPS_MAX */ 1N/A#
endif /* NGROUPS_MAX */ 1N/A usrerr(
"510 You are not permitted to see the queue");
1N/A ** Read and order the queue. 1N/A ** Print the work list that we have read. 1N/A /* first see if there is anything */ 1N/A " (permission denied)\n");
1N/A " (job completed)\n");
1N/A ** Maybe the df file can't be statted because 1N/A ** it is in a different directory than the qf file. 1N/A ** In order to find out, we must read the qf file. 1N/A case 'V':
/* queue file version */ 1N/A case 'M':
/* error message */ 1N/A case 'q':
/* quarantine reason */ 1N/A case 'B':
/* body type */ 1N/A case 'S':
/* sender name */ 1N/A "%8ld %10ld%c%.12s ",
1N/A "\n QUARANTINE: %.*s",
1N/A case 'C':
/* controlling user */ 1N/A "\n\t\t\t\t\t\t(---%.64s---)",
1N/A case 'R':
/* recipient name */ 1N/A case 'T':
/* creation time */ 1N/A case 'F':
/* flag bits */ 1N/A for (p = &
buf[
1]; *p !=
'\0'; p++)
1N/A " (no control file)");
1N/A** QUEUE_LETTER -- get the proper queue letter for the current QueueMode. 1N/A** type -- the file type, used as the first character 1N/A /* Change type according to QueueMode */ 1N/A /* should never happen */ 1N/A** QUEUENAME -- build a file name in the queue directory for this envelope. 1N/A** type -- the file type, used as the first character 1N/A** a pointer to the queue name (in a static buffer). 1N/A** If no id code is already assigned, queuename() will 1N/A** assign an id code with assign_queueid(). If no queue 1N/A** directory is assigned, one will be set with setnewqueue(). 1N/A /* Assign an ID if needed */ 1N/A /* begin of filename */ 1N/A ** We don't want to call setnewqueue() if we are fetching 1N/A ** the pathname of the transcript file, because setnewqueue 1N/A ** chooses a queue, and sometimes we need to write to the 1N/A ** transcript file before we have gathered enough information 1N/A ** to choose a queue. 1N/A /* xf files always have a valid qd and qg picked above */ 1N/A** INIT_QID_ALG -- Initialize the (static) parameters that are used to 1N/A** generate a queue ID. 1N/A** This function is called by the daemon to reset 1N/A** LastQueueTime and LastQueuePid which are used by assign_queueid(). 1N/A** Otherwise the algorithm may cause problems because 1N/A** LastQueueTime and LastQueuePid are set indirectly by main() 1N/A** before the daemon process is started, hence LastQueuePid is not 1N/A** the pid of the daemon and therefore a child of the daemon can 1N/A** actually have the same pid as LastQueuePid which means the section 1N/A** in assign_queueid(): 1N/A** is NOT triggered which will cause the same queue id to be generated. 1N/A** ASSIGN_QUEUEID -- assign a queue ID for this envelope. 1N/A** Assigns an id code if one does not already exist. 1N/A** This code assumes that nothing will remain in the queue for 1N/A** longer than 60 years. It is critical that files with the given 1N/A** name do not already exist in the queue. 1N/A** [No longer initializes e_qdir to NOQDIR.] 1N/A** e -- envelope to set it in. 1N/Astatic const char QueueIdChars[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
1N/A** Note: the length is "officially" 60 because minutes and seconds are 1N/A** usually only 0-59. However (Linux): 1N/A** tm_sec The number of seconds after the minute, normally in 1N/A** the range 0 to 59, but can be up to 61 to allow for 1N/A** Hence the real length of the string is 62 to take this into account. 1N/A** Alternatively % QIC_LEN can (should) be used for access everywhere. 1N/A static unsigned int cX = 0;
1N/A /* if the first time through, pick a random offset */ 1N/A ** Generate a new sequence number between 0 and QIC_LEN_SQR-1. 1N/A ** This lets us generate up to QIC_LEN_SQR unique queue ids 1N/A ** per second, per process. With envelope splitting, 1N/A ** a single message can consume many queue ids. 1N/A /* XXX: inherited from MainEnvelope */ 1N/A /* New ID means it's not on disk yet */ 1N/A** SYNC_QUEUE_TIME -- Assure exclusive PID in any given second 1N/A** Make sure one PID can't be used by two processes in any one second. 1N/A** If the system rotates PIDs fast enough, may get the 1N/A** same pid in the same second for two distinct processes. 1N/A** This will interfere with the queue file naming system. 1N/A#
endif /* FAST_PID_RECYCLE */ 1N/A** UNLOCKQUEUE -- unlock the queue entry for a specified envelope 1N/A** e -- the envelope to unlock. 1N/A** unlocks the queue for `e'. 1N/A /* if there is a lock file in the envelope, close it */ 1N/A /* don't create a queue id if we don't already have one */ 1N/A /* remove the transcript */ 1N/A** SETCTLUSER -- create a controlling address 1N/A** Create a fake "address" given only a local login name; this is 1N/A** used as a "controlling user" for future recipient addresses. 1N/A** user -- the user name of the controlling user. 1N/A** qfver -- the version stamp of this queue file. 1N/A** An address descriptor for the controlling user, 1N/A** using storage allocated from e->e_rpool. 1N/A ** See if this clears our concept of controlling user. 1N/A ** Set up addr fields for controlling user. 1N/A /* if there is another ':': restore it */ 1N/A** LOSEQFILE -- rename queue file with LOSEQF_LETTER & try to let someone know 1N/A** e -- the envelope (e->e_id will be used). 1N/A** why -- reported to whomever can hear. 1N/A /* if already lost, no need to re-lose */ 1N/A** NAME2QID -- translate a queue group name to a queue group id 1N/A** queuename -- name of queue group. 1N/A** queue group id if found. 1N/A** QID_PRINTNAME -- create externally printable version of queue id 1N/A** e -- the envelope. 1N/A** a printable version 1N/A** QID_PRINTQUEUE -- create full version of queue directory for data files 1N/A** qgrp -- index in queue group. 1N/A** qdir -- the short version of the queue directory 1N/A** the full pathname to the queue (might point to a static var) 1N/A** PICKQDIR -- Pick a queue directory from a queue group 1N/A** fsize -- file size in bytes 1N/A** e -- envelope, or NULL 1N/A** NOQDIR if no queue directory in qg has enough free space to 1N/A** hold a file of size 'fsize', otherwise the index of 1N/A** a randomly selected queue directory which resides on a 1N/A** file system with enough disk space. 1N/A** XXX This could be extended to select a queuedir with 1N/A** a few (the fewest?) number of entries. That data 1N/A** is available if shared memory is used. 1N/A** If the request fails and e != NULL then sm_syslog is called. 1N/A /* Pick a random directory, as a starting point. */ 1N/A#
endif /* _FFR_TESTS */ 1N/A ** Now iterate over the queue directories, 1N/A ** looking for a directory with enough space for this message. 1N/A ** might be not correctly updated, 1N/A ** let's try to get the info directly. 1N/A#
endif /* SM_CONF_SHM */ 1N/A "low on space (%s needs %ld bytes + %ld blocks in %s), max avail: %ld",
1N/A** SETNEWQUEUE -- Sets a new queue group and directory 1N/A** Assign a queue group and directory to an envelope and store the 1N/A** directory in e->e_qdir. 1N/A** e -- envelope to assign a queue for. 1N/A** true if successful 1N/A** On success, e->e_qgrp and e->e_qdir are non-negative. 1N/A** On failure (not enough disk space), 1N/A** e->qgrp = NOQGRP, e->e_qdir = NOQDIR 1N/A** and usrerr() is invoked (which could raise an exception). 1N/A /* not set somewhere else */ 1N/A ** Use the queue group of the "first" recipient, as set by 1N/A ** the "queuegroup" rule set. If that is not defined, then 1N/A ** use the queue group of the mailer of the first recipient. 1N/A ** If that is not defined either, then use the default 1N/A ** Notice: "first" depends on the sorting of sendqueue 1N/A ** To avoid problems with "bad" recipients look 1N/A ** for a valid address first. 1N/A usrerr(
"452 4.4.5 Insufficient disk space; try again later");
1N/A** CHKQDIR -- check a queue directory 1N/A** name -- name of queue directory 1N/A** sff -- flags for safefile() 1N/A** is it a queue directory? 1N/A /* skip over . and .. directories */ 1N/A#
endif /* HASLSTAT */ 1N/A ** For a symlink we need to make sure the 1N/A ** target is a directory 1N/A#
endif /* HASLSTAT */ 1N/A /* Print a warning if unsafe (but still use it) */ 1N/A /* XXX do this only if we want the warning? */ 1N/A "queue directory \"%s\": Not safe: %s",
1N/A#
endif /* _FFR_CHK_QUEUE */ 1N/A** MULTIQUEUE_CACHE -- cache a list of paths to queues. 1N/A** Each potential queue is checked as the cache is built. 1N/A** Thereafter, each is blindly trusted. 1N/A** Note that we can be called again after a timeout to rebuild 1N/A** (although code for that is not ready yet). 1N/A** basedir -- base of all queue directories. 1N/A** blen -- strlen(basedir). 1N/A** qg -- queue group. 1N/A** qn -- number of queue directories already cached. 1N/A** phash -- pointer to hash value over queue dirs. 1N/A** only used if shared memory is active. 1N/A#endif * SM_CONF_SHM * 1N/A** new number of queue directories. 1N/A /* Initialize to current directory */ 1N/A /* If running as root, allow safedirpath() checks to use privs */ 1N/A#
endif /* _FFR_CHK_QUEUE */ 1N/A ** XXX we could add basedir, but then we have to realloc() 1N/A ** the string... Maybe another time. 1N/A /* qpath: directory of current workgroup */ 1N/A /* begin of qpath must be same as basedir */ 1N/A syserr(
"QueuePath %s not subpath of QueueDirectory %s",
1N/A /* Do we have a nested subdirectory? */ 1N/A /* Copy subdirectory into prefix for later use */ 1N/A *
cp =
'\0';
/* cut off trailing / */ 1N/A /* This is guaranteed by the basedir check above */ 1N/A *
cp =
'\0';
/* Overwrite wildcard */ 1N/A syserr(
"QueueDirectory: can not wildcard relative path");
1N/A sm_dprintf(
"multiqueue_cache: \"%s*\": Can not wildcard relative path.\n",
1N/A ** Special case of top level wildcard, like /foo* 1N/A *(
cp++) =
'\0';
/* Replace / with \0 */ 1N/A ** Path relative to basedir, with trailing / 1N/A ** It will be modified below to specify the subdirectories 1N/A ** so they can be opened without chdir(). 1N/A /* It is always basedir: we don't need to store it per group */ 1N/A /* XXX: optimize this! -> one more global? */ 1N/A ** XXX Should probably wrap this whole loop in a timeout 1N/A ** in case some wag decides to NFS mount the queues. 1N/A /* Test path to get warning messages. */ 1N/A /* XXX qg_runasuid and qg_runasgid for specials? */ 1N/A /* Skip . and .. directories */ 1N/A /* Create relative pathname: prefix + local directory */ 1N/A continue;
/* way too long */ 1N/A /* assert(strlen(d->d_name) < MAXPATHLEN - 14) */ 1N/A /* maybe even - 17 (subdirs) */ 1N/A#
endif /* SM_CONF_SHM */ 1N/A /* test path to get warning messages */ 1N/A ** Copy the last component into qpaths and 1N/A#
endif /* SM_CONF_SHM */ 1N/A** FILESYS_FIND -- find entry in FileSys table, or add new one 1N/A** Given the pathname of a directory, determine the file system 1N/A** in which that directory resides, and return a pointer to the 1N/A** entry in the FileSys table that describes the file system. 1N/A** A new entry is added if necessary (and requested). 1N/A** If the directory does not exist, -1 is returned. 1N/A** name -- name of directory (must be persistent!) 1N/A** path -- pathname of directory (name plus maybe "/df") 1N/A** add -- add to structure if not found. 1N/A** >=0: found: index in file system table 1N/A** <0: some error, i.e., 1N/A** FSF_TOO_MANY: too many filesystems (-> syserr()) 1N/A** FSF_STAT_FAIL: can't stat() filesystem (-> syserr()) 1N/A** FSF_NOT_FOUND: not in list 1N/A ** Make sure the file system (FS) name is set: 1N/A ** even though the source code indicates that 1N/A ** FILE_SYS_DEV() is only set below, it could be 1N/A ** set via shared memory, hence we need to perform 1N/A** FILESYS_SETUP -- set up mapping from queue directories to file systems 1N/A** This data structure is used to efficiently check the amount of 1N/A** free space available in a set of queue directories. 1N/A** add -- initialize structure if necessary. 1N/A** <0: some error, i.e., 1N/A** FSF_NOT_FOUND: not in list 1N/A** FSF_STAT_FAIL: can't stat() filesystem (-> syserr()) 1N/A** FSF_TOO_MANY: too many filesystems (-> syserr()) 1N/A** FILESYS_UPDATE -- update amount of free space on all file systems 1N/A** The FileSys table is used to cache the amount of free space 1N/A** available on all queue directory file systems. 1N/A** This function updates the cached information if it has expired. 1N/A** Updates FileSys table. 1N/A ** Only the daemon updates the shared memory, i.e., 1N/A ** if shared memory is available but the pid is not the 1N/A ** one of the daemon, then don't do anything. 1N/A#
endif /* SM_CONF_SHM */ 1N/A "filesys_update failed: %s, fs=%s, avail=%ld, blocksize=%ld",
1N/A** FILESYS_FREE -- check whether there is at least one fs with enough space. 1N/A** fsize -- file size in bytes 1N/A** true iff there is one fs with more than fsize bytes free. 1N/A#
endif /* _FFR_ANY_FREE_FS */ 1N/A** DISK_STATUS -- show amount of free space in queue directories 1N/A** out -- output file pointer. 1N/A** prefix -- string to output in front of each line. 1N/A** INIT_SEM -- initialize semaphore system 1N/A** owner -- is this the owner of semaphores? 1N/Astatic int SemId = -
1;
/* Semaphore Id */ 1N/A#
endif /* SM_CONF_SEM */ 1N/A#
endif /* _FFR_USE_SEM_LOCKING */ 1N/A "func=init_sem, sem_key=%ld, sm_sem_start=%d, error=%s",
1N/A "key=%ld, sm_semsetowner=%d, RunAsUid=%d, RunAsGid=%d",
1N/A#
endif /* SM_CONF_SEM */ 1N/A#
endif /* _FFR_USE_SEM_LOCKING */ 1N/A** STOP_SEM -- stop semaphore system 1N/A** owner -- is this the owner of semaphores? 1N/A#
endif /* SM_CONF_SEM */ 1N/A#
endif /* _FFR_USE_SEM_LOCKING */ 1N/A** space -- update the space available as well. 1N/A** where -- caller (for logging) 1N/A** Modifies available space in filesystem. 1N/A** Changes number of entries in queue directory. 1N/A#
endif /* _FFR_USE_SEM_LOCKING */ 1N/A sm_dprintf(
"func=upd_qs, count=%d, space=%d, where=%s, idx=%d, entries=%d\n",
1N/A /* XXX in theory this needs to be protected with a mutex */ 1N/A#
endif /* _FFR_USE_SEM_LOCKING */ 1N/A#
endif /* _FFR_USE_SEM_LOCKING */ 1N/A /* update available space also? (might be loseqfile) */ 1N/A /* convert size to blocks; this causes rounding errors */ 1N/A /* XXX in theory this needs to be protected with a mutex */ 1N/A** WRITE_KEY_FILE -- record some key into a file. 1N/A** keypath -- file name. 1N/A** key -- key to write. 1N/A** true iff file could be written. 1N/A "ownership change on %s to %d failed: %s",
1N/A#
endif /* HASFCHOWN */ 1N/A** READ_KEY_FILE -- read a key from a file. 1N/A** keypath -- file name. 1N/A** key -- default key. 1N/A** INIT_SHM -- initialize shared memory structure 1N/A** Initialize or attach to shared memory segment. 1N/A** Currently it is not a fatal error if this doesn't work. 1N/A** However, it causes us to have a "fallback" storage location 1N/A** for everything that is supposed to be in the shared memory, 1N/A** which makes the code slightly ugly. 1N/A** qn -- number of queue directories. 1N/A** owner -- owner of shared memory. 1N/A** hash -- identifies data that is stored in shared memory. 1N/A/* if this "key" is specified: select one yourself */ 1N/A /* This allows us to disable shared memory at runtime. */ 1N/A /* back where we started? */ 1N/A /* only sleep if we are at the first key */ 1N/A "key=%ld, sm_shmsetowner=%d, RunAsUid=%d, RunAsGid=%d",
1N/A ** XXX how to check the pid? 1N/A ** Read it from the pid-file? That does 1N/A ** not need to exist. 1N/A ** We could disable shm if we can't confirm 1N/A ** that it is the right one. 1N/A /* initialize values in shared memory */ 1N/A "can't %s shared memory, key=%ld: %s",
1N/A#
endif /* SM_CONF_SHM */ 1N/A** SETUP_QUEUES -- set up all queue groups 1N/A** owner -- owner of shared memory? 1N/A** attaches shared memory. 1N/A#endif * SM_CONF_SHM * 1N/A ** Determine basedir for all queue directories. 1N/A ** All queue directories must be (first level) subdirectories 1N/A ** of the basedir. The basedir is the QueueDir 1N/A ** without wildcards, but with trailing / 1N/A /* Provide space for trailing '/' */ 1N/A syserr(
"QueueDirectory: path too long: %d, max %d",
1N/A syserr(
"QueueDirectory: can not wildcard relative path \"%s\"",
1N/A sm_dprintf(
"setup_queues: \"%s\": Can not wildcard relative path.\n",
1N/A /* cut off wildcard pattern */ 1N/A /* append trailing slash since it is a directory */ 1N/A /* len counts up to the last directory delimiter */ 1N/A "Program mode requires special privileges, e.g., root or TrustedUser.\n");
1N/A#
endif /* SM_CONF_SHM */ 1N/A /* initialize for queue runs */ 1N/A ** Check queue directory permissions. 1N/A ** Can we write to a group writable queue directory? 1N/A syserr(
"can not write to queue directory %s (RunAsGid=%d, required=%d)",
1N/A syserr(
"dangerous permissions=%o on queue directory %s",
1N/A#
else /* _FFR_MSP_PARANOIA */ 1N/A "dangerous permissions=%o on queue directory %s",
1N/A#
endif /* _FFR_MSP_PARANOIA */ 1N/A syserr(
"can not use multiple queues for MSP");
1N/A#
endif /* _FFR_MSP_PARANOIA */ 1N/A /* initial number of queue directories */ 1N/A ** We didn't get the right filesystem data 1N/A ** This may happen if we don't have the right shared memory. 1N/A ** So let's do this without shared memory. 1N/A syserr(
"filesys_setup failed twice, result=%d", i);
1N/A "shared memory does not contain expected data, ignored");
1N/A#
else /* SM_CONF_SHM */ 1N/A#
endif /* SM_CONF_SHM */ 1N/A** CLEANUP_SHM -- do some cleanup work for shared memory etc 1N/A** owner -- owner of shared memory? 1N/A** detaches shared memory. 1N/A#
endif /* SM_CONF_SHM */ 1N/A** CLEANUP_QUEUES -- do some cleanup work for queues 1N/A** SET_DEF_QUEUEVAL -- set default values for a queue group. 1N/A** all -- set all values (true for default group)? 1N/A** sets default values for the queue group. 1N/A#
endif /* _FFR_QUEUE_GROUP_SORTORDER */ 1N/A** MAKEQUEUE -- define a new queue. 1N/A** line -- description of queue. This is in labeled fields. 1N/A** F -- the flags associated with the queue 1N/A** I -- the interval between running the queue 1N/A** J -- the maximum # of jobs in work list 1N/A** [M -- the maximum # of jobs in a queue run] 1N/A** N -- the niceness at which to run 1N/A** P -- the path to the queue 1N/A** S -- the queue sorting order 1N/A** R -- number of parallel queue runners 1N/A** r -- max recipients per envelope 1N/A** The first word is the canonical name of the queue. 1N/A** qdef -- this is a 'Q' definition from .cf 1N/A** enters the queue into the queue table. 1N/A /* allocate a queue and set up defaults */ 1N/A /* collect the queue name */ 1N/A /* set default values, can be overridden below */ 1N/A /* now scan through and assign info from the fields */ 1N/A while (*p !=
'\0' &&
1N/A /* p now points to field code */ 1N/A while (*p !=
'\0' && *p !=
'=' && *p !=
',')
1N/A /* p now points to the field body */ 1N/A /* install the field into the queue struct */ 1N/A case 'P':
/* pathname */ 1N/A case 'F':
/* flags */ 1N/A for (; *p !=
'\0'; p++)
1N/A ** Do we need two intervals here: 1N/A ** One for persistent queue runners, 1N/A ** one for "normal" queue runs? 1N/A case 'I':
/* interval between running the queue */ 1N/A case 'N':
/* run niceness */ 1N/A case 'R':
/* maximum # of runners for the group */ 1N/A /* can't have more runners than allowed total */ 1N/A "Q=%s: R=%d exceeds MaxQueueChildren=%d, set to MaxQueueChildren\n",
1N/A case 'J':
/* maximum # of jobs in work list */ 1N/A case 'r':
/* max recipients per envelope */ 1N/A case 'S':
/* queue sorting order */ 1N/A case 'h':
/* Host first */ 1N/A case 'p':
/* Priority order */ 1N/A case 't':
/* Submission time */ 1N/A case 'f':
/* File name */ 1N/A case 'm':
/* Modification time */ 1N/A case 'r':
/* Random */ 1N/A case 's':
/* Shuffled host name */ 1N/A#
endif /* _FFR_RHS */ 1N/A case 'n':
/* none */ 1N/A syserr(
"Invalid queue sort order \"%s\"", p);
1N/A#
endif /* _FFR_QUEUE_GROUP_SORTORDER */ 1N/A "Q%s: Warning: N= set on system that doesn't support nice()\n",
1N/A#
endif /* !HASNICE */ 1N/A /* do some rationality checking */ 1N/A syserr(
"too many queue groups defined (%d max)",
1N/A syserr(
"QueueDir must be defined before queue groups");
1N/A "Warning: Q=%s: R=%d: multiple queue runners specified\n\tbut flag '%c' is not set\n",
1N/A /* enter the queue into the symbol table */ 1N/A /* XXX what about the pointers inside this struct? */ 1N/A /* set default value for max queue runners */ 1N/A** HASHFQN -- calculate a hash value for a fully qualified host name 1N/A** fqn -- an all lower-case host.domain string 1N/A** buckets -- the number of buckets (queue directories) 1N/A** a bucket number (signed integer) 1N/A ** A variation on the gdb hash 1N/A ** This is the best as of Feb 19, 1996 --bcx 1N/A h = (h + (*p << (
cnt *
5 %
24))) &
0x7FFFFFFF;
1N/A h = (
1103515243 * h +
12345) &
0x7FFFFFFF;
1N/A** A structure for sorting Queue according to maxqrun without 1N/A** screwing up Queue itself. 1N/A /* The sort is highest to lowest, so the comparison is reversed */ 1N/A** MAKEWORKGROUP -- balance queue groups into work groups per MaxQueueChildren 1N/A** Take the now defined queue groups and assign them to work groups. 1N/A** This is done to balance out the number of concurrently active 1N/A** queue runners such that MaxQueueChildren is not exceeded. This may 1N/A** result in more than one queue group per work group. In such a case 1N/A** the number of running queue groups in that work group will have no 1N/A** more than the work group maximum number of runners (a "fair" portion 1N/A** of MaxQueueRunners). All queue groups within a work group will get a 1N/A** chance at running. 1N/A** Sets up WorkGrp structure. 1N/A ** There is only the "mqueue" queue group (a default) 1N/A ** containing all of the queues. We want to provide to 1N/A ** this queue group the maximum allowable queue runners. 1N/A ** To match older behavior (8.10/8.11) we'll try for 1N/A ** 1 runner per queue capping it at MaxQueueChildren. 1N/A ** So if there are N queues, then there will be N runners 1N/A ** for the "mqueue" queue group (where N is kept less than 1N/A ** MaxQueueChildren). 1N/A /* can't have more runners than allowed total */ 1N/A ** We now know the number of work groups to pack the queue groups 1N/A ** into. The queue groups in 'Queue' are sorted from highest 1N/A ** to lowest for the number of runners per queue group. 1N/A ** We put the queue groups with the largest number of runners 1N/A ** into work groups first. Then the smaller ones are fitted in 1N/A ** where it looks best. 1N/A /* a to-and-fro packing scheme, continue from last position */ 1N/A syserr(
"!cannot allocate memory for work queues, need %d bytes",
1N/A /* can't have more runners than allowed total */ 1N/A ** XXX: must wg_lowqintvl be the GCD? 1N/A ** qg1: 2m, qg2: 3m, minimum: 2m, when do queue runs for 1N/A /* keep track of the lowest interval for a persistent runner */ 1N/A** DUP_DF -- duplicate envelope data file 1N/A** Copy the data file from the 'old' envelope to the 'new' envelope 1N/A** in the most efficient way possible. 1N/A** Create a hard link from the 'old' data file to the 'new' data file. 1N/A** If the old and new queue directories are on different file systems, 1N/A** then the new data file link is created in the old queue directory, 1N/A** and the new queue file will contain a 'd' record pointing to the 1N/A** directory containing the new data file. 1N/A** old -- old envelope. 1N/A** new -- new envelope. 1N/A** Returns true on success, false on failure. 1N/A** On success, the new data file is created. 1N/A** On fatal failure, EF_FATALERRS is set in old->e_flags. 1N/A ** this can happen if: SuperSafe != True 1N/A ** and a bounce mail is sent that is split. 1N/A ** Attempt to create a hard link, if we think both old and new 1N/A ** are on the same file system, otherwise copy the file. 1N/A ** Don't waste time attempting a hard link unless old and new 1N/A ** are on the same file system. 1N/A ** Can't link across queue directories, so try to create a hard 1N/A ** link in the same queue directory as the old df file. 1N/A ** The qf file will refer to the new df file using a 'd' record. 1N/A "dup_df: can't link %s to %s, error=%s, envelope splitting failed",
1N/A** SPLIT_ENV -- Allocate a new envelope based on a given envelope. 1N/A** sendqueue -- sendqueue for new envelope. 1N/A** qgrp -- index of queue group. 1N/A** qdir -- queue directory. 1N/A /* failed to dup e->e_xfp, start a new transcript */ 1N/A ** XXX Not sure if this copying is necessary. 1N/A ** sendall() does this copying, but I (dm) don't know if that is 1N/A ** because of the storage management discipline we were using 1N/A ** before rpools were introduced, or if it is because these lists 1N/A ** can be modified later. 1N/A/* return values from split functions, check also below! */ 1N/A** SPLIT_ACROSS_QUEUE_GROUPS 1N/A** This function splits an envelope across multiple queue groups 1N/A** based on the queue group of each recipient. 1N/A** SM_SPLIT_FAIL on failure 1N/A** SM_SPLIT_NONE if no splitting occurred, 1N/A** or 1 + the number of additional envelopes created. 1N/A** On success, e->e_sibling points to a list of zero or more 1N/A** additional envelopes, and the associated data files exist 1N/A** on disk. But the queue files are not created. 1N/A** On failure, e->e_sibling is not changed. 1N/A** The order of recipients in e->e_sendqueue is permuted. 1N/A** Abandoned data files for additional envelopes that failed 1N/A** to be created may exist on disk. 1N/A /* Count addresses and assign queue groups. */ 1N/A /* bad addresses and those already sent stay put */ 1N/A /* call ruleset which should return a queue group */ 1N/A "queue group name %s -> %d",
1N/A "can't find queue group name %s, selection ignored",
1N/A /* only one address? nothing to split. */ 1N/A /* sort the addresses by queue group */ 1N/A /* split into multiple envelopes, by queue group */ 1N/A /* same queue group as original envelope? */ 1N/A /* different queue group than original envelope */ 1N/A /* no splits? return right now. */ 1N/A /* assign a queue directory to each additional envelope */ 1N/A /* sort the additional envelopes by queue file system */ 1N/A /* create data files for each additional envelope */ 1N/A /* copy or link to the previous data file */ 1N/A /* success: prepend the new envelopes to the e->e_sibling list */ 1N/A /* failure: clean up */ 1N/A for (j = 0; j < i; j++)
1N/A** SPLIT_WITHIN_QUEUE 1N/A** Split an envelope with multiple recipients into several 1N/A** envelopes within the same queue directory, if the number of 1N/A** recipients exceeds the limit for the queue group. 1N/A** SM_SPLIT_FAIL on failure 1N/A** SM_SPLIT_NONE if no splitting occurred, 1N/A** or 1 + the number of additional envelopes created. 1N/A /* don't bother if there is no recipient limit */ 1N/A /* count recipients */ 1N/A ** Preserve the recipient list 1N/A ** so that we can restore it in case of error. 1N/A ** (But we discard dead addresses.) 1N/A ** Partition the recipient list so that bad and sent addresses 1N/A ** come first. These will go with the original envelope, and 1N/A ** do not count towards the maxrcpt limit. 1N/A ** addrs[] does not contain QS_IS_DEAD() addresses. 1N/A /* Check if no splitting required. */ 1N/A /* prepare buffer for logging */ 1N/A /* get rid of stupid compiler warnings */ 1N/A /* split the envelope */ 1N/A /* Error. Restore e's sibling & recipient lists. */ 1N/A /* prepend the new envelope to e->e_sibling */ 1N/A /* let's try to get this done */ 1N/A "split: maxrcpts=%d, rcpts=%d, count=%d, id%s=%s",
1N/A** SPLIT_BY_RECIPIENT 1N/A** Split an envelope with multiple recipients into multiple 1N/A** envelopes as required by the sendmail configuration. 1N/A** Returns true on success, false on failure. 1N/A** see split_across_queue_groups(), split_within_queue(e) 1N/A /* get rid of stupid compiler warnings */ 1N/A for (i =
1; i < n; ++i)
1N/A /* let's try to get this done */ 1N/A** QUARANTINE_QUEUE_ITEM -- {un,}quarantine a single envelope 1N/A** qgrp -- queue group for the item 1N/A** qdir -- queue directory in the given queue group 1N/A** e -- envelope information for the item 1N/A** reason -- quarantine reason, NULL means unquarantine. 1N/A** true if item changed, false otherwise 1N/A** Changes quarantine tag in queue file and renames it. 1N/A ** Instead of duplicating all the open 1N/A ** and lock code here, tell readqf() to 1N/A ** do that work and return the open 1N/A ** file pointer in e_lockfp. Note that 1N/A ** we must release the locks properly when 1N/A /* open the new queue file */ 1N/A "Skipping %s: Could not open %s: %s\n",
1N/A "Skipping %s: Could not lock %s\n",
1N/A "Skipping %s: Could not lock %s\n",
1N/A /* Copy the data over, changing the quarantine reason */ 1N/A case 'q':
/* quarantine reason */ 1N/A "%s: Removed quarantine of \"%s\"\n",
1N/A "%s: Already quarantined with \"%s\"\n",
1N/A "%s: Quarantine changed from \"%s\" to \"%s\"\n",
1N/A ** If we are quarantining an unquarantined item, 1N/A ** need to put in a new 'q' line before it's 1N/A "%s: Quarantined with \"%s\"\n",
1N/A /* Copy the line to the new file */ 1N/A /* Copy the line to the new file */ 1N/A /* Make sure we read the whole old file */ 1N/A "Skipping %s: Error reading %s: %s\n",
1N/A "Skipping %s: Incomplete file: %s\n",
1N/A /* Check if we actually changed anything or we can just bail now */ 1N/A /* pretend we failed, even though we technically didn't */ 1N/A /* Make sure we wrote things out safely */ 1N/A "Skipping %s: Error writing %s: %s\n",
1N/A /* Figure out the new filename */ 1N/A /* going to rename tempqf to oldqf */ 1N/A /* going to rename tempqf to new name based on newtype */ 1N/A /* rename tempqf to newqf */ 1N/A /* Check rename() success */ 1N/A "quarantine_queue_item: rename(%s, %s): %s",
1N/A "Error renaming %s to %s: %s\n",
1N/A ** Bail here since we don't know the state of 1N/A ** the filesystem and may need to keep tempqf 1N/A ** for the user to rescue us. 1N/A /* remove new file (if rename() half completed) */ 1N/A "Error removing %s: %s\n",
1N/A /* tempqf removed below */ 1N/A /* If changing file types, need to remove old type */ 1N/A "Error removing %s: %s\n",
1N/A /* see if anything above failed */ 1N/A /* Something failed: remove new file, old file still there */ 1N/A ** fsync() after file operations to make sure metadata is 1N/A ** written to disk on filesystems in which renames are 1N/A ** not guaranteed. It's ok if they fail, mail won't be lost. 1N/A /* for soft-updates */ 1N/A /* for soft-updates */ 1N/A /* for other odd filesystems */ 1N/A** QUARANTINE_QUEUE -- {un,}quarantine matching items in the queue 1N/A** reason, and requeue appropriately. 1N/A** reason -- quarantine reason, "." means unquarantine. 1N/A** qgrplimit -- limit to single queue group unless NOQGRP 1N/A** Lots of changes to the queue. 1N/A /* Convert internal representation of unquarantine */ 1N/A /* first see if there is anything */ 1N/A /* setup envelope */