Subject: collected Debian patches for anacron package
Author: Peter Eisentraut <petere@debian.org>
Forwarded: not-needed
Last-Update: 2025-10-03

The packaging for anacron is maintained in Git.  This makes it complex to
separate the changes into individual patches.  They are therefore all
included in a single Debian patch.

For full commit history and separated commits, see the packaging Git
repository.
--- anacron-2.3.orig/ChangeLog
+++ anacron-2.3/ChangeLog
@@ -1,3 +1,8 @@
+   Changes in Anacron 2.3.1
+   ------------------------
+* documentation no longer suggests adding local directories to the PATH
+
+
    Changes in Anacron 2.3
    ----------------------
 * anacron can now read an arbitrary anacrontab file, use the -t option
--- anacron-2.3.orig/Makefile
+++ anacron-2.3/Makefile
@@ -22,7 +22,7 @@
 PREFIX = 
 BINDIR = $(PREFIX)/usr/sbin
 MANDIR = $(PREFIX)/usr/man
-CFLAGS = -Wall -pedantic -O2
+CFLAGS += -Wall -pedantic
 #CFLAGS = -Wall -O2 -g -DDEBUG
 
 # If you change these, please update the man-pages too
--- anacron-2.3.orig/README
+++ anacron-2.3/README
@@ -5,9 +5,9 @@
    Anacron is a periodic command scheduler.  It executes commands at
 intervals specified in days.  Unlike cron, it does not assume that the
 system is running continuously.  It can therefore be used to control
-the execution of daily, weekly and monthly jobs (or anything with a
-period of n days), on systems that don't run 24 hours a day.  When
-installed and configured properly, Anacron will make sure that the
+the execution of daily, weekly, monthly and yearly jobs (or anything
+with a period of n days), on systems that don't run 24 hours a day.
+When installed and configured properly, Anacron will make sure that the
 commands are run at the specified intervals as closely as
 machine-uptime permits.
 
@@ -40,7 +40,8 @@ scripts are scheduled to run on Sundays.
 off for the night or for the weekend, these scripts rarely get run.
 
    Anacron solves this problem.  These jobs can simply be scheduled as
-Anacron-jobs with periods of 1, 7 and 30 days.
+Anacron-jobs with periods of 1, 7 and special target period names:
+@monthly and @yearly.
 
 
      What Anacron is not ?
@@ -90,7 +91,7 @@ scripts, from cron-jobs, or explicitly.
      Setup
      -----
 
-1. Locate your system's daily, weekly and monthly cron-jobs.
+1. Locate your system's daily, weekly, monthly and yearly cron-jobs.
    See your cron documentation for more details.
 
 2. Decide which of these jobs should be controlled by Anacron.
@@ -109,11 +110,12 @@ scripts, from cron-jobs, or explicitly.
 -----Cut
 # /etc/anacrontab example
 SHELL=/bin/sh
-PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+PATH=/sbin:/bin:/usr/sbin:/usr/bin
 # format: period delay job-identifier command
 1       5       cron.daily      run-parts /etc/cron.daily
 7       10      cron.weekly     run-parts /etc/cron.weekly
-30      15      cron.monthly    run-parts /etc/cron.monthly
+@monthly      15      cron.monthly    run-parts /etc/cron.monthly
+@yearly       20      cron.yearly     run-parts /etc/cron.yearly
 -----Cut
 
 5. Put the command "anacron -s" somewhere in your boot-scripts.
@@ -125,9 +127,9 @@ PATH=/usr/local/sbin:/usr/local/bin:/sbi
 
 That's it.
 
-It is a good idea to check what your daily, weekly and monthly scripts
-actually do, and disable any parts that may be irrelevant for your
-system.
+It is a good idea to check what your daily, weekly, monthly and yearly
+scripts actually do, and disable any parts that may be irrelevant for
+your system.
 
 
      Credits
--- anacron-2.3.orig/TODO
+++ anacron-2.3/TODO
@@ -1,6 +1,3 @@
-anacron runs jobs twice in a 31 day month
-add hostname to emails sent to admin
-allow user anacrontabs
 make manpages match #defines automagically --> sed fu
 full ANSI / POSIX compliance
 code cleaning
--- anacron-2.3.orig/anacron.8
+++ anacron-2.3/anacron.8
@@ -1,147 +1,221 @@
-.TH ANACRON 8 2000-06-22 "Sean 'Shaleh' Perry" "Anacron Users' Manual"
+.TH ANACRON 8 2025-12-17 "The Debian Project" "Anacron Users' Manual"
 .SH NAME
 anacron \- runs commands periodically
 .SH SYNOPSIS
-.B anacron \fR[\fB-s\fR] [\fB-f\fR] [\fB-n\fR] [\fB-d\fR] [\fB-q\fR]
-[\fB-t anacrontab\fR] [\fIjob\fR] ...
+.B anacron \fR[\fB\-s\fR] [\fB\-f\fR] [\fB\-n\fR] [\fB\-d\fR] [\fB\-q\fR]
+[\fB\-v\fR] [\fB\-Q\fR] [\fB\-t anacrontab\fR] [\fB\-S spooldir\fR] [\fIjob\fR] ...
 .br
-.B anacron -u [\fB-t anacrontab\fR] \fR[\fIjob\fR] ...
+.B anacron [\fB\-S spooldir\fR] \-u [\fB\-t anacrontab\fR] \fR[\fIjob\fR] ...
 .br
-.B anacron \fR[\fB-V\fR|\fB-h\fR]
+.B anacron \fR[\fB\-V\fR|\fB\-h\fR]
+.br
+.B anacron \-T [\fB\-t anacrontab\fR]
+.br
+.B anacron \-H file
+.br
+.B anacron \-\-list\-obsolete\-timestamp\-files [\fB\-t anacrontab\fR]  [\fB\-S spooldir\fR]
 .SH DESCRIPTION
-Anacron
-can be used to execute commands periodically, with a
-frequency specified in days.  Unlike \fBcron(8)\fR,
-it does not assume that the machine is running continuously.  Hence,
-it can be used on machines that aren't running 24 hours a day,
-to control daily, weekly, and monthly jobs that are
-usually controlled by \fBcron\fR.
+Anacron can be used to execute commands periodically, with a frequency
+specified in days.\&
+Unlike \fBcron\fR(8), it does not assume that the machine is running
+continuously.\&
+Hence, it can be used on machines that aren't running 24 hours a day,
+to control daily, weekly, and monthly jobs that are usually controlled by
+\fBcron\fR.\&
 .PP
 When executed, Anacron reads a list of jobs from a configuration file, normally
 .I /etc/anacrontab
-(see \fBanacrontab(5)\fR).  This file
-contains the list of jobs that Anacron controls.  Each
-job entry specifies a period in days, 
-a delay in minutes, a unique
-job identifier, and a shell command.
-.PP
-For each job, Anacron checks whether
-this job has been executed in the last n days, where n is the period specified
-for that job.  If not, Anacron runs the job's shell command, after waiting
-for the number of minutes specified as the delay parameter.
-.PP
-After the command exits, Anacron records the date in a special
-timestamp file for that job, so it can know when to execute it again.  Only
-the date is used for the time
-calculations.  The hour is not used.
-.PP
-When there are no more jobs to be run, Anacron exits.
-.PP
-Anacron only considers jobs whose identifier, as
-specified in the \fIanacrontab\fR matches any of
-the
+(see \fBanacrontab\fR(5)).\&
+This file contains the list of jobs that Anacron controls.\&
+Each job entry specifies a period in days, a delay in minutes, a unique job
+identifier, and a shell command.\&
+.PP
+For each job, Anacron checks whether this job has been executed in the last
+n days, where n is the period specified for that job.\&
+If not, Anacron runs the job's shell command, after waiting for the number of
+minutes specified as the delay parameter.\&
+.PP
+After the command exits, Anacron records the date in a special timestamp file
+for that job, so it can know when to execute it again.\&
+Only the date is used for the time calculations.\&
+The hour is not used.\&
+.PP
+When there are no more jobs to be run, Anacron exits.\&
+.PP
+Anacron only considers jobs whose identifier, as specified in the
+\fIanacrontab\fR matches any of the
 .I job
-command-line arguments.  The
+command-line arguments.\&
+The
 .I job
-arguments can be shell wildcard patterns (be sure to protect them from
-your shell with adequate quoting).  Specifying no
+arguments can be shell wildcard patterns (be sure to protect them from your
+shell with adequate quoting).\&
+Specifying no
 .I job
-arguments, is equivalent to specifying "*"  (That is, all jobs will be
-considered).
+arguments, is equivalent to specifying "*".\&
+(That is, all jobs will be considered).\&
+.PP
+Unless the \fB\-d\fR option is given (see below), Anacron forks to the
+background when it starts, and the parent process exits immediately.\&
 .PP
-Unless the \fB-d\fR option is given (see below), Anacron forks to the
-background when it starts, and the parent process exits
-immediately.
-.PP
-Unless the \fB-s\fR or \fB-n\fR options are given, Anacron starts jobs
-immediately when their delay is over.  The execution of different jobs is
-completely independent.
-.PP
-If a job generates any output on its standard output or standard error,
-the output is mailed to the user running Anacron (usually root).
-.PP
-Informative messages about what Anacron is doing are sent to \fBsyslogd(8)\fR
-under facility \fBcron\fR, priority \fBnotice\fR.  Error messages are sent at
-priority \fBerror\fR.
-.PP
-"Active" jobs (i.e. jobs that Anacron already decided
-to run and now wait for their delay to pass, and jobs that are currently
-being executed by
-Anacron), are "locked", so that other copies of Anacron won't run them
-at the same time.
+Unless the \fB\-s\fR or \fB\-n\fR options are given, Anacron starts jobs
+immediately when their delay is over.\&
+The execution of different jobs is completely independent.\&
+.PP
+If a job generates any output on its standard output or standard error, the
+output is mailed to the user running Anacron (usually root), or to the address
+contained by the MAILTO environment variable in the /etc/anacrontab file, if
+such exists.\&
+.PP
+Informative messages about what Anacron is doing are sent to \fBsyslogd\fR(8)
+under facility \fBcron\fR, priority \fBnotice\fR.\&
+Error messages are sent at priority \fBerror\fR.\&
+.PP
+"Active" jobs (i.e.\& jobs that Anacron already decided to run and now wait for
+their delay to pass, and jobs that are currently being executed by Anacron),
+are "locked", so that other copies of Anacron won't run them at the same
+time.\&
 .SH OPTIONS
 .TP
-.B -f
-Force execution of the jobs, ignoring the timestamps.
+.B \-f
+Force execution of the jobs, ignoring the timestamps.\&
 .TP
-.B -u
+.B \-u
 Only update the timestamps of the jobs, to the current date, but
-don't run anything.
-.TP
-.B -s
-Serialize execution of jobs.  Anacron will not start a new job before the
-previous one finished.
+don't run anything.\&
 .TP
-.B -n
-Run jobs now.  Ignore the delay specifications in the
+.B \-s
+Serialize execution of jobs.\&
+Anacron will not start a new job before the previous one finished.\&
+.TP
+.B \-n
+Run jobs now.\&
+Ignore the delay specifications in the
 .I /etc/anacrontab
-file.  This options implies \fB-s\fR.
+file.\&
+This options implies \fB\-s\fR.\&
 .TP
-.B -d
-Don't fork to the background.  In this mode, Anacron will output informational
-messages to standard error, as well as to syslog.  The output of jobs
-is mailed as usual.
+.B \-d
+Don't fork to the background.\&
+In this mode, Anacron will output informational messages to standard error, as
+well as to syslog.\&
+The output of jobs is mailed as usual.\&
+.TP
+.B \-q
+Suppress messages to standard error.\&
+Only applicable with \fB\-d\fR.\&
+.TP
+.B \-v
+Enable verbose logging for debugging.\&
+.TP
+.B \-Q
+Enable quiet syslog.\&
+.TP
+.B \-t anacrontab
+Use specified anacrontab, rather than the default.\&
+.TP
+.B \-T
+Anacrontab testing.\&
+The configuration file will be tested for validity.\&
+If there is an error in the file, an error will be shown and anacron will
+return 1.\&
+Valid anacrontabs will return 0.\&
+.TP
+.B \-S spooldir
+Use the specified spooldir to store timestamps in.\&
+This option is required for users who wish to run anacron themselves.\&
+.TP
+.B \-l, \-\-list\-obsolete\-timestamp\-files
+Print obsolete timestamp files relative to anacrontab and spooldir.\&
 .TP
-.B -q
-Suppress messages to standard error.  Only applicable with \fB-d\fR.
+.B \-H
+Print Base64 encoded hash of file for job in anacrontab, and exit.\&
 .TP
-.B -t anacrontab
-Use specified anacrontab, rather than the default
+.B \-V
+Print version information, and exit.\&
 .TP
-.B -V
-Print version information, and exit.
-.TP
-.B -h
-Print short usage message, and exit.
+.B \-h
+Print short usage message, and exit.\&
 .SH SIGNALS
-After receiving a \fBSIGUSR1\fR signal, Anacron waits for running
-jobs, if any, to finish and then exits.  This can be used to stop
-Anacron cleanly.
+After receiving a \fBSIGUSR1\fR signal, Anacron waits for running jobs, if any,
+to finish and then exits.\&
+This can be used to stop Anacron cleanly.\&
 .SH NOTES
-Make sure that the time-zone is set correctly before Anacron is
-started.  (The time-zone affects the date).  This is usually accomplished
-by setting the TZ environment variable, or by installing a
+Make sure that the time-zone is set correctly before Anacron is started.\&
+(The time-zone affects the date).\&
+This is usually accomplished by setting the \fBTZ\fR environment variable, or
+by installing a
 .I /usr/lib/zoneinfo/localtime
-file.  See
-.B tzset(3)
-for more information.
+file.\&
+See \fBtzset\fR(3) for more information.\&
+.PP
+Timestamp files are created in the spool directory for each job in
+anacrontab.\&
+These are never removed automatically by anacron, and should be removed by
+hand if a job is no longer being scheduled.\&
+.SH DEBIAN-SPECIFIC CONFIGURATION
+On Debian-based systems, anacron will be activated hourly every day from 07:30
+local time to 23:30 local time through cron job (on non-systemd systems where
+cron is installed and enabled) or systemd timer (on systemd-based systems).\&
+On activation, anacron will check if it missed some jobs.\&
+If yes, it will start those jobs after a short period of time.\&
+.PP
+By default, the hourly activation of anacron will not take place when the
+system is using battery and no AC power is connected to the computer.\&
+It is meant to reduce power usage and extend battery life, but such design
+might lead to unwanted results.\&
+Users may disable this feature and let anacron run regardless of power
+supply.\&
+.PP
+Please read Debian-specific documentation in
+.I /usr/share/doc/anacron/README.Debian
+file for detailed instruction in how to change such behaviour.\&
 .SH FILES
 .TP
 .I /etc/anacrontab
-Contains specifications of jobs.  See \fBanacrontab(5)\fR for a complete
-description.
+Contains specifications of jobs.
+See \fBanacrontab\fR(5) for a complete description.\&
 .TP
 .I /var/spool/anacron
-This directory is used by Anacron for storing timestamp files.
+This directory is used by Anacron for storing timestamp files.\&
+.TP
+.I /lib/systemd/system/anacron.service
+This file provides systemd service for anacron.\&
+.TP
+.I /lib/systemd/system/anacron.timer
+This file provides systemd timer for anacron.
+Currently the service is triggered hourly through systemd timer.\&
 .SH "SEE ALSO"
-.B anacrontab(5), cron(8), tzset(3)
+\fBanacrontab\fR(5), \fBcron\fR(8), \fBtzset\fR(3)
 .PP
 The Anacron
 .I README
-file.
+file.\&
+.PP
+For Debian-specific modifications, please read
+.I /usr/share/doc/anacron/README.Debian
+file for detailed information.\&
 .SH BUGS
-Anacron never removes timestamp files.  Remove unused files manually.
+Anacron never removes timestamp files. Remove unused files manually.\&
 .PP
-Anacron
-uses up to two file descriptors for each active job.  It may run out of
-descriptors if there are more than about 125 active jobs (on normal kernels).
+Anacron uses up to two file descriptors for each active job.\&
+It may run out of descriptors if there are more than about 125 active jobs
+(on normal kernels).\&
 .PP
-Mail comments, suggestions and bug reports to Sean 'Shaleh' Perry <shaleh@(debian.org|valinux.com)>.
+Mail comments, suggestions and bug reports to Debian's BTS for Anacron by
+emailing \%submit@bugs.debian.org>.\&
 .SH AUTHOR
 Anacron was originally conceived and implemented by Christian Schwarz
-<schwarz@monet.m.isar.de>.
-.PP
-The current implementation is a complete rewrite by Itai Tzur
-<itzur@actcom.co.il>.
+\%<schwarz@monet.m.isar.de>.\&
+The current implementation is a complete rewrite by
+Itai Tzur \%<itzur@actcom.co.il>.\&
+.PP
+The code base was maintained by Sean \&'Shaleh'\& Perry
+\%<shaleh@(debian.org|valinux.com)>.\&
+During 2004\(en2006, it was maintained by Pascal Hakim
+\%<pasc@(debian.org|redellipse.net)>.\&
+During 2009\(en2014, it was maintained by Peter Eisentraut
+\%<petere@debian.org>.\&
 .PP
-The code base is currently maintained by Sean 'Shaleh' Perry <shaleh@(debian.org|valinux.com)>.
+Nowadays anacron in Debian is co-maintained by various developers from Debian
+Project.\&
--- /dev/null
+++ anacron-2.3/anacron.apm
@@ -0,0 +1,19 @@
+#! /bin/sh
+
+# This script makes anacron jobs start to run when the machine is
+# plugged into AC power, or woken up.  For a laptop, these are the 
+# closest parallels to turning on a desktop.
+
+# The /etc/init.d/anacron script now normally tries to avoid running
+# anacron unless on AC power, so as to avoid running down the battery.
+# (Things like the slocate updatedb cause a lot of IO.)  Rather than
+# trying to second-guess which events reflect having or not having
+# power, we just try to run anacron every time and let it abort if
+# there's no AC.  You'll see a message on the cron syslog facility 
+# (typically /var/log/cron) if it does run.
+
+case "$1,$2" in
+change,power|resume,*)
+    /etc/init.d/anacron start >/dev/null
+    ;;
+esac
--- anacron-2.3.orig/anacrontab.5
+++ anacron-2.3/anacrontab.5
@@ -1,28 +1,53 @@
-.TH ANACRONTAB 5 1998-02-02 "Itai Tzur" "Anacron Users' Manual"
+.TH ANACRONTAB 5 2025-12-17 "The Debian Project" "Anacron Users' Manual"
 .SH NAME
 /etc/anacrontab \- configuration file for anacron
 .SH DESCRIPTION
 The file
 .I /etc/anacrontab
-describes the jobs controlled by \fBanacron(8)\fR.  Its lines can be of
-three kinds:  job-description lines, environment
-assignments, or empty lines.
+describes the jobs controlled by \fBanacron\fR(8).\&
+Its lines can be of three kinds:  job-description lines, environment
+assignments, or empty lines.\&
 .PP
-Job-description lines are of the form:
+Job-description lines are of one of these four forms:
 .PP
    period  delay  job-identifier  command
 .PP
+   @period_name delay job-identify command
+.PP
+   period  delay  job-identify/hash  command
+.PP
+   @period_name delay job-identify/hash command
+.PP
 The
 .I period
 is specified in days, the
 .I delay
-in minutes.  The
+in minutes.\&
+The
+.I job-identifier
+can contain any non-blank character, except slashes.\&
+It is used to identify the job in Anacron messages, and as the name for the
+job's timestamp file.\&
+The
 .I job-identifier
-can contain any non-blank character, except slashes.  It is used to identify
-the job in Anacron messages,
-and as the name for the job's timestamp file.  The
+can be followed by a forward slash (\fB/\fR) and a hash.\&
+The
+.I hash
+verifies that a file in the command has not changed before running a job.\&
+The
 .I command
-can be any shell command.
+can be any shell command.\&
+The fields can be separated by blank spaces or tabs.\&
+.I period_name
+can be set to
+.B daily,
+.B weekly,
+.B monthly,
+or
+.BR yearly .\&
+This will ensure jobs are only run once a day, once every seven days, and
+once a month\[em]no matter the number of days in this month or the
+previous month; or analogously for the year.\&
 .PP
 Environment assignment lines are of the form:
 .PP
@@ -30,19 +55,24 @@ Environment assignment lines are of the
 .PP
 Spaces around
 .I VAR
-are removed.  No spaces around
+are removed.\&
+No spaces around
 .I VALUE
-are allowed (unless you want them to be part of the value).  The assignment
-takes effect from the next line to the end of the file, or to the next
-assignment of the same variable.
+are allowed \%(unless you want them to be part of the value).\&
+The assignment takes effect from the next line to the end of the file, or to
+the next assignment of the same variable.\&
 .PP
-Empty lines are either blank lines, line containing white-space only, or
-lines with white-space followed by a '#' followed by an arbitrary comment.
+Empty lines are either blank lines, line containing white-space only, or lines
+with white-space followed by a \&'#'\& followed by an arbitrary comment.\&
+.PP
+You can continue a line onto the next line by ending it with a back slash (\fB\e\fR).\&
 .SH "SEE ALSO"
-.B anacron(8)
+\fBanacron\fR(8)
 .PP
 The Anacron
 .I README
-file.
+file.\&
 .SH AUTHOR
-Itai Tzur <itzur@actcom.co.il>
+Itai Tzur \%<itzur@actcom.co.il>
+.PP
+Currently maintained by Lance Lin \%<lq27267@gmail.com>.\&
--- /dev/null
+++ anacron-2.3/base64.c
@@ -0,0 +1,292 @@
+/* modified from glib source code */
+/*
+ * Copyright (c) 1996-1999 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/*
+ * Portions Copyright (c) 1995 by International Business Machines, Inc.
+ *
+ * International Business Machines, Inc. (hereinafter called IBM) grants
+ * permission under its copyrights to use, copy, modify, and distribute this
+ * Software with or without fee, provided that the above copyright notice and
+ * all paragraphs of this notice appear in all copies, and that the name of IBM
+ * not be used in connection with the marketing of any product incorporating
+ * the Software or modifications thereof, without specific, written prior
+ * permission.
+ *
+ * To the extent it has a right to do so, IBM grants an immunity from suit
+ * under its patents, if any, for the use, sale or manufacture of products to
+ * the extent that such products are used for performing Domain Name System
+ * dynamic updates in TCP/IP networks by means of the Software.  No immunity is
+ * granted for any product per se or for any other function of any product.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE.  IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL,
+ * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN
+ * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+static const char Base64[] =
+	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+static const char Pad64 = '=';
+
+/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt)
+   The following encoding technique is taken from RFC 1521 by Borenstein
+   and Freed.  It is reproduced here in a slightly edited form for
+   convenience.
+
+   A 65-character subset of US-ASCII is used, enabling 6 bits to be
+   represented per printable character. (The extra 65th character, "=",
+   is used to signify a special processing function.)
+
+   The encoding process represents 24-bit groups of input bits as output
+   strings of 4 encoded characters. Proceeding from left to right, a
+   24-bit input group is formed by concatenating 3 8-bit input groups.
+   These 24 bits are then treated as 4 concatenated 6-bit groups, each
+   of which is translated into a single digit in the base64 alphabet.
+
+   Each 6-bit group is used as an index into an array of 64 printable
+   characters. The character referenced by the index is placed in the
+   output string.
+
+                         Table 1: The Base64 Alphabet
+
+      Value Encoding  Value Encoding  Value Encoding  Value Encoding
+          0 A            17 R            34 i            51 z
+          1 B            18 S            35 j            52 0
+          2 C            19 T            36 k            53 1
+          3 D            20 U            37 l            54 2
+          4 E            21 V            38 m            55 3
+          5 F            22 W            39 n            56 4
+          6 G            23 X            40 o            57 5
+          7 H            24 Y            41 p            58 6
+          8 I            25 Z            42 q            59 7
+          9 J            26 a            43 r            60 8
+         10 K            27 b            44 s            61 9
+         11 L            28 c            45 t            62 +
+         12 M            29 d            46 u            63 /
+         13 N            30 e            47 v
+         14 O            31 f            48 w         (pad) =
+         15 P            32 g            49 x
+         16 Q            33 h            50 y
+
+   Special processing is performed if fewer than 24 bits are available
+   at the end of the data being encoded.  A full encoding quantum is
+   always completed at the end of a quantity.  When fewer than 24 input
+   bits are available in an input group, zero bits are added (on the
+   right) to form an integral number of 6-bit groups.  Padding at the
+   end of the data is performed using the '=' character.
+
+   Since all base64 input is an integral number of octets, only the
+         -------------------------------------------------
+   following cases can arise:
+
+       (1) the final quantum of encoding input is an integral
+           multiple of 24 bits; here, the final unit of encoded
+	   output will be an integral multiple of 4 characters
+	   with no "=" padding,
+       (2) the final quantum of encoding input is exactly 8 bits;
+           here, the final unit of encoded output will be two
+	   characters followed by two "=" padding characters, or
+       (3) the final quantum of encoding input is exactly 16 bits;
+           here, the final unit of encoded output will be three
+	   characters followed by one "=" padding character.
+   */
+
+int
+b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize) {
+	size_t datalength = 0;
+	u_char input[3];
+	u_char output[4];
+	size_t i;
+
+	while (2 < srclength) {
+		input[0] = *src++;
+		input[1] = *src++;
+		input[2] = *src++;
+		srclength -= 3;
+
+		output[0] = input[0] >> 2;
+		output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
+		output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
+		output[3] = input[2] & 0x3f;
+
+		if (datalength + 4 > targsize)
+			return (-1);
+		target[datalength++] = Base64[output[0]];
+		target[datalength++] = Base64[output[1]];
+		target[datalength++] = Base64[output[2]];
+		target[datalength++] = Base64[output[3]];
+	}
+
+	/* Now we worry about padding. */
+	if (0 != srclength) {
+		/* Get what's left. */
+		input[0] = input[1] = input[2] = '\0';
+		for (i = 0; i < srclength; i++)
+			input[i] = *src++;
+
+		output[0] = input[0] >> 2;
+		output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
+		output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
+
+		if (datalength + 4 > targsize)
+			return (-1);
+		target[datalength++] = Base64[output[0]];
+		target[datalength++] = Base64[output[1]];
+		if (srclength == 1)
+			target[datalength++] = Pad64;
+		else
+			target[datalength++] = Base64[output[2]];
+		target[datalength++] = Pad64;
+	}
+	if (datalength >= targsize)
+		return (-1);
+	target[datalength] = '\0';	/* Returned value doesn't count \0. */
+	return (datalength);
+}
+
+/* skips all whitespace anywhere.
+   converts characters, four at a time, starting at (or after)
+   src from base - 64 numbers into three 8 bit bytes in the target area.
+   it returns the number of data bytes stored at the target, or -1 on error.
+ */
+
+int
+b64_pton (char const *src, u_char *target, size_t targsize)
+{
+	int tarindex, state, ch;
+	char *pos;
+
+	state = 0;
+	tarindex = 0;
+
+	while ((ch = *src++) != '\0') {
+		if (isspace(ch))	/* Skip whitespace anywhere. */
+			continue;
+
+		if (ch == Pad64)
+			break;
+
+		pos = strchr(Base64, ch);
+		if (pos == 0) 		/* A non-base64 character. */
+			return (-1);
+
+		switch (state) {
+		case 0:
+			if (target) {
+				if ((size_t)tarindex >= targsize)
+					return (-1);
+				target[tarindex] = (pos - Base64) << 2;
+			}
+			state = 1;
+			break;
+		case 1:
+			if (target) {
+				if ((size_t)tarindex + 1 >= targsize)
+					return (-1);
+				target[tarindex]   |=  (pos - Base64) >> 4;
+				target[tarindex+1]  = ((pos - Base64) & 0x0f)
+							<< 4 ;
+			}
+			tarindex++;
+			state = 2;
+			break;
+		case 2:
+			if (target) {
+				if ((size_t)tarindex + 1 >= targsize)
+					return (-1);
+				target[tarindex]   |=  (pos - Base64) >> 2;
+				target[tarindex+1]  = ((pos - Base64) & 0x03)
+							<< 6;
+			}
+			tarindex++;
+			state = 3;
+			break;
+		case 3:
+			if (target) {
+				if ((size_t)tarindex >= targsize)
+					return (-1);
+				target[tarindex] |= (pos - Base64);
+			}
+			tarindex++;
+			state = 0;
+			break;
+		default:
+			return (-1);
+		}
+	}
+
+	/*
+	 * We are done decoding Base-64 chars.  Let's see if we ended
+	 * on a byte boundary, and/or with erroneous trailing characters.
+	 */
+
+	if (ch == Pad64) {		/* We got a pad char. */
+		ch = *src++;		/* Skip it, get next. */
+		switch (state) {
+		case 0:		/* Invalid = in first position */
+		case 1:		/* Invalid = in second position */
+			return (-1);
+
+		case 2:		/* Valid, means one byte of info */
+			/* Skip any number of spaces. */
+			for ((void)NULL; ch != '\0'; ch = *src++)
+				if (!isspace(ch))
+					break;
+			/* Make sure there is another trailing = sign. */
+			if (ch != Pad64)
+				return (-1);
+			ch = *src++;		/* Skip the = */
+			/* Fall through to "single trailing =" case. */
+			/* FALLTHROUGH */
+
+		case 3:		/* Valid, means two bytes of info */
+			/*
+			 * We know this char is an =.  Is there anything but
+			 * whitespace after it?
+			 */
+			for ((void)NULL; ch != '\0'; ch = *src++)
+				if (!isspace(ch))
+					return (-1);
+
+			/*
+			 * Now make sure for cases 2 and 3 that the "extra"
+			 * bits that slopped past the last full byte were
+			 * zeros.  If we don't check them, they become a
+			 * subliminal channel.
+			 */
+			if (target && target[tarindex] != 0)
+				return (-1);
+		}
+	} else {
+		/*
+		 * We ended by seeing the end of the string.  Make sure we
+		 * have no partial bytes lying around.
+		 */
+		if (state != 0)
+			return (-1);
+	}
+
+	return (tarindex);
+}
--- /dev/null
+++ anacron-2.3/base64.h
@@ -0,0 +1,10 @@
+#ifndef _ANACRON_BASE64_H
+#define _ANACRON_BASE64_H
+
+int
+b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize);
+
+int
+b64_pton (char const *src, u_char *target, size_t targsize);
+
+#endif
--- anacron-2.3.orig/global.h
+++ anacron-2.3/global.h
@@ -2,6 +2,7 @@
     Anacron - run commands periodically
     Copyright (C) 1998  Itai Tzur <itzur@actcom.co.il>
     Copyright (C) 1999  Sean 'Shaleh' Perry <shaleh@debian.org>
+    Copyright (C) 2004  Pascal Hakim <pasc@redellipse.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -42,6 +43,47 @@
 
 #include <signal.h>
 
+#include <syslog.h>
+#include <time.h>
+#include "sha256.h"
+#include "base64.h"
+
+#ifndef PATH_MAX
+  #define PATH_MAX 4096
+#endif
+
+#define DEFAULT_SIZE 4096
+
+#define MIN_HASH_INFO (HASH_SIZE + sizeof(uint64_t) + 1)
+#define BASE64_HASH_SIZE (((HASH_SIZE / 3) + 1) * 4 + 1)
+
+#define INIT_H \
+	( LOG_MASK(LOG_EMERG) | LOG_MASK(LOG_ALERT) | LOG_MASK(LOG_CRIT) )
+
+#define DEFAULT_CTX_T \
+	{ __sha256_init_ctx, __sha256_process_bytes, __sha256_finish_ctx }
+
+typedef struct {
+	void (*init) (struct sha256_ctx *ctx);
+	void (*process) (const void *buffer,
+			 size_t len,
+			 struct sha256_ctx *ctx);
+	void *(*finish) (struct sha256_ctx *ctx, void *resbuf);
+	struct sha256_ctx ctx;
+} sha256_ctx_t;
+
+#define NEXT_CTX(p) ((void*)p->next)
+
+typedef int * pint;
+
+typedef struct { pint N; pint *H; } context;
+inline static int xinit(void *co, pint n) { \
+	context *c = co; if(*c->H == NULL) { *c->H = n; return 0; } \
+	int p = **c->H; **c->H = INIT_H; if(n != NULL) c->N = n; return p; }
+#define xreset(c, p, n) { if (c) { (*((context *)c)->N) += n ; \
+	(*(*((context *)(c))->H)) = (p == 0) ? INIT_H : p; } }
+#define LOG_MASK_INIT(o) xinit(plm, o);
+
 /* Some declarations */
 
 struct env_rec1 {
@@ -53,9 +95,15 @@ typedef struct env_rec1 env_rec;
 
 struct job_rec1 {
    int period;
+   int named_period;
    int delay;
    char *ident;
    char *command;
+   char *mailto;
+   char *temp_file_path;
+   char *path_to_hash;
+   unsigned char *hash;
+   off_t hash_file_size;
 
    int tab_line;
    int arg_num;
@@ -75,10 +123,12 @@ typedef struct job_rec1 job_rec;
 extern pid_t primary_pid;
 extern char *program_name;
 extern char *anacrontab;
+extern char *spooldir;
 extern int old_umask;
 extern sigset_t old_sigmask;
-extern int serialize,force,update_only,now,no_daemon,quiet;
+extern int serialize,force,update_only,now,no_daemon,quiet,testing_only,list_only;
 extern int day_now;
+extern time_t start_sec;
 extern int year,month,day_of_month;
 extern int in_background;
 
@@ -93,6 +143,9 @@ extern job_rec **job_array;
 
 extern int running_jobs,running_mailers;
 
+extern int complaints;
+
+extern char *mail_charset;
 
 /* Function prototypes */
 
@@ -100,6 +153,7 @@ extern int running_jobs,running_mailers;
 int xopen(int fd, const char *file_name, int flags);
 void xclose(int fd);
 pid_t xfork();
+int open_locked(const char *path, int flags, int *locked_by);
 
 /* log.c */
 void explain(const char *fmt, ...);
@@ -111,26 +165,26 @@ void die_e(const char *fmt, ...);
 void xdebug(const char *fmt, ...);
 void xdebug_e(const char *fmt, ...);
 void xcloselog();
-
-#ifdef DEBUG
-#define Debug(args) xdebug args
-#define Debug_e(args) xdebug_e args
-#else /* not DEBUG */
-#define Debug(args) (void)(0)
-#define Debug_e(args) (void)(0)
-#endif /* not DEBUG */
+extern int log_mask;
 
 /* readtab.c */
-void read_tab();
+void read_tab(int cwd);
 void arrange_jobs();
+int check_exists(const job_rec *jr);
+void list_obsolete_jobs();
 
 /* lock.c */
+int lock_file(int fd, short type, pid_t *p);
+int unlock_file(int fd);
 int consider_job(job_rec *jr);
 void unlock(job_rec *jr);
 void update_timestamp(job_rec *jr);
 void fake_job(job_rec *jr);
 
 /* runjob.c */
+int print_temp_name(char *str, size_t size);
+int init_temp_file(char *name, size_t size);
+int sha256_hash(int fd, size_t len, void *resbuf);
 void tend_children();
 void launch_job(job_rec *jr);
 
--- anacron-2.3.orig/gregor.c
+++ anacron-2.3/gregor.c
@@ -2,6 +2,7 @@
     Anacron - run commands periodically
     Copyright (C) 1998  Itai Tzur <itzur@actcom.co.il>
     Copyright (C) 1999  Sean 'Shaleh' Perry <shaleh@debian.org>
+    Copyright (C) 2004  Pascal Hakim <pasc@redellipse.net>
  
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -23,6 +24,7 @@
 
 
 #include <limits.h>
+#include <time.h>
 #include "gregor.h"
 
 const static int
@@ -65,7 +67,7 @@ day_num(int year, int month, int day)
 {
     int dn;
     int i;
-    const int isleap; /* save three calls to leap() */
+    int isleap; /* save three calls to leap() */
 
     /* Some validity checks */
 
@@ -114,3 +116,66 @@ leap(int year)
     /* but not by 400 */
     return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
 }
+
+int
+days_last_month (void)
+/* How many days did last month have? */
+{
+    struct tm time_record;
+    time_t current_time;
+    time (&current_time);
+    localtime_r (&current_time, &time_record);
+
+    switch (time_record.tm_mon) {
+	case 0: return days_in_month[11];
+	case 2: return days_in_month[1] + (leap (time_record.tm_year + 1900) ? 1 : 0);
+	default: return days_in_month[time_record.tm_mon - 1];
+    }
+}
+
+int
+days_this_month (void)
+/* How many days does this month have? */
+{
+    struct tm time_record;
+    time_t current_time;
+    time (&current_time);
+    localtime_r (&current_time, &time_record);
+
+    switch (time_record.tm_mon) {
+	case 1: return days_in_month[1] + (leap (time_record.tm_year + 1900) ? 1 : 0);
+	default: return days_in_month[time_record.tm_mon];
+    }
+}
+
+int
+days_last_year (void)
+/* How many days this last year have? */
+{
+    struct tm time_record;
+    time_t current_time;
+    time (&current_time);
+    localtime_r (&current_time, &time_record);
+
+    if (leap(time_record.tm_year - 1 + 1900)) {
+	return 366;
+    }
+
+    return 365;
+}
+
+int
+days_this_year (void)
+/* How many days does this year have */
+{
+     struct tm time_record;
+    time_t current_time;
+    time (&current_time);
+    localtime_r (&current_time, &time_record);
+
+    if (leap(time_record.tm_year + 1900)) {
+	return 366;
+    }
+
+    return 365;
+}
--- anacron-2.3.orig/gregor.h
+++ anacron-2.3/gregor.h
@@ -2,6 +2,7 @@
     Anacron - run commands periodically
     Copyright (C) 1998  Itai Tzur <itzur@actcom.co.il>
     Copyright (C) 1999  Sean 'Shaleh' Perry <shaleh@debian.org>
+    Copyright (C) 2004  Pascal Hakim <pasc@redellipse.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -23,3 +24,7 @@
 
 
 int day_num(int year, int month, int day);
+int days_last_month (void);
+int days_this_month (void);
+int days_last_year (void);
+int days_this_year (void);
--- anacron-2.3.orig/lock.c
+++ anacron-2.3/lock.c
@@ -2,6 +2,7 @@
     Anacron - run commands periodically
     Copyright (C) 1998  Itai Tzur <itzur@actcom.co.il>
     Copyright (C) 1999  Sean 'Shaleh' Perry <shaleh@debian.org>
+    Copyirght (C) 2004  Pascal Hakim <pasc@redellipse.net>
  
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -51,19 +52,53 @@ open_tsfile(job_rec *jr)
 	die_e("Can't chmod timestamp file %s", jr->ident);
 }
 
-static int
-lock_file(int fd)
+int
+lock_file(int fd, short type, pid_t *p)
 /* Attempt to put an exclusive fcntl() lock on file "fd"
  * Return 1 on success, 0 on failure.
+ * Report the blocking pid
  */
 {
-    int r;
+    int r, n = 10;
     struct flock sfl;
 
-    sfl.l_type = F_WRLCK;
+    sfl.l_type = type;
     sfl.l_start = 0;
     sfl.l_whence = SEEK_SET;
     sfl.l_len = 0;   /* we lock all the file */
+    while (n > 0)
+    {
+    r = fcntl(fd, F_GETLK, &sfl);
+    if (r == -1) die_e("fcntl() error");
+    if (sfl.l_type == F_UNLCK) break;
+    if (p) *p = sfl.l_pid;
+    /* try again */
+    usleep(10000);
+    fsync(fd);
+    n--;
+    }
+    sfl.l_type = type;
+    errno = 0;
+    r = fcntl(fd, F_SETLK, &sfl);
+    if (r != -1) return 1;
+    if (errno != EACCES && errno != EAGAIN)
+	die_e("fcntl() error");
+    return 0;
+}
+
+int
+unlock_file(int fd)
+/* Attempt to fcntl() unlock on file "fd"
+ * Return 1 on success, 0 on failure.
+ */
+{
+    int r;
+    struct flock sfl;
+
+    sfl.l_type = F_UNLCK;
+    sfl.l_start = 0;
+    sfl.l_whence = SEEK_SET;
+    sfl.l_len = 0;   /* we unlock all the file */
     errno = 0;
     r = fcntl(fd, F_SETLK, &sfl);
     if (r != -1) return 1;
@@ -111,10 +146,46 @@ consider_job(job_rec *jr)
 	    xclose(jr->timestamp_fd);
 	    return 0;
 	}
+
+	/*
+	 * Check to see if it's a named period, in which case we need 
+	 * to figure it out.
+	 */
+	if (jr->named_period)
+	{
+	    int period = 0, bypass = 0;
+	    switch (jr->named_period)
+	    {
+		case 1:
+		    period = days_last_month ();
+		    bypass = days_this_month ();
+		    break;
+		case 2:
+		    period = days_last_year ();
+		    bypass = days_this_year ();
+		    break;
+        case 3:
+            period = 7;
+            bypass = 7;
+            break;
+        case 4:
+            period = 1;
+            bypass = 1;
+            break;
+		default:
+		    die ("Unknown named period for %s (%d)", jr->ident, jr->named_period);
+	    }
+	    if (day_delta < period && day_delta != bypass)
+	    {
+		/* Job is still too young */
+		xclose (jr->timestamp_fd);
+		return 0;
+	    }
+	}
     }
 
     /* no! try to grab the lock */
-    if (lock_file(jr->timestamp_fd)) return 1;  /* success */
+    if (lock_file(jr->timestamp_fd, F_WRLCK, NULL)) return 1;  /* success */
 
     /* didn't get lock */
     xclose(jr->timestamp_fd);
--- anacron-2.3.orig/log.c
+++ anacron-2.3/log.c
@@ -2,6 +2,7 @@
     Anacron - run commands periodically
     Copyright (C) 1998  Itai Tzur <itzur@actcom.co.il>
     Copyright (C) 1999  Sean 'Shaleh' Perry <shaleh@debian.org>
+    Copyright (C) 2004  Pascal Hakim <pasc@redellipse.net>
  
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -24,7 +25,7 @@
 
 /* Error logging
  *
- * We have two levels of logging (plus debugging if DEBUG is defined):
+ * We have two levels of logging (plus debugging if -v is used):
  * "explain" level for informational messages, and "complain" level for errors.
  *
  * We log everything to syslog, see the top of global.h for relevant
@@ -42,18 +43,31 @@
 #include <signal.h>
 #include <sys/types.h>
 #include <string.h>
+#include <stdlib.h> /* for exit() */
 #include "global.h"
 
 static char truncated[] = " (truncated)";
 static char msg[MAX_MSG + 1];
 static int log_open = 0;
 
+int log_mask = 0;
+
+/* Number of complaints that we've seen */
+int complaints = 0;
+
+static void
+xsetlogmask(int mask)
+{
+    setlogmask(mask);
+}
+
 static void
 xopenlog()
 {
     if (!log_open)
     {
 	openlog(program_name, LOG_PID, SYSLOG_FACILITY);
+	xsetlogmask(log_mask);
 	log_open = 1;
     }
 }
@@ -79,7 +93,7 @@ make_msg(const char *fmt, va_list args)
 }
 
 static void
-log(int priority, const char *fmt, va_list args)
+log_default(int priority, const char *fmt, va_list args)
 /* Log a message, described by "fmt" and "args", with the specified
  * "priority". */
 {
@@ -96,7 +110,7 @@ log(int priority, const char *fmt, va_li
 }
 
 static void
-log_e(int priority, const char *fmt, va_list args)
+log_error(int priority, const char *fmt, va_list args)
 /* Same as log(), but also appends an error description corresponding
  * to "errno". */
 {
@@ -120,57 +134,74 @@ void
 explain(const char *fmt, ...)
 /* Log an "explain" level message */
 {
+    if(log_mask & LOG_MASK(EXPLAIN_LEVEL))
+    {
     va_list args;
 
     va_start(args, fmt);
-    log(EXPLAIN_LEVEL, fmt, args);
+    log_default(EXPLAIN_LEVEL, fmt, args);
     va_end(args);
+    }
 }
 
 void
 explain_e(const char *fmt, ...)
 /* Log an "explain" level message, with an error description */
 {
+    if(log_mask & LOG_MASK(EXPLAIN_LEVEL))
+    {
     va_list args;
 
     va_start(args, fmt);
-    log_e(EXPLAIN_LEVEL, fmt, args);
+    log_error(EXPLAIN_LEVEL, fmt, args);
     va_end(args);
+    }
 }
 
 void
 complain(const char *fmt, ...)
 /* Log a "complain" level message */
 {
+    if(log_mask & LOG_MASK(COMPLAIN_LEVEL))
+    {
     va_list args;
 
     va_start(args, fmt);
-    log(COMPLAIN_LEVEL, fmt, args);
+    log_default(COMPLAIN_LEVEL, fmt, args);
     va_end(args);
+    }
+
+    complaints += 1;
 }
 
 void
 complain_e(const char *fmt, ...)
 /* Log a "complain" level message, with an error description */
 {
+    if(log_mask & LOG_MASK(COMPLAIN_LEVEL))
+    {
     va_list args;
 
     va_start(args, fmt);
-    log_e(COMPLAIN_LEVEL, fmt, args);
+    log_error(COMPLAIN_LEVEL, fmt, args);
     va_end(args);
+    }
+    complaints += 1;
 }
 
 void
 die(const char *fmt, ...)
 /* Log a "complain" level message, and exit */
 {
+    if(log_mask & LOG_MASK(COMPLAIN_LEVEL))
+    {
     va_list args;
 
     va_start(args, fmt);
-    log(COMPLAIN_LEVEL, fmt, args);
+    log_default(COMPLAIN_LEVEL, fmt, args);
     va_end(args);
     if (getpid() == primary_pid) complain("Aborted");
-
+    }
     exit(FAILURE_EXIT);
 }
 
@@ -178,39 +209,40 @@ void
 die_e(const char *fmt, ...)
 /* Log a "complain" level message, with an error description, and exit */
 {
+    if(log_mask & LOG_MASK(COMPLAIN_LEVEL))
+    {
     va_list args;
 
     va_start(args, fmt);
-    log_e(COMPLAIN_LEVEL, fmt, args);
+    log_error(COMPLAIN_LEVEL, fmt, args);
     va_end(args);
     if (getpid() == primary_pid) complain("Aborted");
-
+    }
     exit(FAILURE_EXIT);
 }
 
-#ifdef DEBUG
-
-/* These are called through the Debug() and Debug_e() macros, defined
- * in global.h */
-
 void
 xdebug(const char *fmt, ...)
 {
+    if(log_mask & LOG_MASK(DEBUG_LEVEL))
+    {
     va_list args;
 
     va_start(args, fmt);
-    log(DEBUG_LEVEL, fmt, args);
+    log_default(DEBUG_LEVEL, fmt, args);
     va_end(args);
+    }
 }
 
 void
 xdebug_e(const char *fmt, ...)
 {
+    if(log_mask & LOG_MASK(COMPLAIN_LEVEL))
+    {
     va_list args;
 
     va_start(args, fmt);
-    log_e(DEBUG_LEVEL, fmt, args);
+    log_error(DEBUG_LEVEL, fmt, args);
     va_end(args);
+    }
 }
-
-#endif  /* DEBUG */
--- anacron-2.3.orig/main.c
+++ anacron-2.3/main.c
@@ -2,6 +2,7 @@
     Anacron - run commands periodically
     Copyright (C) 1998  Itai Tzur <itzur@actcom.co.il>
     Copyright (C) 1999  Sean 'Shaleh' Perry <shaleh@debian.org>
+    Copyright (C) 2004  Pascal Hakim <pasc@redellipse.net>
  
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -30,6 +31,14 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <string.h>
+#include <stdlib.h> /* for exit() */
+#include <langinfo.h>
+#include <locale.h>
+#include <syslog.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <getopt.h>
+#include <endian.h>
 #include "global.h"
 #include "gregor.h"
 
@@ -39,8 +48,9 @@ int year, month, day_of_month;
 
 char *program_name;
 char *anacrontab;
+char *spooldir;
 int serialize, force, update_only, now,
-    no_daemon, quiet;                            /* command-line options */
+    no_daemon, quiet, testing_only, list_only; /* command-line options */
 char **args;                       /* vector of "job" command-line arguments */
 int nargs;                                     /* number of these */
 char *defarg = "*";
@@ -50,28 +60,59 @@ sigset_t old_sigmask;
 
 job_rec *first_job_rec;
 env_rec *first_env_rec;
+job_rec sen_job_rec = {0}; /* sentinel job, indicates end of list */
+void *null_sen = NULL; /* sentinel to indicate end of list */
 
-static time_t start_sec;                       /* time anacron started */
+char *mail_charset;
+
+time_t start_sec;                       /* time anacron started */
 static volatile int got_sigalrm, got_sigchld, got_sigusr1;
 int running_jobs, running_mailers;              /* , number of */
 
-static void
-print_version()
-{
-    printf("Anacron " RELEASE "\n"
+
+#define NULL_SEN { NULL, (void*)&null_sen }
+
+#define NORMAL_MASK(lm) { (*lm) = (LOG_MASK(LOG_EMERG) | LOG_MASK(LOG_ALERT) \
+		| LOG_MASK(LOG_CRIT) | LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING) \
+		| LOG_MASK(LOG_NOTICE) | LOG_MASK(LOG_INFO)); LOG_MASK_INIT(lm); }
+#define VERBOSE_MASK(lm) ((*lm) = ((*lm) | LOG_MASK(LOG_DEBUG)))
+#define QUIET_MASK(lm) ((*lm) = ( LOG_MASK(LOG_EMERG) | LOG_MASK(LOG_ALERT) \
+			| LOG_MASK(LOG_CRIT) ))
+
+static const char *version = "Anacron " RELEASE "\n"
 	   "Copyright (C) 1998  Itai Tzur <itzur@actcom.co.il>\n"
 	   "Copyright (C) 1999  Sean 'Shaleh' Perry <shaleh@debian.org>\n"
+	   "Copyright (C) 2004  Pascal Hakim <pasc@redellipse.net>\n"
 	   "\n"
-	   "Mail comments, suggestions and bug reports to <shaleh@debian.org>."
-	   "\n\n");
+	   "Mail comments, suggestions and bug reports to <pasc@redellipse.net>."
+	   "\n\n";
+
+static env_rec fr = NULL_SEN;
+
+static context lm = NULL_SEN;
+static context *plm = &lm;
+
+static void
+print_version()
+{
+	printf("%s", version);
 }
 
 static void
-print_usage()
+print_usage(int is_verbose)
 {
-    printf("Usage:  anacron [-s] [-f] [-n] [-d] [-q] [-t anacrontab] [job] ...\n"
-	   "        anacron -u [job] ...\n"
+	const char *verbose_version = "";
+	if(is_verbose)
+	{
+	    verbose_version = version;
+	}
+
+	printf("%sUsage:  anacron [-s] [-f] [-n] [-d] [-q] [-v] [-Q] [-t anacrontab] [-S spooldir] [job] ...\n"
+	   "        anacron [-S spooldir] -u [job] ...\n"
 	   "        anacron [-V|-h]\n"
+	   "        anacron -T [-t anacrontab]\n"
+	   "        anacron -H file\n"
+	   "        anacron --list-obsolete-timestamp-files [-t anacrontab] [-S spooldir]\n"
 	   "\n"
 	   " -s  Serialize execution of jobs\n"
 	   " -f  Force execution of jobs, even before their time\n"
@@ -82,20 +123,130 @@ print_usage()
 	   " -t  Use this anacrontab\n"
 	   " -V  Print version information\n"
 	   " -h  Print this message\n"
+	   " -v  Enable verbose logging\n"
+	   " -Q  Enable quiet syslog\n"
+	   " -T  Test an anacrontab\n"
+	   " -S  Select a different spool directory\n"
+	   " -H  Print Base64 encoded hash of file for anacrontab\n"
+	   " -l, --list-obsolete-timestamp-files\n"
+	   "     List obsolete timestamp files relative to anacrontab and spooldir\n"
 	   "\n"
 	   "See the manpage for more details.\n"
-	   "\n");
+	   "\n", verbose_version);
+}
+
+int
+open_locked(const char *path, int flags, int *locked_by)
+{
+    int fd = open(path, flags);
+    if (fd == -1)
+    {
+        xdebug("Failed to open path %s, skipping job", path);
+        return -1;
+    }
+    if(0 == lock_file(fd, F_RDLCK, (pid_t*)locked_by))
+    {
+        xdebug("Failed to lock file");
+        close(fd);
+        return -1;
+    }
+    return fd;
+}
+
+static int
+hash_file(const char *path)
+{
+    struct stat st;
+    uint64_t besize;
+    char *rp = realpath(path, NULL);
+    if (rp == NULL)
+    {
+        printf("Error hashing file %s: %s\n", path, strerror(errno));
+        return 1;
+    }
+    int rplen = strlen(rp);
+
+    if(stat(rp, &st) != 0)
+    {
+        printf("Path to file %s does not exist", rp);
+        free(rp);
+        return 1;
+    }
+    besize = htobe64(st.st_size);
+    unsigned char *input = calloc(sizeof(uint64_t) + HASH_SIZE + rplen, 1);
+    if (input == NULL)
+    {
+        printf("Failed to allocate memory for hash\n");
+        return 1;
+    }
+    memcpy(input, &besize, sizeof(uint64_t));
+    memcpy(&input[sizeof(uint64_t) + HASH_SIZE], rp, rplen);
+
+    if(st.st_size == 0)
+    {
+        printf("Path to file %s is a zero byte file", rp);
+        return 1;
+    }
+    int locked_by = 0;
+    int fd = open_locked(rp, O_RDONLY, &locked_by);
+    if (fd == -1)
+    {
+        return 1;
+    }
+    if(locked_by != 0)
+    {
+        printf("%s: may be locked by another process (%d). Hash may change!\n",
+           rp, locked_by);
+    }
+
+    if(-1 == sha256_hash(fd, st.st_size, &input[sizeof(uint64_t)]))
+    {
+        printf("Failed to hash file %s\n", rp);
+        free(rp);
+        unlock_file(fd);
+        close(fd);
+        return 1;
+    }
+    free(rp);
+
+    unlock_file(fd);
+    close(fd);
+
+    size_t base64len = 4 * (sizeof(uint64_t) + HASH_SIZE + rplen);
+    char *base64 = calloc(base64len, 1);
+    if(base64 == NULL)
+    {
+        printf("Failed to allocate memory for base64\n");
+        return 1;
+    }
+    if(-1 == b64_ntop(input, sizeof(uint64_t) + HASH_SIZE + rplen, base64, base64len))
+    {
+        printf("Failed to base64 encode\n");
+        return 1;
+    }
+    free(input);
+    printf("Hash for anacrontab:\n%s\n", base64);
+    free(base64);
+    return 0;
 }
 
 static void
 parse_opts(int argc, char *argv[])
 /* Parse command-line options */
 {
+    struct option longopts[] =
+    {
+        {"list-obsolete-timestamp-files", no_argument, NULL, 'l'},
+        {NULL, 0, NULL, 0}
+    };
     int opt;
+    int is_help = 0;
+    int is_verbose = 0;
+    int is_quiet = 0;
 
-    quiet = no_daemon = serialize = force = update_only = now = 0;
+    quiet = no_daemon = serialize = force = update_only = now = list_only = 0;
     opterr = 0;
-    while ((opt = getopt(argc, argv, "sfundqt:Vh")) != EOF)
+    while ((opt = getopt_long(argc, argv, "sfundqt:TS:VhlvQH:", longopts, NULL)) != EOF)
     {
 	switch (opt)
 	{
@@ -120,12 +271,29 @@ parse_opts(int argc, char *argv[])
 	case 't':
 	    anacrontab = strdup(optarg);
 	    break;
+	case 'T':
+	    testing_only = 1;
+	    break;
+	case 'S':
+	    spooldir = strdup(optarg);
+	    break;
 	case 'V':
 	    print_version();
 	    exit(0);
 	case 'h':
-	    print_usage();
-	    exit(0);
+	    is_help = 1;
+	    break;
+	case 'v':
+	    is_verbose = 1;
+	    break;
+	case 'Q':
+	    is_quiet = 1;
+	    break;
+	case 'H':
+	    exit(hash_file(optarg));
+	case 'l':
+	    list_only = 1;
+	    break;
 	case '?':
 	    fprintf(stderr, "%s: invalid option: %c\n",
 		    program_name, optopt);
@@ -133,7 +301,28 @@ parse_opts(int argc, char *argv[])
 		    program_name);
 	    exit(FAILURE_EXIT);
 	}
+	}
+    if(is_help)
+    {
+        print_usage(is_verbose);
+        exit(0);
+    }
+
+    NORMAL_MASK(&log_mask);
+    if(is_verbose)
+    {
+        if(is_quiet)
+        {
+            print_usage(is_verbose);
+            exit(FAILURE_EXIT);
+        }
+        VERBOSE_MASK(&log_mask);
+    }
+    if(is_quiet)
+    {
+        QUIET_MASK(&log_mask);
     }
+
     if (optind == argc)
     {
 	/* no arguments. Equivalent to: `*' */
@@ -213,19 +402,19 @@ go_background()
 }
 
 void
-handle_sigalrm()
+handle_sigalrm(int sig)
 {
     got_sigalrm = 1;
 }
 
 void
-handle_sigchld()
+handle_sigchld(int sig)
 {
     got_sigchld = 1;
 }
 
 void
-handle_sigusr1()
+handle_sigusr1(int sig)
 {
     got_sigusr1 = 1;
 }
@@ -351,7 +540,7 @@ record_start_time()
     day_of_month = tm_now->tm_mday;
     day_now = day_num(year, month, day_of_month);
     if (day_now == -1) die("Invalid date (this is really embarrassing)");
-    if (!update_only)
+    if (!update_only && !testing_only && !list_only)
 	explain("Anacron " RELEASE " started on %04d-%02d-%02d",
 		year, month, day_of_month);
 }
@@ -377,7 +566,7 @@ fake_jobs()
     int j;
 
     j = 0;
-    while (j < njobs)
+    while (job_array[j] != &sen_job_rec)
     {
 	fake_job(job_array[j]);
 	explain("Updated timestamp for job `%s' to %04d-%02d-%02d",
@@ -392,7 +581,7 @@ explain_intentions()
     int j;
 
     j = 0;
-    while (j < njobs)
+    while (job_array[j] != &sen_job_rec)
     {
 	if (now)
 	{
@@ -405,7 +594,7 @@ explain_intentions()
 	}
 	j++;
     }
-    if (serialize && njobs > 0)
+    if (serialize && j > 0)
 	explain("Jobs will be executed sequentially");
 }
 
@@ -414,21 +603,46 @@ main(int argc, char *argv[])
 {
     int j;
 
+    int cwd;
+    char *cs;
+    njobs = 0;
+
     anacrontab = NULL;
+    spooldir = NULL;
 
     if((program_name = strrchr(argv[0], '/')) == NULL)
 	program_name = argv[0];
     else
 	++program_name; /* move pointer to char after '/' */
 
+    /* Get the default locale character set for the mail
+     * "Content-Type: ...; charset=" header
+     */
+    setlocale(LC_CTYPE, "");
+    /* "US-ASCII" is preferred to "ANSI_X3.4-1968" in MIME, even
+     * though "ANSI_X3.4-1968" is the official charset name. */
+    if ((cs = nl_langinfo(CODESET)) && strcmp(cs, "ANSI_X3.4-1968") != 0)
+	mail_charset = strdup(cs);
+    else
+	mail_charset = "US-ASCII";
+    setlocale(LC_CTYPE, "C");
+
     parse_opts(argc, argv);
+    first_env_rec = &fr;
 
     if (anacrontab == NULL)
 	anacrontab = strdup(ANACRONTAB);
 
+    if (spooldir == NULL)
+	spooldir = strdup(SPOOLDIR);
+
+    if ((cwd = open ("./", O_RDONLY)) == -1) {
+	die_e ("Can't save current directory");
+    }
+
     in_background = 0;
 
-    if (chdir(SPOOLDIR)) die_e("Can't chdir to " SPOOLDIR);
+    if (chdir(spooldir)) die_e("Can't chdir to %s", spooldir );
 
     old_umask = umask(0);
 
@@ -437,15 +651,29 @@ main(int argc, char *argv[])
     if (fclose(stdin)) die_e("Can't close stdin");
     xopen(0, "/dev/null", O_RDONLY);
 
-    if (!no_daemon)
+    if (!no_daemon && !testing_only && !list_only)
 	go_background();
     else
 	primary_pid = getpid();
 
     record_start_time();
-    read_tab();
+    read_tab(cwd);
+    close(cwd);
     arrange_jobs();
 
+    if (list_only)
+    {
+    list_obsolete_jobs();
+    exit(0);
+    }
+
+    if (testing_only)
+    {
+	if (complaints) exit (1);
+	
+	exit (0);
+    }
+
     if (update_only)
     {
 	fake_jobs();
@@ -462,6 +690,6 @@ main(int argc, char *argv[])
 	launch_job(job_array[j]);
     }
     wait_children();
-    explain("Normal exit (%d jobs run)", njobs);
+    explain("Normal exit (%d job%s run)", njobs, (njobs == 1 ? "" : "s"));
     exit(0);
 }
--- anacron-2.3.orig/matchrx.c
+++ anacron-2.3/matchrx.c
@@ -26,6 +26,7 @@
 #include <regex.h>
 #include <stdarg.h>
 #include <stdlib.h>
+#include <string.h> /* for memset() */
 #include "matchrx.h"
 
 int
@@ -41,7 +42,7 @@ match_rx(const char *rx, char *string, i
  * This is not the most efficient, or elegant way of doing this.
  */
 {
-	int r, n;
+	int r, n, ret = 1;
 	regex_t crx;
 	va_list va;
 	char **substring;
@@ -49,11 +50,24 @@ match_rx(const char *rx, char *string, i
 	sub_offsets = malloc(sizeof(regmatch_t) * (n_sub + 1));
 	memset(sub_offsets, 0, sizeof(regmatch_t) * (n_sub + 1));
 
-	if (regcomp(&crx, rx, REG_EXTENDED)) return - 1;
+	if (regcomp(&crx, rx, REG_EXTENDED))
+	{
+		ret = -1;
+		goto done;
+	}
 	r = regexec(&crx, string, n_sub + 1, sub_offsets, 0);
-	if (r != 0 && r != REG_NOMATCH) return - 1;
+	if (r != 0 && r != REG_NOMATCH)
+	{
+		ret = -1;
+		goto done;
+	}
+
 	regfree(&crx);
-	if (r == REG_NOMATCH) return 0;
+	if (r == REG_NOMATCH)
+	{
+		ret = 0;
+		goto done;
+	}
 
 	va_start(va, n_sub);
 	n = 1;
@@ -62,13 +76,16 @@ match_rx(const char *rx, char *string, i
 		substring = va_arg(va, char**);
 		if (substring != NULL)
 		{
-			if (sub_offsets[n].rm_so == -1) return - 1;
-			*substring = string + sub_offsets[n].rm_so;
-			*(string + sub_offsets[n].rm_eo) = 0;
+			if (sub_offsets[n].rm_so != -1)
+			{
+				*substring = string + sub_offsets[n].rm_so;
+				*(string + sub_offsets[n].rm_eo) = 0;
+			}
 		}
 		n++;
 	}
 	va_end(va);
+done:
 	free(sub_offsets);
-	return 1;
+	return ret;
 }
--- anacron-2.3.orig/readtab.c
+++ anacron-2.3/readtab.c
@@ -2,6 +2,7 @@
     Anacron - run commands periodically
     Copyright (C) 1998  Itai Tzur <itzur@actcom.co.il>
     Copyright (C) 1999  Sean 'Shaleh' Perry <shaleh@debian.org>
+    Copyright (C) 2004  Pascal Hakim <pasc@redellipse.net>
  
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -34,6 +35,9 @@
 #include <fnmatch.h>
 #include <unistd.h>
 #include <signal.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/stat.h>
 #include "global.h"
 #include "matchrx.h"
 
@@ -46,11 +50,38 @@ static int jobs_read;            /* numb
 static int line_num;             /* current line in anacrontab */
 static job_rec *last_job_rec;    /* last job stored in memory, at the moment */
 static env_rec *last_env_rec;    /* last environment assignment stored */
+extern job_rec sen_job_rec;      /* sentinel job, indicates end of list */
+
+/* internal 'errno' lookup table */
+static int err_val = 0;
+#define ERR_OUT_OF_RANGE 1
+#define ERR_BAD_HASH 2
+#define ERR_UNKNOWN_NAMED 3
+#define ERR_INVALID_SYNTAX 4
+static const char * err_msgs[] = {
+  "No error",
+  "%s: number out of range on line %d, skipping",
+  "%s: invalid hash information on line %d, skipping. Use -H to generate:\n"
+    "$ anacron -H /usr/sbin/anacron\n"
+    "Hash for anacrontab:\n"
+    "AAAAAAABKCDX8ts7Ik86X-Q2VTTTxku_iT9TKb6r1pjTxl-5OWTQHS91c3Ivc2Jpbi9hbmFjcm9u",
+  "%s: Unknown named period on line %d, skipping",
+  "Invalid syntax in %s on line %d - skipping this line",
+};
+
+static char data[4096] = {0};
 
 /* some definitions for the obstack macros */
 #define obstack_chunk_alloc xmalloc
 #define obstack_chunk_free free
 
+static void
+setup_hash_index(char *path, job_rec *jr, char *id)
+{
+    jr->ident = id;
+    jr->path_to_hash = path;
+}
+
 static void *
 xmalloc (size_t size)
 /* Just like standard malloc(), only never returns NULL. */
@@ -83,11 +114,23 @@ read_tab_line ()
 Return NULL if no more lines.
  */
 {
-    int c;
+    int c, prev=0;
 
     if (feof(tab)) return NULL;
-    while ((c = getc(tab)) != EOF && c != '\n')
-	obstack_1grow(&input_o, c);
+    while (1)
+    {
+	c = getc(tab);
+	if ((c == '\n' && prev != '\\') || c == EOF)
+	{
+	    if (0 != prev) obstack_1grow(&input_o, prev);
+	    break;
+	}
+
+	if ('\\' != prev && 0 != prev && '\n' != prev) obstack_1grow(&input_o, prev);
+	else if ('\n' == prev) obstack_1grow(&input_o, ' ');
+
+	prev = c;
+    }
     if (ferror(tab)) die_e("Error reading %s", anacrontab);
     obstack_1grow(&input_o, '\0');
     return obstack_finish(&input_o);
@@ -95,7 +138,7 @@ Return NULL if no more lines.
 
 static int
 job_arg_num(const char *ident)
-/* Return the command-line-argument number refering to this job-identifier.
+/* Return the command-line-argument number referring to this job-identifier.
  * If it isn't specified, return -1.
  */
 {
@@ -128,17 +171,87 @@ register_env(const char *env_var, const
     if (last_env_rec != NULL) last_env_rec->next = er;
     else first_env_rec = er;
     last_env_rec = er;
-    Debug(("on line %d: %s", line_num, er->assign));
+    xdebug("on line %d: %s", line_num, er->assign);
+}
+
+off_t
+parse_hash_info(char *base64, unsigned char *hash, char **path)
+{
+    uint64_t hash_file_size = 0;
+    err_val = 0;
+    if(base64 == NULL || path == NULL || hash == NULL)
+    {
+        xdebug("No hash information on line %d", line_num);
+        return 0;
+    }
+
+    /* Max base64 decode size is length * 3 / 4 - padding bytes */
+    int max_b64_len = strlen(base64) * 3 / 4;
+    if(max_b64_len <= MIN_HASH_INFO)
+    {
+        err_val = ERR_BAD_HASH;
+        return -1;
+    }
+    *path = obstack_alloc(&tab_o, max_b64_len);
+    int decoded_size = b64_pton(base64, (unsigned char *)*path, max_b64_len);
+    if (decoded_size == -1 || decoded_size > max_b64_len)
+    {
+        err_val = ERR_BAD_HASH;
+        return -1;
+    }
+    memcpy(&hash_file_size, *path, sizeof(uint64_t));
+    hash_file_size = be64toh(hash_file_size);
+    memcpy(hash, *path + sizeof(uint64_t), HASH_SIZE);
+    memmove(*path,
+            *path + sizeof(uint64_t) + HASH_SIZE,
+            decoded_size - (sizeof(uint64_t) + HASH_SIZE));
+    (*path)[decoded_size - (sizeof(uint64_t) + HASH_SIZE)] = '\0';
+    return hash_file_size;
+}
+
+static job_rec *
+register_job_internal(int period, int named_period, int delay, char *ident,
+	char *command, unsigned char *hash, size_t hash_file_size, char *path)
+{
+    job_rec *jr = obstack_alloc(&tab_o, sizeof(job_rec));
+    jr->period = period;
+    jr->named_period = named_period;
+    jr->delay = delay;
+    jr->tab_line = line_num;
+    jr->ident = ident;
+    jr->command = command;
+    jr->job_pid = jr->mailer_pid = 0;
+    if (last_job_rec != NULL) last_job_rec->next = jr;
+    else first_job_rec = jr;
+    last_job_rec = jr;
+    jr->prev_env_rec = last_env_rec;
+    jr->next = &sen_job_rec;
+    jr->path_to_hash = NULL;
+    jr->mail_header_size = 0;
+    if(hash_file_size > 0)
+    {
+        jr->hash = obstack_alloc(&tab_o, HASH_SIZE);
+        memcpy(jr->hash, hash, HASH_SIZE);
+        jr->hash_file_size = hash_file_size;
+        if(path != NULL)
+        {
+            jr->path_to_hash = path;
+        }
+    }
+    return jr;
 }
 
 static void
 register_job(const char *periods, const char *delays,
-	     const char *ident, char *command)
+	     const char *ident, char *command, char *base64)
 /* Store a job definition */
 {
     int period, delay;
     job_rec *jr;
     int ident_len, command_len;
+    unsigned char hash[HASH_SIZE] = {0};
+    off_t hash_file_size = 0;
+    char *path = NULL;
 
     ident_len = strlen(ident);
     command_len = strlen(command);
@@ -147,27 +260,76 @@ register_job(const char *periods, const
     delay = conv2int(delays);
     if (period < 0 || delay < 0)
     {
-	complain("%s: number out of range on line %d, skipping",
-		 anacrontab, line_num);
+        err_val = ERR_OUT_OF_RANGE;
+        return;
+    }
+    hash_file_size = parse_hash_info(base64, hash, &path);
+    if(hash_file_size == -1)
+    {
+        return;
+    }
+    jr = register_job_internal(period, 0, delay,
+	    obstack_alloc(&tab_o, ident_len + 1),
+	    obstack_alloc(&tab_o, command_len + 1),
+	    hash, hash_file_size, path);
+    strcpy(jr->ident, ident);
+    jr->arg_num = job_arg_num(ident);
+    strcpy(jr->command, command);
+
+    xdebug("Read job - period=%d, delay=%d, ident=%s, command=%s",
+	   jr->period, jr->delay, jr->ident, jr->command);
+}
+
+static void
+register_period_job(const char *periods, const char *delays,
+		    const char *ident, char *command, char *base64)
+/* Store a job definition with a named period */
+{
+    int delay, named_period;
+    job_rec *jr = NULL;
+    int ident_len, command_len;
+    unsigned char hash[HASH_SIZE] ={0};
+    off_t hash_file_size = 0;
+    char *path = NULL;
+
+    ident_len = strlen(ident);
+    command_len = strlen(command);
+    jobs_read++;
+    delay = conv2int(delays);
+    if (delay < 0)
+    {
+        err_val = ERR_OUT_OF_RANGE;
+        return;
+    }
+    hash_file_size = parse_hash_info(base64, hash, &path);
+    if(hash_file_size == -1)
+    {
+        return;
+    }
+
+    if (!strncmp ("@monthly", periods, 7)) {
+	named_period = 1;
+    } else if (!strncmp("@yearly", periods, 7)) {
+	named_period = 2;
+    } else if (!strncmp("@weekly", periods, 7)) {
+	named_period = 3;
+    } else if (!strncmp("@daily", periods, 7)) {
+	named_period = 4;
+    } else {
+	err_val = ERR_UNKNOWN_NAMED;
 	return;
     }
-    jr = obstack_alloc(&tab_o, sizeof(job_rec));
-    jr->period = period;
-    jr->delay = delay;
-    jr->tab_line = line_num;
-    jr->ident = obstack_alloc(&tab_o, ident_len + 1);
+    jr = register_job_internal(0, named_period, delay,
+	    obstack_alloc(&tab_o, ident_len + 1),
+	    obstack_alloc(&tab_o, command_len + 1),
+	    hash, hash_file_size, path);
+
     strcpy(jr->ident, ident);
     jr->arg_num = job_arg_num(ident);
-    jr->command = obstack_alloc(&tab_o, command_len + 1);
     strcpy(jr->command, command);
-    jr->job_pid = jr->mailer_pid = 0;
-    if (last_job_rec != NULL) last_job_rec->next = jr;
-    else first_job_rec = jr;
-    last_job_rec = jr;
-    jr->prev_env_rec = last_env_rec;
-    jr->next = NULL;
-    Debug(("Read job - period=%d, delay=%d, ident=%s, command=%s",
-	   jr->period, jr->delay, jr->ident, jr->command));
+
+    xdebug("Read job - period %d, delay=%d, ident=%s, command=%s",
+	  jr->named_period, jr->delay, jr->ident, jr->command);
 }
 
 static void
@@ -179,14 +341,17 @@ parse_tab_line(char *line)
     char *periods;
     char *delays;
     char *ident;
+    char *hash=NULL;
     char *command;
+    char *wrapper;
 
+    err_val = 0;
     /* an empty line? */
     r = match_rx("^[ \t]*($|#)", line, 0);
     if (r == -1) goto reg_err;
     if (r)
     {
-	Debug(("line %d empty", line_num));
+	xdebug("line %d empty", line_num);
 	return;
     }
 
@@ -202,42 +367,190 @@ parse_tab_line(char *line)
 
     /* a job? */
     r = match_rx("^[ \t]*([[:digit:]]+)[ \t]+([[:digit:]]+)[ \t]+"
-		 "([^ \t/]+)[ \t]+([^ \t].*)$",
-		 line, 4, &periods, &delays, &ident, &command);
+		 "([^ \t/]+)(\\/([-0-9A-Za-z_=]+))?[ \t]+([^ \t].*)$",
+		 line, 6, &periods, &delays, &ident, &wrapper, &hash, &command);
     if (r == -1) goto reg_err;
     if (r)
     {
-	register_job(periods, delays, ident, command);
+	register_job(periods, delays, ident, command, hash);
 	return;
     }
-    complain("Invalid syntax in %s on line %d - skipping this line",
-	     anacrontab, line_num);
+
+    /* A period job? */
+    r = match_rx("^[ \t]*(@[^ \t]+)[ \t]+([[:digit:]]+)[ \t]+"
+		 "([^ \t/]+)(\\/([-0-9A-Za-z_=]+))?[ \t]+([^ \t].*)$",
+		 line, 6, &periods, &delays, &ident, &wrapper, &hash, &command);
+    if (r == -1) goto reg_err;
+    if (r)
+    {
+	register_period_job(periods, delays, ident, command, hash);
+	return;
+    }
+
+    err_val = ERR_INVALID_SYNTAX;
     return;
 
  reg_err:
     die("Regex error reading %s", anacrontab);
 }
 
-void
-read_tab()
-/* Read the anacrontab file into memory */
+static env_rec *fr = NULL;
+static char name[PATH_MAX];
+
+static int
+parse_line(char *line, char **path, unsigned char *hash)
 {
-    char *tab_line;
+    off_t r;
+    err_val = 0;
+    r = parse_hash_info(line, hash, path);
+    return r;
+}
 
-    first_job_rec = last_job_rec = NULL;
-    first_env_rec = last_env_rec = NULL;
-    jobs_read = 0;
-    line_num = 0;
+static void
+store_hash(job_rec *jr, char *assign, unsigned char *hash, int size)
+{
+    if(strlen(assign) == size)
+    {
+    env_rec *er;
+    int var_len = strlen(assign);
+    er = obstack_alloc(&tab_o, sizeof(env_rec));
+    er->assign = assign;
+    er->assign[size] = '=';
+    memcpy(er->assign + var_len + 1, hash, HASH_SIZE);
+    er->assign[var_len + 1 + HASH_SIZE] = 0;
+    if (last_env_rec != NULL) last_env_rec->next = er;
+    else first_env_rec = er;
+    last_env_rec = er;
+    }
+    else
+    {
+    if (last_job_rec != NULL) last_job_rec->next = jr;
+    else first_job_rec = jr;
+    last_job_rec = jr;
+    jr->next = &sen_job_rec;
+
+    jr->hash = obstack_alloc(&tab_o, HASH_SIZE);
+    memcpy(jr->hash, hash, HASH_SIZE);
+    }
+}
+
+static void
+env_hash_line(job_rec *jr, char *env)
+{
+    unsigned char hash[HASH_SIZE] = {0};
+    int r =0;
+    char *assign = NULL;
+    /* loop through env and check each line for info */
+    if(env == NULL) return;
+    char *line = env;
+    do
+    {
+    if ((r = parse_line(line, &assign, hash)) == -1 && err_val > 0)
+    {
+        snprintf(data, sizeof(data), err_msgs[err_val], anacrontab, line_num);
+        xdebug("Invalid env %s", data);
+    }
+    line = strchr(line, '\n');
+    if(line != NULL) line++;
+    }
+    while(r == -1 && line != NULL);
+    if(r > 0)
+    {
+        store_hash(jr, assign, hash, r);
+    }
+}
+
+static void
+check_hash_env(void *nx, char *ind, int *init)
+{
+    context *ctx = (context *)nx;
+    int n = 0;
+    int delay = xinit(ctx, init);
+    job_rec *jr = obstack_alloc(&tab_o, sizeof(job_rec));
+    if(jr == NULL) return;
+
+    memset(jr, 0, sizeof(job_rec));
+
+    /* Check for hash file */
+    setup_hash_index(ind, jr, "");
+    jr->hash_file_size = DEFAULT_SIZE;
+    if (check_exists(jr) != 1)
+    {
+        goto done;
+    }
+
+    env_hash_line(jr, data);
+
+    job_rec *next = jr->next;
+    if (next == NULL || jr->path_to_hash == NULL)
+    {
+        goto done;
+    }
+
+    n++;
+    jr->next = nx;
+    jr->mail_header_size = 0;
+    *next = *jr;
+done:
+    xreset(ctx, delay, n);
+    first_job_rec = &sen_job_rec;
+    last_job_rec = NULL;
+    first_env_rec = NULL;
+    fr = NULL;
+}
+
+static void
+check_default_env(char *name, int *n)
+{
+    if (fr != NULL)
+    {
+        check_hash_env(fr, name, n);
+    }
+}
+
+static void
+setup_tab(int cwd)
+{
     /* Open the anacrontab file */
+    if (fchdir (cwd)) die_e("Can't fchdir to %s", cwd);
     tab = fopen(anacrontab, "r");
+    if (chdir(spooldir)) die_e("Can't chdir to %s", spooldir);
+
     if (tab == NULL) die_e("Error opening %s", anacrontab);
+
     /* Initialize the obstacks */
     obstack_init(&input_o);
     obstack_init(&tab_o);
+
+    fr = first_env_rec;
+}
+
+void
+read_tab(int cwd)
+/* Read the anacrontab file into memory */
+{
+    char *tab_line;
+
+    /* Initialize temp file names */
+    if (-1 == init_temp_file(name, sizeof(name))) die_e("Can't init temp_file state");
+
+    /* Open anancrontab and setup for parsing */
+    setup_tab(cwd);
+
+    first_job_rec = &sen_job_rec;
+    last_job_rec = NULL;
+    first_env_rec = last_env_rec = NULL;
+    jobs_read = 0;
+    line_num = 0;
+
+    /* Check for default env */
+    check_default_env(name, &njobs);
+
     while ((tab_line = read_tab_line()) != NULL)
     {
 	line_num++;
 	parse_tab_line(tab_line);
+	if(err_val > 0) complain(err_msgs[err_val], anacrontab, line_num);
 	obstack_free(&input_o, tab_line);
     }
     if (fclose(tab)) die_e("Error closing %s", anacrontab);
@@ -258,6 +571,36 @@ execution_order(const job_rec **job1, co
     return d;
 }
 
+int
+check_exists(const job_rec *jr)
+{
+    struct stat st;
+
+    if(stat(jr->path_to_hash, &st) != 0)
+    {
+        explain("Job `%s' hash file %s does not exist - skipping",
+            jr->ident, jr->path_to_hash);
+        return -1;
+    }
+    if(st.st_size != jr->hash_file_size)
+    {
+        explain("Job `%s' hash file %s size does not match expected - skipping",
+            jr->ident, jr->path_to_hash);
+        return -1;
+    }
+    return 1;
+}
+
+static int
+valid_job(job_rec * jr)
+{
+    if(consider_job(jr) && (jr->path_to_hash == NULL || check_exists(jr) == 1))
+    {
+        return 1;
+    }
+    return 0;
+}
+
 void
 arrange_jobs()
 /* Make an array of pointers to jobs that are going to be executed,
@@ -265,22 +608,76 @@ arrange_jobs()
  * Also lock these jobs.
  */
 {
+    int nsort = 0;
     job_rec *j;
 
     j = first_job_rec;
-    njobs = 0;
-    while (j != NULL)
+    while (j != &sen_job_rec)
     {
-	if (j->arg_num != -1 && (update_only || consider_job(j)))
+	if (j->arg_num != -1 && (update_only || testing_only || list_only || valid_job(j)))
 	{
 	    njobs++;
 	    obstack_grow(&tab_o, &j, sizeof(j));
 	}
 	j = j->next;
     }
+
+    nsort = obstack_object_size(&tab_o)/sizeof(j);
+
+    /* add sentinel to indicate end of list */
+    obstack_grow(&tab_o, &j, sizeof(j));
+
     job_array = obstack_finish(&tab_o);
 
     /* sort the jobs */
-    qsort(job_array, njobs, sizeof(*job_array),
+    qsort(job_array, nsort, sizeof(*job_array),
 	  (int (*)(const void *, const void *))execution_order);
 }
+
+static int
+ident_order(const job_rec **job1, const job_rec **job2)
+{
+    int d;
+
+    d = strcmp((*job1)->ident, (*job2)->ident);
+    return d;
+}
+
+void
+list_obsolete_jobs()
+{
+    DIR *dir;
+    struct dirent *ent;
+    struct stat statbuf;
+    job_rec key;
+    job_rec *keyp;
+    job_rec *res;
+
+    /* sort the jobs */
+    qsort(job_array, njobs, sizeof(*job_array),
+        (int (*)(const void *, const void *))ident_order);
+
+    dir = opendir(".");
+    if (!dir) die_e("Can't opendir %s", spooldir);
+
+    errno = 0;
+    keyp = &key;
+    while ((ent = readdir(dir)) != NULL)
+    {
+    if (stat(ent->d_name, &statbuf))
+    {
+    complain_e("Can't stat %s", ent->d_name);
+    errno = 0;
+    continue;
+    }
+    if (!S_ISREG(statbuf.st_mode)) continue;
+
+    key.ident = ent->d_name;
+    res = bsearch(&keyp, job_array, njobs, sizeof(*job_array),
+        (int (*)(const void *, const void *))ident_order);
+    if (!res) puts(ent->d_name);
+    }
+    if (errno) die_e("Error reading %s", spooldir);
+
+    closedir(dir);
+}
--- anacron-2.3.orig/runjob.c
+++ anacron-2.3/runjob.c
@@ -33,32 +33,71 @@
 #include <signal.h>
 #include <stdio.h>
 #include <string.h>
+#include <inttypes.h>
 #include "global.h"
 
+static char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+static int name_len;
+
+static int DEFAULT_FLAGS = O_RDONLY;
+
+int
+print_temp_name(char *str, size_t size)
+{
+    char template[] = "/tmp/anacron-%s";
+    char suffix[8] = {0};
+    int i, d;
+    long r;
+
+    for (i = 0; i < sizeof(suffix)-1; i++)
+    {
+        r= random();
+        suffix[i] = chars[r%(sizeof(chars)-1)];
+    }
+    d = snprintf(str, size, template, suffix);
+    if (d < 0) die_e("Can't find a unique temporary filename");
+    return d;
+}
+
+int
+init_temp_file(char *name, size_t size)
+{
+    unsigned int seed;
+
+    seed = start_sec;
+    srandom(seed);
+
+    /* get expected print length */
+    name_len = print_temp_name(name, size);
+
+    return name_len;
+}
+
 static int
-temp_file()
+temp_file(job_rec *jr)
 /* Open a temporary file and return its file descriptor */
 {
-    const int max_retries = 50;
-    char *name;
-    int fd, i;
+    char name[PATH_MAX];
+    int fd;
+    static int ready = 0;
 
-    i = 0;
-    name = NULL;
+    if (!ready)
+    {
+        if (-1 == init_temp_file(name, sizeof(name))) die_e("Can't init temp_file state");
+        ready = 1;
+    }
+
+    fd = -1;
     do
     {
-	i++;
-	free(name);
-	name = tempnam(NULL, NULL);
-	if (name == NULL) die("Can't find a unique temporary filename");
-	fd = open(name, O_RDWR | O_CREAT | O_EXCL | O_APPEND,
-		  S_IRUSR | S_IWUSR);
-	/* I'm not sure we actually need to be so persistent here */
-    } while (fd == -1 && errno == EEXIST && i < max_retries);
-    
-    if (fd == -1) die_e("Can't open temporary file");
-    if (unlink(name)) die_e("Can't unlink temporary file");
-    free(name);
+        if (print_temp_name(name, sizeof(name)) != name_len) continue; /* try again */
+        fd = open(name, O_RDWR | O_CREAT | O_EXCL | O_APPEND | O_NOFOLLOW,
+            S_IRUSR | S_IWUSR);
+
+    } while (fd == -1 && errno == EEXIST);
+
+    if (fd == -1) die_e("Failed to create and open unique temporary filename");
+    jr->temp_file_path = strdup(name);
     fcntl(fd, F_SETFD, 1);    /* set close-on-exec flag */
     return fd;
 }
@@ -84,7 +123,7 @@ username()
 }
 
 static void
-xputenv(const char *s)
+xputenv(char *s)
 {
     if (putenv(s)) die_e("Can't set the environment");
 }
@@ -100,19 +139,287 @@ setup_env(const job_rec *jr)
     xputenv(er->assign);
     while (er != jr->prev_env_rec)
     {
-	er = er->next;
-	xputenv(er->assign);
+        er = er->next;
+        xputenv(er->assign);
+    }
+}
+
+static int
+compare_hash(int hd, const job_rec *jr)
+{
+    unsigned char hash[HASH_SIZE];
+    if (hd == -1 || jr->hash_file_size <= 0 || jr->hash == NULL)
+    {
+        return -1;
+    }
+    if(-1 == sha256_hash(hd, jr->hash_file_size, hash))
+    {
+        xdebug("Failed to hash file, skipping job");
+        return -1;
+    }
+    if(memcmp(hash, jr->hash, sizeof(hash)) != 0)
+    {
+        xdebug("Hash did not match, skipping job");
+        return -1;
+    }
+    xdebug("hash matched");
+
+    return 1;
+}
+
+static void
+xwrite(int fd, const char *string)
+/* Write (using write()) the string "string" to temporary file "fd".
+ * Don't return on failure */
+{
+    if (write(fd, string, strlen(string)) == -1)
+	die_e("Can't write to temporary file");
+}
+
+static void
+write_common(job_rec *jr, int index)
+{
+    char hostname[512];
+    char *mailto;
+
+    /* get hostname */
+    if (gethostname(hostname, 512)) {
+      strcpy (hostname,"unknown machine");
+    }
+
+    /* Get the destination email address if set, or current user otherwise */
+    mailto = getenv("MAILTO");
+
+    if (mailto)
+        jr->mailto = mailto;
+    else
+        jr->mailto = username ();
+
+    /* write mail header */
+    if (lseek(jr->output_fd, index, SEEK_SET) == -1) die_e("Can't lseek()");
+    xwrite(jr->output_fd, "From: ");
+    xwrite(jr->output_fd, "Anacron <");
+    xwrite(jr->output_fd, username());
+    xwrite(jr->output_fd, ">\n");
+    xwrite(jr->output_fd, "To: ");
+    if (mailto) {
+       xwrite(jr->output_fd, mailto);
+    } else {
+       xwrite(jr->output_fd, username());
+    }
+    xwrite(jr->output_fd, "\n");
+    xwrite(jr->output_fd, "Subject: Anacron job '");
+    xwrite(jr->output_fd, jr->ident);
+    xwrite(jr->output_fd, "' on ");
+    xwrite(jr->output_fd, hostname);
+    xwrite(jr->output_fd, "\n");
+    xwrite(jr->output_fd, "Content-Type: text/plain; charset=");
+    xwrite(jr->output_fd, mail_charset);
+    xwrite(jr->output_fd, "\n\n");
+}
+
+static int
+open_file(const char *path, int flags, int *lb)
+{
+    int fd = open_locked(path, flags, lb);
+    if (fd != -1 && lb != NULL && *lb != 0)
+    {
+        explain("File %s may be opened by another process %d", path, *lb);
+    }
+    return fd;
+}
+
+static int
+check_header_size(job_rec *jr, off_t expected)
+{
+    if(expected != jr->mail_header_size)
+    {
+        /* reset output file */
+        jr->mail_header_size = expected;
+        unlink(jr->temp_file_path);
+        free(jr->temp_file_path);
+        return temp_file(jr);
+    }
+    return 0;
+}
+
+static char data[DEFAULT_SIZE] = {0};
+static int mailer_change = 0;
+
+static int
+message_id_header(int fd, job_rec *jr)
+{
+    int index = jr->hash_file_size, n = -1, opts = 0;
+    /* base64 encoded hash as message-id hdr */
+    char b64hash[BASE64_HASH_SIZE] = {0};
+    context *ctx = NEXT_CTX(jr);
+    /* adjust delay */
+    int delay = xinit(ctx, NULL);
+    if(check_exists(jr) != 1)
+    {
+        goto done;
+    }
+    jr->timestamp_fd = fd;
+
+    fd = jr->output_fd = open_file(jr->path_to_hash, O_RDWR | O_NOFOLLOW, &opts);
+    if (fd == -1 || opts <= 1)
+    {
+        goto fail;
+    }
+
+    int temp_fd = check_header_size(jr, jr->hash_file_size);
+    if (temp_fd > 0) close(temp_fd);
+
+    if(jr->temp_file_path == NULL)
+    {
+        goto fail;
+    }
+
+    /* validate expected message-id */
+    if(1 != compare_hash(fd, jr))
+    {
+        xdebug("Hash failure\n");
+        goto fail;
+    }
+
+    write_common(jr, index);
+
+    /* add message-id header */
+    if(-1 == b64_ntop(jr->hash, HASH_SIZE, b64hash, BASE64_HASH_SIZE))
+    {
+        xdebug("Failed to base64 encode\n");
+        goto fail;
+    }
+    xwrite(fd, "Message-ID:");
+    xwrite(fd, b64hash);
+
+    /* set mailer command */
+    xwrite(fd, data);
+    jr->command=data;
+    jr->path_to_hash = NULL;
+
+    xwrite(fd, jr->command);
+    if (lseek(fd, 0, SEEK_SET) == -1) die_e("Can't lseek()");
+
+    unlock_file(fd);
+
+    mailer_change = 1;
+    jr->mail_header_size = file_size(fd);
+    return delay;
+fail:
+    if(fd >= 0)
+    {
+        unlock_file(fd);
+        close(fd);
+        fd = 1;
+    }
+    jr->output_fd = fd;
+done:
+    unlink(jr->temp_file_path);
+    free(jr->temp_file_path);
+    jr->temp_file_path = NULL;
+    xreset(ctx, delay, n);
+    return -1;
+}
+static void
+cleanup_mail(int r, job_rec *jr)
+{
+    if(r != 1)
+    {
+        xreset(NEXT_CTX(jr), r, -1);
+    }
+}
+static int
+setup_mail(job_rec *jr)
+{
+    int fd;
+    int index = 0;
+
+    /* create temporary file for stdout and stderr of the job */
+    fd = jr->output_fd = temp_file(jr);
+
+    if(jr->command != NULL || jr->path_to_hash == NULL)
+    {
+        write_common(jr, index);
+
+        jr->mail_header_size = file_size(fd);
+        return 1;
+    }
+
+    /* add hash specific mail headers */
+    int r = message_id_header(fd, jr);
+    if(r == -1)
+    {
+        close(fd);
+    }
+    return r;
+}
+
+
+int
+sha256_hash(int fd, size_t len, void *resbuf)
+{
+    off_t n;
+    sha256_ctx_t ctx = DEFAULT_CTX_T;
+    ctx.init(&ctx.ctx);
+
+    while (len > 0)
+    {
+        n = read(fd, data, sizeof(data));
+        if (n == -1)
+        {
+            if (errno == EINTR) continue;
+            //error = "Error reading %s for job %s";
+            return -1;
+        }
+        ctx.process(data, n, &ctx.ctx);
+        len -= n;
+    }
+
+    ctx.finish(&ctx.ctx, resbuf);
+    return 1;
+}
+
+static int
+check_hash(const job_rec *jr)
+{
+    int hash_result = -1;
+    int locked_by = 0;
+    if(jr->path_to_hash == NULL)
+    {
+        /* no hashing required */
+        return 1;
+    }
+    hash_result = check_exists(jr);
+    if(hash_result != 1)
+    {
+        return hash_result;
+    }
+
+    int fd = open_file(jr->path_to_hash, DEFAULT_FLAGS, &locked_by);
+    if (fd == -1)
+    {
+        return hash_result;
     }
+
+    hash_result = compare_hash(fd, jr);
+
+    unlock_file(fd);
+    close(fd);
+    return hash_result;
 }
 
 static void
 run_job(const job_rec *jr)
 /* This is called to start the job, after the fork */
 {
-    setup_env(jr);
     /* setup stdout and stderr */
     xclose(1);
     xclose(2);
+    if(-1 == check_hash(jr))
+    {
+        die("Hash failure, terminating job");
+    }
     if (dup2(jr->output_fd, 1) != 1 || dup2(jr->output_fd, 2) != 2)
 	die_e("dup2() error");     /* dup2 also clears close-on-exec flag */
     in_background = 0;  /* now, errors will be mailed to the user */
@@ -126,15 +433,6 @@ run_job(const job_rec *jr)
     die_e("execl() error");
 }
 
-static void
-xwrite(int fd, const char *string)
-/* Write (using write()) the string "string" to temporary file "fd".
- * Don't return on failure */
-{
-    if (write(fd, string, strlen(string)) == -1)
-	die_e("Can't write to temporary file");
-}
-
 static int
 xwait(pid_t pid , int *status)
 /* Check if child process "pid" has finished.  If it has, return 1 and its
@@ -153,6 +451,14 @@ static void
 launch_mailer(job_rec *jr)
 {
     pid_t pid;
+    struct stat buf;
+
+    /* Check that we have a way of sending mail. */
+    if(stat(SENDMAIL, &buf))
+    {
+        complain("Can't find sendmail at %s, not mailing output", SENDMAIL);
+        return;
+    }
 
     pid = xfork();
     if (pid == 0)
@@ -173,7 +479,7 @@ launch_mailer(job_rec *jr)
 	 * options, which don't seem to be appropriate here.
 	 * Hopefully, this will keep all the MTAs happy. */
 	execl(SENDMAIL, SENDMAIL, "-FAnacron", "-odi",
-	      username(), (char *)NULL);
+	      jr->mailto, (char *)NULL);
 	die_e("Can't exec " SENDMAIL);
     }
     /* parent */
@@ -187,7 +493,7 @@ tend_mailer(job_rec *jr, int status)
 {
     if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
 	complain("Tried to mail output of job `%s', "
-		 "but mailer process (" SENDMAIL ") exited with ststus %d",
+		 "but mailer process (" SENDMAIL ") exited with status %d",
 		 jr->ident, WEXITSTATUS(status));
     else if (!WIFEXITED(status) && WIFSIGNALED(status))
 	complain("Tried to mail output of job `%s', "
@@ -205,35 +511,26 @@ tend_mailer(job_rec *jr, int status)
 void
 launch_job(job_rec *jr)
 {
+    int r;
     pid_t pid;
-    int fd;
 
-    /* create temporary file for stdout and stderr of the job */
-    fd = jr->output_fd = temp_file();
-    /* write mail header */
-    xwrite(fd, "From: ");
-    xwrite(fd, username());
-    xwrite(fd, " (Anacron)\n");
-    xwrite(fd, "To: ");
-    xwrite(fd, username());
-    xwrite(fd, "\n");
-    xwrite(fd, "Subject: Anacron job '");
-    xwrite(fd, jr->ident);
-    xwrite(fd, "'\n\n");
-    jr->mail_header_size = file_size(fd);
+    setup_env(jr);
+    r = setup_mail(jr);
+    if(r == -1) return;
 
     pid = xfork();
     if (pid == 0)
     {
-	/* child */
-	in_background = 1;
-	run_job(jr);
-	/* execution never gets here */
+        /* child */
+        in_background = 1;
+        run_job(jr);
+        /* execution never gets here */
     }
     /* parent */
     explain("Job `%s' started", jr->ident);
     jr->job_pid = pid;
     running_jobs++;
+    cleanup_mail(r, jr);
 }
 
 static void
@@ -263,9 +560,23 @@ tend_job(job_rec *jr, int status)
     jr->job_pid = 0;
     running_jobs--;
     if (mail_output) launch_mailer(jr);
+    if (unlink(jr->temp_file_path)) die_e("Can't unlink temporary file");
+    free(jr->temp_file_path);
+    jr->temp_file_path = NULL;
     xclose(jr->output_fd);
 }
 
+static void
+tend_last(job_rec *jr, int status)
+{
+    if(running_jobs != 1 || !mailer_change || jr == NULL || jr->job_pid == 0) return;
+    int delay = xinit(NEXT_CTX(jr), NULL);
+    xwait(jr->job_pid, &status);
+    tend_job(jr, status);
+    xreset(NEXT_CTX(jr), delay, 0);
+}
+
+
 void
 tend_children()
 /* This is called whenever we get a SIGCHLD.
@@ -286,4 +597,5 @@ tend_children()
 	    tend_job(job_array[j], status);
 	j++;
     }
+    tend_last(job_array[j], status);
 }
--- /dev/null
+++ anacron-2.3/sha256.c
@@ -0,0 +1,309 @@
+/* Functions to compute SHA256 message digest of files or memory blocks.
+   according to the definition of SHA256 in FIPS 180-2.
+   Copyright (C) 2007-2018 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* Written by Ulrich Drepper <drepper@redhat.com>, 2007.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <endian.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "sha256.h"
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+# ifdef _LIBC
+#  include <byteswap.h>
+#  define SWAP(n) bswap_32 (n)
+#  define SWAP64(n) bswap_64 (n)
+# else
+#  define SWAP(n) \
+    (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
+#  define SWAP64(n) \
+  (((n) << 56)					\
+   | (((n) & 0xff00) << 40)			\
+   | (((n) & 0xff0000) << 24)			\
+   | (((n) & 0xff000000) << 8)			\
+   | (((n) >> 8) & 0xff000000)			\
+   | (((n) >> 24) & 0xff0000)			\
+   | (((n) >> 40) & 0xff00)			\
+   | ((n) >> 56))
+# endif
+#else
+# define SWAP(n) (n)
+# define SWAP64(n) (n)
+#endif
+
+
+/* This array contains the bytes used to pad the buffer to the next
+   64-byte boundary.  (FIPS 180-2:5.1.1)  */
+static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ...  */ };
+
+
+/* Constants for SHA256 from FIPS 180-2:4.2.2.  */
+static const uint32_t K[64] =
+  {
+    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+  };
+
+void __sha256_process_block (const void *, size_t, struct sha256_ctx *);
+
+/* Initialize structure containing state of computation.
+   (FIPS 180-2:5.3.2)  */
+void
+__sha256_init_ctx (struct sha256_ctx *ctx)
+{
+  ctx->H[0] = 0x6a09e667;
+  ctx->H[1] = 0xbb67ae85;
+  ctx->H[2] = 0x3c6ef372;
+  ctx->H[3] = 0xa54ff53a;
+  ctx->H[4] = 0x510e527f;
+  ctx->H[5] = 0x9b05688c;
+  ctx->H[6] = 0x1f83d9ab;
+  ctx->H[7] = 0x5be0cd19;
+
+  ctx->total64 = 0;
+  ctx->buflen = 0;
+}
+
+
+/* Process the remaining bytes in the internal buffer and the usual
+   prolog according to the standard and write the result to RESBUF.
+
+   IMPORTANT: On some systems it is required that RESBUF is correctly
+   aligned for a 32 bits value.  */
+void *
+__sha256_finish_ctx (struct sha256_ctx *ctx, void *resbuf)
+{
+  /* Take yet unprocessed bytes into account.  */
+  uint32_t bytes = ctx->buflen;
+  size_t pad;
+
+  /* Now count remaining bytes.  */
+  ctx->total64 += bytes;
+
+  pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
+  memcpy (&ctx->buffer[bytes], fillbuf, pad);
+
+  /* Put the 64-bit file length in *bits* at the end of the buffer.  */
+#if _STRING_ARCH_unaligned
+  ctx->buffer64[(bytes + pad) / 8] = SWAP64 (ctx->total64 << 3);
+#else
+  ctx->buffer32[(bytes + pad + 4) / 4] = SWAP (ctx->total[TOTAL64_low] << 3);
+  ctx->buffer32[(bytes + pad) / 4] = SWAP ((ctx->total[TOTAL64_high] << 3) |
+					   (ctx->total[TOTAL64_low] >> 29));
+#endif
+
+  /* Process last bytes.  */
+  __sha256_process_block (ctx->buffer, bytes + pad + 8, ctx);
+
+  /* Put result from CTX in first 32 bytes following RESBUF.  */
+  for (unsigned int i = 0; i < 8; ++i)
+    ((uint32_t *) resbuf)[i] = SWAP (ctx->H[i]);
+
+  return resbuf;
+}
+
+
+void
+__sha256_process_bytes (const void *buffer, size_t len, struct sha256_ctx *ctx)
+{
+  /* When we already have some bits in our internal buffer concatenate
+     both inputs first.  */
+  if (ctx->buflen != 0)
+    {
+      size_t left_over = ctx->buflen;
+      size_t add = 128 - left_over > len ? len : 128 - left_over;
+
+      memcpy (&ctx->buffer[left_over], buffer, add);
+      ctx->buflen += add;
+
+      if (ctx->buflen > 64)
+	{
+	  __sha256_process_block (ctx->buffer, ctx->buflen & ~63, ctx);
+
+	  ctx->buflen &= 63;
+	  /* The regions in the following copy operation cannot overlap.  */
+	  memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
+		  ctx->buflen);
+	}
+
+      buffer = (const char *) buffer + add;
+      len -= add;
+    }
+
+  /* Process available complete blocks.  */
+  if (len >= 64)
+    {
+#if !_STRING_ARCH_unaligned
+/* To check alignment gcc has an appropriate operator.  Other
+   compilers don't.  */
+# if __GNUC__ >= 2
+#  define UNALIGNED_P(p) (((uintptr_t) p) % __alignof__ (uint32_t) != 0)
+# else
+#  define UNALIGNED_P(p) (((uintptr_t) p) % sizeof (uint32_t) != 0)
+# endif
+      if (UNALIGNED_P (buffer))
+	while (len > 64)
+	  {
+	    __sha256_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx);
+	    buffer = (const char *) buffer + 64;
+	    len -= 64;
+	  }
+      else
+#endif
+	{
+	  __sha256_process_block (buffer, len & ~63, ctx);
+	  buffer = (const char *) buffer + (len & ~63);
+	  len &= 63;
+	}
+    }
+
+  /* Move remaining bytes into internal buffer.  */
+  if (len > 0)
+    {
+      size_t left_over = ctx->buflen;
+
+      memcpy (&ctx->buffer[left_over], buffer, len);
+      left_over += len;
+      if (left_over >= 64)
+	{
+	  __sha256_process_block (ctx->buffer, 64, ctx);
+	  left_over -= 64;
+	  memcpy (ctx->buffer, &ctx->buffer[64], left_over);
+	}
+      ctx->buflen = left_over;
+    }
+}
+
+/* Process LEN bytes of BUFFER, accumulating context into CTX.
+   It is assumed that LEN % 64 == 0.  */
+void
+__sha256_process_block (const void *buffer, size_t len, struct sha256_ctx *ctx)
+{
+  const uint32_t *words = buffer;
+  size_t nwords = len / sizeof (uint32_t);
+  uint32_t a = ctx->H[0];
+  uint32_t b = ctx->H[1];
+  uint32_t c = ctx->H[2];
+  uint32_t d = ctx->H[3];
+  uint32_t e = ctx->H[4];
+  uint32_t f = ctx->H[5];
+  uint32_t g = ctx->H[6];
+  uint32_t h = ctx->H[7];
+
+  /* First increment the byte count.  FIPS 180-2 specifies the possible
+     length of the file up to 2^64 bits.  Here we only compute the
+     number of bytes.  */
+  ctx->total64 += len;
+
+  /* Process all bytes in the buffer with 64 bytes in each round of
+     the loop.  */
+  while (nwords > 0)
+    {
+      uint32_t W[64];
+      uint32_t a_save = a;
+      uint32_t b_save = b;
+      uint32_t c_save = c;
+      uint32_t d_save = d;
+      uint32_t e_save = e;
+      uint32_t f_save = f;
+      uint32_t g_save = g;
+      uint32_t h_save = h;
+
+      /* Operators defined in FIPS 180-2:4.1.2.  */
+#define Ch(x, y, z) ((x & y) ^ (~x & z))
+#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+#define S0(x) (CYCLIC (x, 2) ^ CYCLIC (x, 13) ^ CYCLIC (x, 22))
+#define S1(x) (CYCLIC (x, 6) ^ CYCLIC (x, 11) ^ CYCLIC (x, 25))
+#define R0(x) (CYCLIC (x, 7) ^ CYCLIC (x, 18) ^ (x >> 3))
+#define R1(x) (CYCLIC (x, 17) ^ CYCLIC (x, 19) ^ (x >> 10))
+
+      /* It is unfortunate that C does not provide an operator for
+	 cyclic rotation.  Hope the C compiler is smart enough.  */
+#define CYCLIC(w, s) ((w >> s) | (w << (32 - s)))
+
+      /* Compute the message schedule according to FIPS 180-2:6.2.2 step 2.  */
+      for (unsigned int t = 0; t < 16; ++t)
+	{
+	  W[t] = SWAP (*words);
+	  ++words;
+	}
+      for (unsigned int t = 16; t < 64; ++t)
+	W[t] = R1 (W[t - 2]) + W[t - 7] + R0 (W[t - 15]) + W[t - 16];
+
+      /* The actual computation according to FIPS 180-2:6.2.2 step 3.  */
+      for (unsigned int t = 0; t < 64; ++t)
+	{
+	  uint32_t T1 = h + S1 (e) + Ch (e, f, g) + K[t] + W[t];
+	  uint32_t T2 = S0 (a) + Maj (a, b, c);
+	  h = g;
+	  g = f;
+	  f = e;
+	  e = d + T1;
+	  d = c;
+	  c = b;
+	  b = a;
+	  a = T1 + T2;
+	}
+
+      /* Add the starting values of the context according to FIPS 180-2:6.2.2
+	 step 4.  */
+      a += a_save;
+      b += b_save;
+      c += c_save;
+      d += d_save;
+      e += e_save;
+      f += f_save;
+      g += g_save;
+      h += h_save;
+
+      /* Prepare for the next round.  */
+      nwords -= 16;
+    }
+
+  /* Put checksum in context given as argument.  */
+  ctx->H[0] = a;
+  ctx->H[1] = b;
+  ctx->H[2] = c;
+  ctx->H[3] = d;
+  ctx->H[4] = e;
+  ctx->H[5] = f;
+  ctx->H[6] = g;
+  ctx->H[7] = h;
+}
--- /dev/null
+++ anacron-2.3/sha256.h
@@ -0,0 +1,71 @@
+/* Declaration of functions and data types used for SHA256 sum computing
+   library functions.
+   Copyright (C) 2007-2018 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _SHA256_H
+#define _SHA256_H 1
+
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <endian.h>
+
+#define HASH_SIZE 32
+
+
+/* Structure to save state of computation between the single steps.  */
+struct sha256_ctx
+{
+  uint32_t H[8];
+
+  union
+  {
+    uint64_t total64;
+#define TOTAL64_low (1 - (BYTE_ORDER == LITTLE_ENDIAN))
+#define TOTAL64_high (BYTE_ORDER == LITTLE_ENDIAN)
+    uint32_t total[2];
+  };
+  uint32_t buflen;
+  union
+  {
+    char buffer[128];
+    uint32_t buffer32[32];
+    uint64_t buffer64[16];
+  };
+};
+
+/* Initialize structure containing state of computation.
+   (FIPS 180-2: 5.3.2)  */
+extern void __sha256_init_ctx (struct sha256_ctx *ctx) __THROW;
+
+/* Starting with the result of former calls of this function (or the
+   initialization function update the context for the next LEN bytes
+   starting at BUFFER.
+   It is NOT required that LEN is a multiple of 64.  */
+extern void __sha256_process_bytes (const void *buffer, size_t len,
+				    struct sha256_ctx *ctx) __THROW;
+
+/* Process the remaining bytes in the buffer and put result from CTX
+   in first 32 bytes following RESBUF.
+
+   IMPORTANT: On some systems it is required that RESBUF is correctly
+   aligned for a 32 bits value.  */
+extern void *__sha256_finish_ctx (struct sha256_ctx *ctx, void *resbuf)
+  __THROW;
+
+#endif /* sha256.h */
