From 886d9ad08ece61022493f976c10583378a3819e1 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Mon, 8 May 2017 15:30:03 +0200 Subject: [PATCH] Imported Upstream version 1.4.8 --- .gitignore | 29 + AUTHORS | 2 + COPYING | 78 +++ COPYING.anacron | 340 ++++++++++ ChangeLog | 1541 +++++++++++++++++++++++++++++++++++++++++++ ChangeLog.anacron | 39 ++ INSTALL | 22 + Makefile.am | 13 + NEWS | 0 README | 11 + README.anacron | 142 ++++ anacron/Makefile.am | 28 + anacron/global.h | 159 +++++ anacron/gregor.c | 181 +++++ anacron/gregor.h | 30 + anacron/lock.c | 211 ++++++ anacron/log.c | 225 +++++++ anacron/main.c | 516 +++++++++++++++ anacron/matchrx.c | 90 +++ anacron/matchrx.h | 26 + anacron/readtab.c | 393 +++++++++++ anacron/runjob.c | 346 ++++++++++ configure.ac | 244 +++++++ contrib/0anacron | 18 + contrib/0hourly | 4 + contrib/anacrontab | 16 + contrib/dailyjobs | 8 + crond.sysconfig | 3 + cronie.init | 132 ++++ man/Makefile.am | 6 + man/anacron.8 | 167 +++++ man/anacrontab.5 | 98 +++ man/cron.8 | 228 +++++++ man/crond.8 | 1 + man/crontab.1 | 186 ++++++ man/crontab.5 | 309 +++++++++ pam/crond | 10 + src/.indent.pro | 32 + src/Makefile.am | 73 ++ src/bitstring.h | 141 ++++ src/cron.c | 664 +++++++++++++++++++ src/cron.h | 51 ++ src/crontab.c | 971 +++++++++++++++++++++++++++ src/database.c | 623 +++++++++++++++++ src/do_command.c | 571 ++++++++++++++++ src/entry.c | 580 ++++++++++++++++ src/env.c | 242 +++++++ src/externs.h | 127 ++++ src/funcs.h | 113 ++++ src/globals.h | 89 +++ src/job.c | 67 ++ src/macros.h | 132 ++++ src/misc.c | 754 +++++++++++++++++++++ src/pathnames.h | 73 ++ src/popen.c | 165 +++++ src/pw_dup.c | 123 ++++ src/security.c | 615 +++++++++++++++++ src/structs.h | 77 +++ src/user.c | 135 ++++ 59 files changed, 12270 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 COPYING.anacron create mode 100644 ChangeLog create mode 100644 ChangeLog.anacron create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 README.anacron create mode 100644 anacron/Makefile.am create mode 100644 anacron/global.h create mode 100644 anacron/gregor.c create mode 100644 anacron/gregor.h create mode 100644 anacron/lock.c create mode 100644 anacron/log.c create mode 100644 anacron/main.c create mode 100644 anacron/matchrx.c create mode 100644 anacron/matchrx.h create mode 100644 anacron/readtab.c create mode 100644 anacron/runjob.c create mode 100644 configure.ac create mode 100644 contrib/0anacron create mode 100644 contrib/0hourly create mode 100644 contrib/anacrontab create mode 100644 contrib/dailyjobs create mode 100644 crond.sysconfig create mode 100755 cronie.init create mode 100644 man/Makefile.am create mode 100644 man/anacron.8 create mode 100644 man/anacrontab.5 create mode 100644 man/cron.8 create mode 100644 man/crond.8 create mode 100644 man/crontab.1 create mode 100644 man/crontab.5 create mode 100644 pam/crond create mode 100644 src/.indent.pro create mode 100644 src/Makefile.am create mode 100644 src/bitstring.h create mode 100644 src/cron.c create mode 100644 src/cron.h create mode 100644 src/crontab.c create mode 100644 src/database.c create mode 100644 src/do_command.c create mode 100644 src/entry.c create mode 100644 src/env.c create mode 100644 src/externs.h create mode 100644 src/funcs.h create mode 100644 src/globals.h create mode 100644 src/job.c create mode 100644 src/macros.h create mode 100644 src/misc.c create mode 100644 src/pathnames.h create mode 100644 src/popen.c create mode 100644 src/pw_dup.c create mode 100644 src/security.c create mode 100644 src/structs.h create mode 100644 src/user.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c873b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +Makefile.in +Makefile +anacron/anacron +anacron/anacron-paths.h +anacron/Makefile +anacron/Makefile.in +aclocal.m4 +autom4te.cache +config.guess +config.h +config.h.in +config.log +config.status +config.sub +configure +depcomp +install-sh +man/Makefile +man/Makefile.in +missing +stamp-h1 +tags +.deps +*.o +src/crond +src/crontab +src/cron-paths.h +*~ +*.tar.* diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..1cbd2b5 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Original vixie-cron was written by Paul Vixie. + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..53b06f6 --- /dev/null +++ b/COPYING @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ +/* + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software written by Ken Arnold and + * published in UNIX Review, Vol. 6, No. 8. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Paul Vixie. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)bitstring.h 8.1 (Berkeley) 7/19/93 + */ diff --git a/COPYING.anacron b/COPYING.anacron new file mode 100644 index 0000000..60549be --- /dev/null +++ b/COPYING.anacron @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..dfb77ae --- /dev/null +++ b/ChangeLog @@ -0,0 +1,1541 @@ +2011-06-24 Marcela Mašláňová + + * src/do_command.c: Cron writes job output to syslog incorrectly. + When cron is invoked in a way to print job output to syslog, it does + print only the first character of the output. Author: Vitezslav + Cizek Signed-off-by: Marcela Mašláňová + +2011-06-21 Tomas Mraz + + * src/cron.c, src/database.c, src/funcs.h, src/structs.h: Check + orphaned crontabs for adoption. + +2011-06-21 Tomas Mraz + + * src/do_command.c: Unify logging in case of SyslogOutput with the + rest of crond. + +2011-05-26 Marcela Mašláňová + + * man/cron.8, man/crontab.1: Change of email adress of cluster + support author. + +2011-05-16 Kiyoshi OHGISHI + + * anacron/main.c: The charset of anacron's mail is always + ANSI_X3.4-1968. There are no setlocale is anacron's source. Signed-off-by: Marcela Mašláňová + +2011-04-28 Marcela Mašláňová + + * src/cron.c: Cronie disables inotify support when the /etc/crontab + file does not exist at startup. Existence of crontab and directories wasn't controlled before + creating inotify watches. + +2011-03-15 Marcela Mašláňová + + * configure.ac: Clean configure. + +2011-03-15 Marcela Mašláňová + + * ChangeLog, configure.ac: New release 1.4.7. + +2011-03-15 Marcela Mašláňová + + * configure.ac: Split relro and pie into two options. + +2011-03-11 Tomas Mraz + + * anacron/matchrx.c: Add missing va_end() call. + +2011-03-11 Tomas Mraz + + * src/cron.c: Remove unused variable. + +2011-03-11 Tomas Mraz + + * src/env.c: Fix incorrect pointer in sizeof. + +2011-03-11 Tomas Mraz + + * src/crontab.c: Fixed leaking of env and members of entry in + replace_cmd(). + +2011-03-11 Tomas Mraz + + * src/database.c, src/user.c: Fix memory leaks in load_user. + +2011-03-11 Tomas Mraz + + * src/entry.c: The last bit to set is always LAST_ - FIRST_. + +2011-03-11 Tomas Mraz + + * anacron/matchrx.c: Check malloc return for NULL. + +2011-03-11 Tomas Mraz + + * src/misc.c: Do not try to compile dead code. + +2011-03-11 Tomas Mraz + + * src/cron.c, src/crontab.c, src/funcs.h, src/misc.c: Rename + set_cron_cwd() to check_spool_dir() as we do not do any chdir + anymore. + +2011-03-11 Tomas Mraz + + * src/misc.c: Add missing check for stat() return value. + +2011-03-09 Tomas Mraz + + * anacron/main.c: Safeguard for system time changes. Otherwise + anacron might wait with the job running for a too long time. + +2011-03-08 Tomas Mraz + + * src/cron.c: Fix the non-default timezone support. Do not run jobs + multiple times on DST change. + +2011-03-07 Tomas Mraz + + * src/do_command.c, src/security.c: Set mailfrom to the user + account, better PAM logging. + +2011-03-07 Tomas Mraz + + * src/do_command.c, src/funcs.h, src/popen.c, src/security.c: Set + only groups in the process handling PAM calls. Keep uids at 0 so the + process is not killable by the user. + +2011-03-02 Tomas Mraz + + * src/do_command.c: Whitespace cleanups. + +2011-03-02 Tomas Mraz + + * src/do_command.c, src/popen.c: Fix SIGPIPE handling in + do_command() and popen. Ensure that PAM session is always closed. + +2011-02-21 Marcela Mašláňová + + * src/do_command.c: Comment not freeing mailfrom - found during code + review. + +2011-02-21 Marcela Mašláňová + + * src/security.c: Free security contexts. + +2011-02-18 Marcela Mašláňová + + * src/crontab.c: mkstemp needs only 6 X's + +2011-02-21 Marcela Mašláňová + + * anacron/runjob.c: fdin could be tested before initialization. + +2011-02-17 Marcela Mašláňová + + * configure.ac: PIE and RELRO flags to be set We want all long running daemons to have PIE and RELRO flags set. + This is a missing security mechanism. Fixing this generally involves + adding -fPIE -DPIE to the CFLAGS, and -pie -Wl,-z,relro -Wl,-z,now + to the LDFLAGS. Expected results: PIE and partial RELRO at a + minimum. + +2010-12-16 Tomas Mraz + + * src/security.c: Add translation for remaining hardcoded contexts + and av bits. + +2010-12-16 Tomas Mraz + + * src/cron.c, src/funcs.h, src/security.c: Revert "Cache the + security class and bit values and translate the remaining hardcoded + values." Caching cannot be done as the values can change on SELinux + policy update. This reverts commit b15f72976965d2ae1a1273558bf45be7de077b79. + +2010-12-16 Tomas Mraz + + * src/cron.c: Revert "Missing exit if cron_init_security() fails." + Caching cannot be done as the values can change on SELinux policy + update. This reverts commit ac70de36ec6c403c28291689701bd2567c565107. + +2010-12-16 Tomas Mraz + + * src/cron.c: Missing exit if cron_init_security() fails. + +2010-12-15 Tomas Mraz + + * src/cron.c, src/funcs.h, src/security.c: Cache the security class + and bit values and translate the remaining hardcoded values. + +2010-12-15 Tomas Mraz + + * src/security.c: Clean up unnecessary assignment. + +2010-12-15 Dan Walsh + + * src/security.c: This patch causes cronie to ask kernel for + constant definition rather then using hard coded Also add info to syslog message to help diagnose problems. Signed-off-by: Marcela Mašláňová + +2010-12-10 Tomas Mraz + + * anacron/global.h, anacron/lock.c, anacron/main.c: Do not lock jobs + that fall out of allowed range - rhbz#661966 + +2010-10-26 Marcela Mašláňová + + * man/crontab.1: Man - another typo + +2010-10-26 Marcela Mašláňová + + * man/cron.8, man/crontab.1: Re-add missing option into man and fix + some typos. Thanks to Colin Dean. + +2010-10-22 Marcela Mašláňová + + * ChangeLog, configure.ac: New release 1.4.6 + +2010-10-21 Martin Prpič + + * man/anacron.8, man/anacrontab.5, man/cron.8, man/crontab.1, + man/crontab.5: Rewrite of man pages & correction. Signed-off-by: Marcela Mašláňová + +2010-10-21 Marcela Mašláňová + + * man/bitstring.3: Remove useless man page. Thanks to Colin Dean. + +2010-10-21 Marcela Mašláňová + + * src/cron.c: Apply previous patch correctly. + +2010-10-20 Colin Dean + + * src/cron.c: Check clustering before un/watch function Signed-off-by: Marcela Mašláňová + +2010-10-13 Marcela Mašláňová + + * : commit 2d3a872e9c66d9550a5b3cc97fa78ff9d7708cce Author: Marcela + Mašláňová Date: Fri Oct 8 12:17:15 2010 + +0200 + +2010-10-07 Marcela Mašláňová + + * man/cron.8: man page typo: change to correct option + +2010-10-06 Marcela Mašláňová + + * src/database.c: Remove cluster support from inotify_database check_inotify_database is called only when inotify is on, which is + not with cluster support. + +2010-10-06 Colin Dean + + * man/crontab.1, src/crontab.c: The crontab command uses "-c" and + "-n" instead of "-h". Signed-off-by: Marcela Mašláňová + +2010-10-04 Marcela Mašláňová + + * src/crontab.c: Correct ifdef HAVE_FCHOWN Based on https://fedorahosted.org/cronie/ticket/7 Thanks to + crrodriguez + +2010-10-04 Marcela Mašláňová + + * .gitignore, src/database.c: is_local set to zero + +2010-10-04 Colin Dean + + * man/cron.8, man/crontab.1, src/Makefile.am, src/cron.c, + src/crontab.c, src/database.c, src/globals.h: cronie on cluster On issue we have is that although we've made our services very + resilient, by employing HA failover, load balancing and round robin + DNS, the one service that's difficult to do anything with is cron, + because it has traditionally been tied to a single machine. For instance, we have a cluster of 4 Fedora servers which our end + users use as timeshare systems, using round robin DNS, and if one of + the servers is down it doesn't really matter too much. We don't even + backup the servers, relying on NFS home directories and rebuilding + from scratch using kickstart + cfengine if a server fails and can't + be restarted. However, the users have scattered crontab files around the 4 + servers, so that if the particular server a cron job is meant to run + on dies, the job doesn't run any more, and the crontab file may be + permanently lost. What we needed was a facility to allow crontabs in /var/spool/cron + on these 4 servers be NFS mounted from a single directory on our + NetApp filers (so giving us backups and snapshots), with any user + able to run "crontab -e" from any of the servers to manage a single + shared crontab, and for us in the IT Service to be able to set just + one of the 4 servers to run user crontab jobs at any time. However, + we needed to keep /etc/crontab and the files in /etc/cron.d/ + specific to each individual server still, and keep crond running on + all 4 servers. Signed-off-by: Marcela Mašláňová + +2010-08-30 Marcela Mašláňová + + * anacron/runjob.c: Typo in log message Based on Tom London report + https://bugzilla.redhat.com/show_bug.cgi?id=626947 + +2010-08-23 Marcela Mašláňová + + * anacron/main.c: Close leaking file descriptor anacron was leaking fd, which caused denial of jobs by selinux. + +2010-08-02 Marcela Mašláňová + + * ChangeLog, configure.ac: New minor release 1.4.5. + +2010-08-02 Marcela Mašláňová + + * cronie.init: OK value printed twice Fix based on 615107. There were too many OK's messages. + +2010-07-21 Marcela Mašláňová + + * src/cron.c: Help and usage fix Option -h was added. Also details about options were added into + usage. + +2010-07-21 Marcela Mašláňová + + * man/cron.8: man crond -i Document missing option. + +2010-07-12 Marcela Mašláňová + + * man/cron.8, src/cron.c: Syslog output will be used instead of mail If sendmail isn't installed, syslog is used. This patch should also + solve problem with RPM requirements of sendmail (and which mail + should be installed by default). Based on: https://bugzilla.redhat.com/show_bug.cgi?id=472710#c42 + +2010-04-14 Marcela Mašláňová + + * src/entry.c: Remove the whole unused part. + +2010-04-14 Marcela Mašláňová + + * src/entry.c: Remove 'dont log' part which probably never worked. + +2010-04-14 Marcela Mašláňová + + * src/do_command.c, src/entry.c: Beautify error outputs. + +2010-04-14 Michal Seben + + * man/crontab.5, src/entry.c: Option "-" don't log jobs as root If the uid of the owner is 0 (root), he can put a "-" as first + character of a crontab entry. This will prevent cron from writing a + syslog message about this command getting executed. Signed-off-by: Marcela Mašláňová + +2010-04-14 Michal Seben + + * src/security.c: Correctly reported PAM errors cron_conv could be helpfull for debug purposes, when something bad + happens with pam e.g. : expired user password - without cron_conv + cronie doesn't report usefull info in syslog messages (it just + write no conversation function error to messages file),if you want + to do quick test of pam conversation function, you could set + PASS_MAX_DAYS and PASS_WARN_AGE in etc/login.defs , add new user and + for this user create cron rule (crontab -e) Signed-off-by: Marcela Mašláňová + +2010-03-25 Andrew Man-Hon Lau + + * contrib/0anacron: 0anacron should check only readability. Signed-off-by: Marcela Mašláňová + +2010-03-23 Marcela Mašláňová + + * src/security.c: Revert previous change. The code which control the + input entry should be sufficient. Only "/" are removed from scripts. + +2010-03-22 Marcela Mašláňová + + * contrib/0hourly, contrib/dailyjobs, src/security.c: In system + tables was hardcoded home directory to "/". This was changed to + switching to "/" only when home for user isn't defined. Based on rhbz#575419 + +2010-02-23 Marcela Mašláňová + + * man/crontab.5: Definition of system crontables. + +2010-02-18 Marcela Mašláňová + + * ChangeLog, configure.ac: New release + +2010-02-17 Marcela Mašláňová + + * src/crontab.c: CVE-2010-0424 -- crontab -e crontab file timestamp + race condition When run as "crontab -e", crontab creates a temporary file in /tmp, + copies the contents of an existing crontab to this file, and then + calls utime() on the temporary file name to set its mtime and atime + to 0, in order to check after editing whether or not the file has + been modified. Since the file is created with the user's euid, and + because utime is called on the file as root, an attacker can replace + the temporary file after it is created with a symlink to any file or + folder on disk, which will then have its atime and mtime set to 0. + This is certainly not a critical issue, but this action can be used + to deny service in many scenarios. For example, the cron daemon + checks the mtime of the crontab spool folder and its contents to + determine whether or not it needs to update its database of + cronjobs, and if these times are reset to 0, no new cronjobs will be + added. Other daemons relying on accurate timestamps may be similarly + affected. Finally, build tools such as make could be tricked into + not re-compiling source, based on an old timestamp. Thanks to: Dan + Rosenberg + +2010-02-16 Marcela Mašláňová + + * configure.ac: Dynamic shared libraries -laudit There is need to add -laudit into gcc options because now it's not + found automatically. Based on: + http://fedoraproject.org/wiki/Features/ChangeInImplicitDSOLinking + +2010-02-12 Marcela Mašláňová + + * man/anacrontab.5: Make man page more readable based on #564206. + +2010-01-25 Marcela Mašláňová + + * crond.sysconfig, cronie.init: CRON_VALIDATE_MAILRCPTS was removed, + because it was not used anyway. + +2010-01-13 Marcela Mašláňová + + * src/do_command.c, src/funcs.h, src/security.c: With NFS homes + can't be job executed, because root can't access this directory. + +2010-01-05 Will Woods + + * man/cron.8, src/do_command.c: Disable mailing output. Signed-off-by: Marcela Mašláňová + +2010-01-05 Will Woods + + * man/cron.8, src/cron.c, src/do_command.c, src/globals.h: Output + could be redirectered to syslog. Signed-off-by: Marcela Mašláňová + +2009-12-21 Marcela Mašláňová + + * src/security.c: Cron doesn't use environment values from + /etc/security/pam_env.conf. This was fixed by moving pam_setcred + into first part of authentication of pam. + +2009-11-27 Marcela Mašláňová + + * cronie.init: Initscript: if unprivileged user stop deamon, it + should return 4. + +2009-11-27 Marcela Mašláňová + + * cronie.init: Initscript: if unprivileged user restart deamon, it + should return 4. + +2009-11-05 Guido Trentalancia + + * src/security.c: This function will be probably removed from + libselinux, so it is added into source code here. Signed-off-by: Marcela Mašláňová + +2009-11-05 Marcela Mašláňová + + * pam/crond: One line was missing in pam authentication. rhbz#533189 + +2009-11-03 Marcela Mašláňová + + * Makefile.am, configure.ac: Autotools - Makefile includes + dailyjobs, configure has new version. + +2009-11-03 Marcela Mašláňová + + * ChangeLog: New release 1.4.3. + +2009-11-03 SATOH Fumiyasu + + * src/misc.c: Fix "warning: unused variable" if LOG_FILE is not + defined Signed-off-by: Marcela Mašláňová + +2009-11-03 SATOH Fumiyasu + + * src/crontab.c: Portability: Use swap_uids() instead of setreuid() + directly Signed-off-by: Marcela Mašláňová + +2009-11-03 SATOH Fumiyasu + + * src/externs.h: Portability: Solaris and AIX support saved UID/GID Signed-off-by: Marcela Mašláňová + +2009-11-03 SATOH Fumiyasu + + * configure.ac, src/externs.h: Portability: Check if fchown() and + fchgrp() exist by configure. Signed-off-by: Marcela Mašláňová + +2009-11-03 SATOH Fumiyasu + + * src/crontab.c: Security: Ignore $TMPDIR if ruid!=euid and/or + rgid!=egid A setuid/setgid process with GNU C library does NOT + inherit $TMPDIR from the parent process for security reason, but + this behavior is NOT standard feature. Signed-off-by: Marcela Mašláňová + +2009-11-03 SATOH Fumiyasu + + * configure.ac: Portability: pam_misc.so is the Linux-PAM specific + library Signed-off-by: Marcela Mašláňová + +2009-11-03 SATOH Fumiyasu + + * configure.ac, src/externs.h: Portability: Check if sys/fcntl.h + exists or not Signed-off-by: Marcela Mašláňová + +2009-11-03 Marcela Mašláňová + + * contrib/dailyjobs: Dailyjobs are here for users who don't like + anacron. 0hourly executes cron.hourly scripts and other will be + executed by dailyjobs. + +2009-10-19 HONDA Hirofumi + + * cronie.init: When parent crond is stopped and child crond + (executing program) is running,"service crond status" reports "crond + (pid XXX) is running...". Signed-off-by: Marcela Mašláňová + +2009-10-12 Marcela Mašláňová + + * ChangeLog, configure.ac: New release with few bugfixes. + +2009-09-25 Marcela Mašláňová + + * src/database.c: Symlinks were not followed. This is a limitation + of inotify API. rhbz#477100 + +2009-09-18 Tomas Mraz + + * src/do_command.c: Do not segfault if mailto does not pass safe_p + test. + +2009-09-16 Tomas Mraz + + * pam/crond: Use password-auth common PAM configuration. + +2009-08-17 Marcela Mašláňová + + * anacron/lock.c, anacron/readtab.c, man/anacrontab.5: Add daily, + weekly as a possibility of anacrontab configuration. + +2009-08-11 Marcela Mašláňová + + * contrib/anacrontab: Add anacrontab configuration file. + +2009-08-11 Marcela Mašláňová + + * man/anacron.8, man/anacrontab.5, man/cron.8, man/crontab.1, + man/regularly-jobs.5: Polish manual pages. + +2009-07-30 Marcela Mašláňová + + * Makefile.am, anacron/Makefile, configure.ac, man/Makefile.am, + man/anacron.8, man/anacrontab.5, man/cron.8, man/crontab.5: Revert + configuration file regularly-file back to anacrontab. + +2009-07-29 Marcela Mašláňová + + * ChangeLog, configure.ac: Start minor releases cronie-1.4.1. + +2009-07-29 Marcela Mašláňová + + * contrib/regularly-jobs: Revert configure file for anacrontab. + +2009-07-29 Rocco Iannacci + + * anacron/readtab.c: Segfault on ppc64 was caused by parsing random + delay from anacrontab. Signed-off-by: Marcela Mašláňová + +2009-07-21 Marcela Mašláňová + + * configure.ac: New release cronie1.4. + +2009-07-20 Marcela Mašláňová + + * ChangeLog, Makefile.am, anacron/Makefile.am, man/Makefile.am, + man/anacron.8, man/anacrontab.5, man/cron.8, man/crontab.1, + man/crontab.5, man/regularly-jobs.5: New option: enable-anacron in + configure which can set compilation with or without anacron part. + Also there were changes in manual pages. Updated ChangeLog. + +2009-07-17 Štěpán Kasal + + * .gitignore, Makefile.am, anacron/Makefile.am, configure.ac: Fix of + autotools stuff for anacron. Signed-off-by: Marcela Mašláňová + +2009-07-16 Marcela Mašláňová + + * Makefile.am, anacron/Makefile.am, configure.ac, man/Makefile.am: + Make anacron configurable. The option --enable-anacron in configure + can switch on/off compilation of this part of the package. + +2009-07-16 Marcela Mašláňová + + * anacron/Makefile.am: Put anacron binary into proper location. + +2009-07-16 Marcela Mašláňová + + * .gitignore, Makefile.am, anacron/anacron.8, anacron/anacrontab.5, + contrib/0anacron, contrib/0hourly, contrib/regularly-jobs, + man/Makefile.am, man/anacron.8, man/anacrontab.5, regularly-jobs: + Add configuration scripts into contrib. Cleaning/adding man pages + into Makefile/directories. + +2009-07-14 Marcela Mašláňová + + * COPYING.anacron, ChangeLog.anacron, README.anacron, + anacron/COPYING, anacron/ChangeLog, anacron/README, anacron/TODO, + anacron/anacron.apm, anacron/debian/0anacron.daily, + anacron/debian/0anacron.monthly, anacron/debian/0anacron.weekly, + anacron/debian/README.debian, anacron/debian/anacron.postinst, + anacron/debian/anacron.postrm, anacron/debian/anacrontab, + anacron/debian/apm.d, anacron/debian/changelog, + anacron/debian/compat, anacron/debian/control, + anacron/debian/copyright, anacron/debian/cron.d, + anacron/debian/dirs, anacron/debian/docs, anacron/debian/init.d, + anacron/debian/rules: Cleaning useless files. + +2009-07-14 Marcela Mašláňová + + * Makefile.am, anacron/Makefile, anacron/Makefile.am, + anacron/global.h, anacron/main.c, configure.ac, regularly-jobs: + Anacron makefile was rewritten according to the rest of autotools + makefiles in this project. + +2009-07-13 Marcela Mašláňová + + * anacron/global.h, anacron/log.c, anacron/main.c, + anacron/readtab.c: New options: random delay could be set from + anacrontab instead of sysconfig file, range of hours when should be + jobs started. + +2009-07-13 Marcela Mašláňová + + * anacron/anacron.8, anacron/anacrontab.5: Update manual pages. + +2009-07-13 Marcela Mašláňová + + * anacron/readtab.c: Fix error message for wrong spooldir. + +2009-07-13 Marcela Mašláňová + + * anacron/global.h, anacron/gregor.c, anacron/log.c, + anacron/matchrx.c, anacron/runjob.c: Memory leaks should be fixed. + Instead of log is used slog function. + +2009-07-13 Marcela Mašláňová + + * anacron/global.h, anacron/runjob.c: The temporary file has file + descriptors for input and output instead of one descriptor. + +2009-07-13 Marcela Mašláňová + + * anacron/runjob.c: Mail langinfo was fixed. + +2009-07-13 Marcela Mašláňová + + * anacron/ChangeLog, anacron/README, anacron/TODO, + anacron/anacron.8, anacron/anacron.apm, anacron/anacrontab.5, + anacron/debian/0anacron.daily, anacron/debian/0anacron.monthly, + anacron/debian/0anacron.weekly, anacron/debian/README.debian, + anacron/debian/anacron.postinst, anacron/debian/anacron.postrm, + anacron/debian/anacrontab, anacron/debian/apm.d, + anacron/debian/changelog, anacron/debian/compat, + anacron/debian/control, anacron/debian/copyright, + anacron/debian/cron.d, anacron/debian/dirs, anacron/debian/docs, + anacron/debian/init.d, anacron/debian/rules, anacron/global.h, + anacron/gregor.c, anacron/gregor.h, anacron/lock.c, anacron/log.c, + anacron/main.c, anacron/readtab.c, anacron/runjob.c: Minor debian + release anacron-2.3.1 which adds this features: -anacron runs jobs + twice in a 31 day month -add hostname to emails sent to admin -allow + user anacrontabs and some debian scripts for apm support. + +2009-07-13 Marcela Mašláňová + + * anacron/COPYING, anacron/ChangeLog, anacron/Makefile, + anacron/README, anacron/TODO, anacron/anacron.8, + anacron/anacrontab.5, anacron/global.h, anacron/gregor.c, + anacron/gregor.h, anacron/lock.c, anacron/log.c, anacron/main.c, + anacron/matchrx.c, anacron/matchrx.h, anacron/readtab.c, + anacron/runjob.c: Initial upload of anacron-2.3 which should be + optimized for better cooperation with cronie. However, cronie should + be working with or without anacron, which should be configurable. + +2009-06-19 Tomas Mraz + + * src/cron.c: Fix the disable inotify functionality. + +2009-06-19 Marcela Mašláňová + + * src/cron.c: Option -i for disabling inotify support. This option + was based on email by user who can't set up daemon when they have + mounted from NFS /var/spool/cron for a number of identical machines. + Inotify pass the test because it find the directory, but didn't + notice changes. + http://linux-nfs.org/pipermail/nfsv4/2007-November/007127.html + Thanks to: Alex Bame + +2009-06-05 Marcela Mašláňová + + * src/cron.c, src/do_command.c, src/globals.h: ValidateMailRcpts + removed for problems when could be enviromental settings exported + under wrong user. + +2009-05-29 Marcela Mašláňová + + * src/.indent.pro, src/cron.c, src/crontab.c, src/database.c, + src/do_command.c, src/entry.c, src/env.c, src/job.c, src/misc.c, + src/popen.c, src/security.c, src/user.c: Beautify the code with + indent. Thanks for .indent.pro to Martin Klozik. + +2009-05-29 Marcela Mašláňová + + * configure.ac, src/Makefile.am, src/misc.c: CRON_DIR became + obsolete. SPOOL_DIR is enough for work with user crontables. + +2009-05-22 Marcela Mašláňová + + * : commit b96c9b94317b31c6bb7e1335a4c7ba7a7dca7e4a Author: + Štěpán Kasal Date: Fri May 22 + 09:05:10 2009 +0200 + +2009-05-22 Štěpán Kasal + + * src/cron.c, src/database.c, src/pathnames.h: Rename RH_CRON_DIR to + SYS_CRON_DIR. + +2009-05-22 Štěpán Kasal + + * configure.ac: Drop uselles part of configure. + +2009-05-22 Marcela Mašláňová + + * man/cron.8, src/funcs.h, src/security.c: Remove unused function + cron_get_job_context. + +2009-05-20 Marcela Mašláňová + + * src/do_command.c, src/funcs.h, src/security.c: PAM logging was + incorrect bz#249870. The jobs is setuid to user before exec. Mail + runs under root permissions. + +2009-05-14 Vlad Glagolev + + * src/cron.c: @reboot alias check the return value. Signed-off-by: Marcela Mašláňová + +2009-05-14 Marcela Mašláňová + + * src/do_command.c: Save delimiters need more characters f.e. '_'. + Fix based on: http://bugs.gentoo.org/show_bug.cgi?id=197625 + +2009-05-13 Marcela Mašláňová + + * configure.ac, man/crontab.5, src/do_command.c: MAILFROM, if set, + will be used as the envelope sender address when cron mails the + output of commands in that crontab. The initial patch was written + by: Heath Caldwell + +2009-04-28 Marcela Mašláňová + + * src/globals.h: No need to initialize globals, which are set by + default to zero. + +2009-04-28 Stěpán Kasal + + * configure.ac: Fix with(out)-pam in configure. Signed-off-by: Marcela Mašláňová + +2009-04-27 Marcela Mašláňová + + * ChangeLog, configure.ac: Update ChangeLog for new release. + +2009-04-23 Marcela Mašláňová + + * src/cron.c: Inotify initialization left open file descriptors + which are leaking and annoying SElinux. This could be once fixed by + inotify_init1, but that's supported from kernel 2.6.27. + +2009-04-15 Willy Tarreua + + * src/cron.c, src/crontab.c, src/globals.h: I have noticed that with + cronie-1.2, my binaries have seen their \ size grow by 10x (from + 28kB to 290kB). After searching a bit, I found that the responsible + was the INIT() macro in globals.h \ which initializes huge strings + MailCmd and cron_default_mail_charset both of which are 128 kB. Due + to this initialization, the memory \ areas are stored for real in + the binary, resulting in larger sizes Signed-off-by: Marcela Mašláňová + +2009-02-12 Marcela Mašláňová + + * configure.ac: Cronie could be build as Position Independent + Executable when the configure is executed with --enable-pie. Erased + commented unused stuff from configure. + +2009-02-12 Marcela Mašláňová + + * crond.sysconfig: Fix typo in sysconfig. + +2008-12-23 Marcela Mašláňová + + * ChangeLog: After long time update ChangeLog file. + +2008-12-23 Tomas Mraz + + * man/cron.8, man/crontab.5: Update man pages to reflect the + reality. + +2008-12-22 Tomas Mraz + + * src/cron.c, src/database.c, src/funcs.h, src/structs.h: Fix + handling of HUP signal with inotify enabled. + +2008-12-02 Marcela Mašláňová + + * configure.ac: Configure can't be run without pam-devel libraries. + (rhbz#473893) + +2008-10-24 Marcela Mašláňová + + * cronie.init: Init script is according to SysVInitScript + guidelines. + +2008-08-11 Marcela Mašláňová + + * src/cron.c: Check user before job is run. + +2008-07-28 Marcela Mašláňová + + * src/pathnames.h: Clean hardwired pathnames. + +2008-07-28 Marcela Mašláňová + + * man/crontab.5, src/cron.c, src/pathnames.h: @reboot jobs should be + run only after reboot instead of every daemon's restart. Patch + inspired by debian. + +2008-06-26 Marcela Mašláňová + + * ChangeLog, configure.ac: Release 1.2 + +2008-06-26 Marcela Mašláňová + + * man/cron.8, man/crontab.1: Updated manuals - diffent typos and + inotify support mentioned. + +2008-06-26 SATOH Fumiyasu + + * configure.ac, src/misc.c: Portability: File locking by fcntl, + lockf or flock Signed-off-by: Marcela Mašláňová + +2008-06-26 SATOH Fumiyasu + + * src/macros.h, src/misc.c: Bugfix: PATH_MAX is defined in limits.h Signed-off-by: Marcela Mašláňová + +2008-06-26 SATOH Fumiyasu + + * configure.ac, src/popen.c: Portability: Check if sys/cdefs.h is + there on the platform Signed-off-by: Marcela Mašláňová + +2008-06-23 SATOH Fumiyasu + + * configure.ac, src/funcs.h, src/macros.h, src/misc.c: Portability: + Check for struct tm.tm_gmtoff by AC_CHECK_MEMBERS Signed-off-by: Marcela Mašláňová + +2008-06-26 SATOH Fumiyasu + + * src/funcs.h: Bugfix: Correct log_it() prototype Signed-off-by: Marcela Mašláňová + +2008-06-26 Marcela Mašláňová + + * src/database.c: Add missing endif. + +2008-06-23 Tomas Mraz + + * src/cron.c, src/database.c: Fix the inotify support. + +2008-06-23 Tomas Mraz + + * src/crontab.c, src/database.c, src/do_command.c, src/entry.c, + src/env.c, src/job.c, src/misc.c, src/popen.c, src/pw_dup.c, + src/user.c: Remove rcsid tags. + +2008-06-23 Tomas Mraz + + * src/cron.h, src/security.c: Move macros and static declarations + where they are needed. + +2008-06-23 Tomas Mraz + + * configure.ac: Add check for _GNU_SOURCE. + +2008-06-23 Tomas Mraz + + * src/cron.c: Fix wrong declaration of orig_tz. + +2008-06-23 Tomas Mraz + + * src/cron.c, src/crontab.c, src/database.c, src/do_command.c, + src/entry.c, src/funcs.h, src/misc.c, src/popen.c, src/security.c, + src/user.c: Unification of logging (syslog->log_it). + +2008-06-23 Tomas Mraz + + * src/security.c: Fixed call to setkeycreatecon. + +2008-05-30 Marcela Mašláňová + + * configure.ac: Update version also in configure. + +2008-05-30 Marcela Mašláňová + + * ChangeLog: cronie-1.1 released. Updated Changelog. + +2008-05-30 Marcela Mašláňová + + * cronie.init: Init script die faster, if the sysconfig script is + missing. Patch from scop. + +2008-05-29 Marcela Mašláňová + + * src/database.c: Function instead of macro. Code cleaning. + +2008-05-27 Marcela + + * man/crontab.1: Man: crontab.1 TMP directory could be set in + eviroment instead of /tmp. + +2008-05-27 Marcela + + * src/security.c: Keyring will be created after restart of computer + and services won't be dying on selinux denial. The reason for this + change was pam-devels update. + +2008-05-27 Marcela + + * src/funcs.h, src/security.c: Remove unused function. + +2008-05-27 Marcela + + * src/cron.c, src/database.c: Permission of crontabs are checked in + case we AREN'T using -p option. + +2008-03-14 Marcela Mašláňová + + * src/cron.c: Better testing, when we are closing watches. + +2008-03-14 Marcela Mašláňová + + * configure.ac, src/cron.c, src/cron.h, src/crontab.c, + src/database.c, src/externs.h, src/funcs.h, src/structs.h: Rewrited + inotify support. + +2008-01-31 Marcela Mašláňová + + * src/security.c, src/user.c: Using get_default_context() for name = + NULL instead of getseuserbyname because files created in + /var/spool/cron hadn't wrong context. rhbz#426704 + +2008-01-31 Marcela Mašláňová + + * .gitignore, Makefile.am, crond.pam, man/bitstring.3, man/cron.8, + man/crontab.1, man/crontab.5, pam/crond: Stepan Kasal: create pam's + own directory for pam configure. Create correct man pages in man + directory. Add .gitignore file. + +2008-01-30 Marcela Mašláňová + + * Makefile.am, config.h, config.h.in, configure.ac, + man/Makefile.am, man/bitstring.3.in, man/cron.8.in, man/crond.8.in, + man/crontab.1.in, man/crontab.5.in, src/.deps/cron.Po, + src/.deps/crontab.Po, src/.deps/database.Po, src/.deps/debug.Po, + src/.deps/do_command.Po, src/.deps/entry.Po, src/.deps/env.Po, + src/.deps/job.Po, src/.deps/misc.Po, src/.deps/popen.Po, + src/.deps/pw_dup.Po, src/.deps/security.Po, src/.deps/user.Po, + src/Makefile.am, src/cron.c, src/crontab.c, src/database.c, + src/do_command.c, src/entry.c, src/env.c, src/job.c, src/misc.c, + src/popen.c, src/pw_dup.c, src/security.c, src/user.c, stamp-h1: + Added patch from Stepan Kasal, which fixed all autotools issues. + Also the pam's configure file is now installed directly into correct + path, if it's configure runned with pam. + +2008-01-17 Marcela Mašláňová + + * CHANGES, COPYING, ChangeLog, INSTALL, LICENSE, Makefile, + Makefile.am, Makefile.in, NEWS, README, aclocal.m4, + autom4te.cache/output.0, autom4te.cache/output.1, + autom4te.cache/requests, autom4te.cache/traces.0, + autom4te.cache/traces.1, config.guess, config.h, config.log, + config.status, config.sub, configure, configure.ac, cronie.init, + depcomp, install-sh, man/Makefile, man/Makefile.am, + man/Makefile.in, man/bitstring.3, man/cron.8, man/crond.8, + man/crontab.1, man/crontab.5, missing, src/Makefile, + src/Makefile.am, src/Makefile.in, vixie-cron.init: Cleaning git from + unnecessary files, which were generated from autotools. Add files or + rename according to autotools custom practice. There were also alternation of configure and makefiles. + +2008-01-09 Marcela Maslanova + + * Makefile, config.h, config.log, config.status, man/Makefile, + man/bitstring.3, man/cron.8, man/crontab.1, man/crontab.5, + src/.deps/cron.Po, src/.deps/crontab.Po, src/.deps/database.Po, + src/.deps/debug.Po, src/.deps/do_command.Po, src/.deps/entry.Po, + src/.deps/env.Po, src/.deps/job.Po, src/.deps/misc.Po, + src/.deps/popen.Po, src/.deps/pw_dup.Po, src/.deps/security.Po, + src/.deps/user.Po, src/Makefile: Add missing files generated after + running ./configure + +2008-01-09 Marcela Maslanova + + * CHANGES: In CHANGES could be found git-log with last changes. + +2008-01-09 Marcela Maslanova + + * Makefile.in, aclocal.m4, autom4te.cache/output.0, + autom4te.cache/output.1, autom4te.cache/requests, + autom4te.cache/traces.0, autom4te.cache/traces.1, config.guess, + config.sub, configure, depcomp, install-sh, man/Makefile.in, + missing, src/Makefile.in: Now really add all files generated by + autotools. + +2008-01-09 Marcela Maslanova + + * config.h.in: Add binary configure. + +2008-01-03 Marcela Maslanova + + * vixie-cron.spec: Use changelog instead of spec. + +2007-11-30 Marcela Maslanova + + * configure.ac: Don't set up MAIL_DATE, because if the user doesn't + use sendmail, then he has incorrect time format. + +2007-11-19 Marcela Maslanova + + * vixie-cron.spec: Upload spec file with fixed bcond macro. + +2007-11-12 Marcela Maslanova + + * man/cron.8.in, man/crond.8.in, man/crontab.5.in: Man pages are + updated for time zone and some small changes. + +2007-11-12 Marcela Maslanova + + * src/cron.c, src/misc.c: Time zones are now supported. Setting in + cron table CRON_TZ=SomeTimeZone does jobs in this time zone. There + was problem with syslog, because it print local time into log only, + when I remove ifdef parts from misc.c. With ifdef parts print out + the time of 'highest' time zone, which could be bug of rsyslog. + +2007-11-12 Marcela Maslanova + + * configure.ac: In configure was incorrect path for sendmail. The + error occured only when sendmail wasn't set up like default MTA. + +2007-11-05 Marcela Maslanova + + * src/security.c: Opening credentials is really needed for cron + jobs, but not for crontab. Crontab doesn't use credentials from file + security.c so we can leave it as it was. + +2007-10-30 Marcela Maslanova + + * configure.ac: Change version of cron in configure. + +2007-10-29 Marcela Maslanova + + * vixie-cron.spec: Update spec file - new bcond macro. + +2007-10-29 Marcela Maslanova + + * src/popen.c: Some cron jobs failed without error message. If the + job had too "big" output and no mail client set, then only a part + from job was done. The reason was pipe, which has restricted size. + rh bugzilla #247228 + +2007-10-03 Marcela Maslanova + + * LICENSE: The file LICENSE is used instead of COPYING. + +2007-10-03 Marcela Maslanova + + * vixie-cron.spec: Add spec file from rpm package, because there is + changelog. + +2007-10-03 Marcela Maslanova + + * COPYING, ChangeLog, INSTALL, Makefile.am, NEWS, README, + configure.ac, doc/CHANGES, doc/CONVERSION, doc/FEATURES, + doc/INSTALL, doc/MAIL, doc/Makefile.am, doc/README, doc/THANKS, + src/Makefile.am: Deleting and modyfing files with license etc. The + compilation with selinux and audit is optional. + +2007-10-02 Marcela Maslanova + + * src/user.c: The jobs in RH_CROND_DIR weren't syntactically + checked. Also SYSCRON wasn't checked. The problem is reported into + log. + +2007-08-28 Marcela Mašláňová + + * vixie-cron.init: Fix reading arguments from configure file. + +2007-08-27 Tomas Janousek + + * src/security.c: Commented out cron_open_pam_session, it's unused. Signed-off-by: Tomas Janousek + +2007-08-27 Tomas Janousek + + * src/security.c: Protect the call to setexeccon with WITH_SELINUX. Signed-off-by: Tomas Janousek + +2007-08-27 Tomas Janousek + + * src/misc.c: Compile fix in misc.c. (I have no f*cking idea why do I do this change now...) Signed-off-by: Tomas Janousek + +2007-08-27 Tomas Janousek + + * src/crontab.c, src/funcs.h: Kill a few warnings. Signed-off-by: Tomas Janousek + +2007-08-27 Tomas Janousek + + * config.h.in, configure.ac: Fix the MAILARG and MAILFMT definitions + and regenerate config.h.in. Signed-off-by: Tomas Janousek + +2007-08-27 Tomas Janousek + + * configure.ac, src/security.c: Move #define _GNU_SOURCE from + security.c to CFLAGS. (it's needed for HAVE_SELINUX as well) Signed-off-by: Tomas Janousek + +2007-08-27 Tomas Janousek + + * src/pathnames.h: Don't define SYSLOG in pathnames.h. It's in configure.ac and gets defined in config.h. Signed-off-by: Tomas Janousek + +2007-08-27 Tomas Janousek + + * configure.ac: CRON_GROUP shall not be defined at all. (also filled in a sensible default in case someone enables it again) Signed-off-by: Tomas Janousek + +2007-08-27 Tomas Janousek + + * src/Makefile.am: Added -laudit to LDADD. Signed-off-by: Tomas Janousek + +2007-08-27 mmaslano + + * configure.ac, src/cron.c, src/cron.h: Version of cron is used from + configure. + +2007-08-27 mmaslano + + * configure.ac: Correct setting of path to mail program. + +2007-08-27 mmaslano + + * src/config.h, src/cron.h, src/pw_dup.c: Use config.h generated by + autotools. + +2007-08-27 mmaslano + + * src/security.c: Added missing #ifdef WITH_PAM. + +2007-08-27 mmaslano + + * src/funcs.h: Correct definition of cron_popen in .h file. + +2007-08-27 Tomas Janousek + + * src/crontab.c: Added missing #ifdef WITH_PAM. Signed-off-by: Tomas Janousek + +2007-08-27 Tomas Janousek + + * src/funcs.h, src/security.c: Make + cron_restore_default_security_context return void. Signed-off-by: Tomas Janousek + +2007-08-27 Tomas Janousek + + * src/misc.c: Revert "System table wasn't sometimes checked for + changes." This reverts commit b18c0c9a01bef691c7b696709cd2f9736ba98a82. Signed-off-by: Tomas Janousek + +2007-08-24 mmaslano + + * src/popen.c: rhbz#247228 cron jobs fail semi-randomly if sendmail + (or other mail) isn't set. The jobs aren't "sometimes" run, because output, which + has to be sent isn't set and the stdin pipe, which is used haven't + enough capacity. The problem is at least reported in log. + +2007-08-24 mmaslano + + * man/crond.8.in, man/crontab.1.in, man/crontab.5.in: Add crond.8 + (the same as cron.8) manual page and other man pages were updated. + +2007-08-24 mmaslano + + * src/funcs.h: Change definiton from popen.c in header file. + +2007-08-24 mmaslano + + * src/bitstring.h, src/popen.c: For the same license for whole cron, + I've removed two files and found almost the same with correct license. + +2007-08-24 mmaslano + + * AUTHORS, CHANGES, CONVERSION, COPYING, ChangeLog, FEATURES, + INSTALL, MAIL, Makefile, Makefile.am, NEWS, README, THANKS, + bitstring.3, bitstring.h, config.h, config.h.in, configure.ac, + cron.8, cron.c, cron.h, crond.sysconfig, crontab.1, crontab.5, + crontab.c, database.c, do_command.c, doc/CHANGES, doc/CONVERSION, + doc/FEATURES, doc/INSTALL, doc/MAIL, doc/Makefile.am, doc/README, + doc/THANKS, entry.c, env.c, externs.h, funcs.h, globals.h, job.c, + macros.h, man/Makefile.am, man/bitstring.3.in, man/cron.8.in, + man/crontab.1.in, man/crontab.5.in, misc.c, pathnames.h, popen.c, + putman.sh, pw_dup.c, security.c, src/Makefile.am, src/bitstring.h, + src/config.h, src/cron.c, src/cron.h, src/crontab.c, + src/database.c, src/do_command.c, src/entry.c, src/env.c, + src/externs.h, src/funcs.h, src/globals.h, src/job.c, src/macros.h, + src/misc.c, src/pathnames.h, src/popen.c, src/pw_dup.c, + src/security.c, src/structs.h, src/user.c, stamp-h1, structs.h, + user.c, vixie-cron.init: Cron source was ready for merge with + patches. After the merge I used autotools (files were copied to new dirs and configure.ac and + Makefile.am were written). + +2007-08-24 mmaslano + + * cron.h, crontab.c, do_command.c, security.c: Pam authentication + wasn't used wise. User's crontab didn't use pam and functions, which were for pam opening etc. were + incorrect (wrong credetials). + +2007-08-24 mmaslano + + * crontab.1, security.c: Checking homedir is last, because we need + at first set up gid and uid. + +2007-08-24 mmaslano + + * crontab.c: It's possible to use your own tmp dir. Before was /tmp + hardwired. + +2007-08-24 mmaslano + + * crontab.5: System table in manual page is mentioned. + +2007-08-24 mmaslano + + * database.c: Hard links on system table break doing jobs. + +2007-08-24 mmaslano + + * misc.c: System table wasn't sometimes checked for changes. + +2007-08-24 mmaslano + + * security.c: Audit: new auditing message is print, when the user + isn't allowed to use mls range. Job wasn't runned without warning message. + +2007-08-24 mmaslano + + * misc.c: Because there was typo (- instead of +) jobs wasn't runned + after new year. + +2007-08-24 mmaslano + + * cron.8, crontab.1, crontab.5: Errors in manual was fixed and mls + range was mentioned. + +2007-08-17 mmaslano + + * cron.c, database.c: Force reload of database when SIGGUP is + received. max_mtime uses dir_name instead of SPOOL_DIR now. (which + caused a bug preventing correct detection of changes in + RH_CROND_DIR) (comment updated by ) + +2007-08-17 mmaslano + + * crontab.c, do_command.c, funcs.h, security.c: Selinux ranges: for + every selinux operation are now checked the ranges of user. Now is set not only context for user, but even + ranges(enabled selinux or selinux in mls mode). + +2007-08-17 mmaslano + + * crontab.c: It's possible to change file without changing mtime of + file. So we're stat'ing files for the changes of files. The detection of + not_a_crontab files was added: files started with dot aren't + crontabs etc. + +2007-08-17 mmaslano + + * crond.pam: pam.limits.so was substitued by system-auth (pam + progress). + +2007-08-17 mmaslano + + * security.c: Part with_selinux now include even the testing of + linux context. + +2007-08-17 mmaslano + + * cron.c: Loading database before reaping the child take up time of running the jobs. + +2007-08-17 mmaslano + + * do_command.c: The Auto-Submitted header is defined (and suggested + by) RFC3834. Added into mail header: 'Auto-Submitted: auto-generated' + +2007-08-17 mmaslano + + * cron.8: Fix typo in man pages. + +2007-08-17 mmaslano + + * database.c: It's possible to change file without changing mtime of + file. So we're stat'ing files for the changes of files. The detection of + not_a_crontab files was added: files started with dot aren't + crontabs etc. + +2007-08-17 mmaslano + + * crontab.1, crontab.5, crontab.c, funcs.h, security.c: Selinux: + option -s added. Header from crontab was removed and now is print into crontab the SELINUX_ROLE_TYPE which specify the + permission of user. With mls could one user run some jobs with + different roles and security level. + +2007-08-17 mmaslano + + * Makefile, security.c, structs.h: Into with_selinux and with_pam + part was added variables used only there. In Makefile are libs set by variables. + +2007-08-17 mmaslano + + * cron.c, crontab.5, do_command.c, externs.h, globals.h: Now is + possible to use different character encodings for mailed cron job + output by setting the CONTENT_TYPE and CONTENT_TRANSFER_ENCODING variables in + crontabs, to the correct values of the mail headers of those names. + +2007-08-17 mmaslano + + * crond.pam: Module pam_limit.so was added to default configuration. + +2007-08-17 mmaslano + + * security.c: Fixing "security": minutely job are made realy only + one time per minute. If the job is delayed into next minute, then it's skipped + for this minute. + +2007-08-17 mmaslano + + * Makefile, crontab.c, do_command.c, funcs.h, popen.c, security.c, + user.c: The security.c file was filled with selinux and pam often used functions, which were removed from other files. + +2007-08-17 mmaslano + + * cron.8, cron.c, do_command.c, globals.h: Option -m was added: it's + possible to use something else then sendmail. + +2007-08-17 mmaslano + + * Makefile, misc.c: Now is cron with audit. Complaining about + denying users. + +2007-08-17 mmaslano + + * crontab.c, do_command.c, env.c, misc.c, pw_dup.c: The return value + were added because of too many warning messages from compiler. Also the variables were initialized. + +2007-08-17 mmaslano + + * do_command.c: 'Build enviroment' is set in pam section for better + security. + +2007-08-17 mmaslano + + * config.h: Comments were changed. + +2007-08-17 mmaslano + + * user.c: Selinux: Instead of getting context and then the username + is used function getuserbyname. + +2007-08-17 mmaslano + + * crontab.c: Too many flags was set for lstating crontab. Time of + change is checked and uids for reading new crontab are ok. + +2007-08-17 mmaslano + + * database.c, funcs.h, structs.h, user.c: List corruption when items + are removed from /etc/cron.d. Variable tabname is filled with file or NULL and checked when + crontab is changed. + +2007-08-17 mmaslano + + * crond.pam: Crond.pam was changed according to pam modules. The pam_limits.so could be used. + +2007-08-17 mmaslano + + * crontab.c: lstat instead of stat can stat even symlink itself, not + the file that it refers to. + +2007-08-17 mmaslano + + * macros.h: Redefined limits of macros. + +2007-08-17 mmaslano + + * do_command.c: If fork fails, pam has to close session. + +2007-08-17 mmaslano + + * crontab.c: fix of bug rhbz#154065: crontab should not use + waitpid(...,WUNTRACED) and stop itself if its child is stopped + +2007-08-17 mmaslano + + * do_command.c: PAM hadn't closed session. + +2007-08-17 mmaslano + + * do_command.c, user.c: Selinux doesn't segfault, because of: + permissive mode returns 0 and selinux enabled is also check context. + +2007-08-17 mmaslano + + * cron.c, do_command.c, globals.h: Cron validate the recipient only + when CRON_VALIDATE_MAILRCPTS isn't null. Validating of email + recipient is default off. + +2007-08-17 mmaslano + + * do_command.c: Set item in pam - "cron". + +2007-08-17 mmaslano + + * do_command.c, misc.c, pw_dup.c: Initialize some important + variables. + +2007-08-17 mmaslano + + * cron.8, cron.c, database.c, globals.h: Add -p option for crontab. Without the -p option /etc/crontab must not be writable by any user + other than root, no crontab files may be links, or linked to by any + other file, and no crontab files may be executable, or be writable + by any user other than their owner + +2007-08-17 mmaslano + + * Makefile, crontab.c: Fix for ppc: int ch='\0' is initialized. + +2007-08-17 mmaslano + + * crontab.c: Don't read the header of crontab. + +2007-08-17 mmaslano + + * misc.c: For setegid are used saved gid instead of getgid(). Now are swaped back the correct gid. + +2007-08-17 mmaslano + + * misc.c: Allow root's crontab (check with getuid). + +2007-08-17 mmaslano + + * crontab.c: According to changes in selinux + selinux_check_passwd_access is now enough for examinitaion of user's password. + +2007-08-17 mmaslano + + * user.c: Context in selinux is now correctly undone. + +2007-08-17 mmaslano + + * cron.8, crontab.5, database.c: /etc/crontab is now writable only + by root. No links on this file are allowed. + +2007-08-17 mmaslano + + * crontab.1, crontab.c: Add -i option to crontab, which print prompt + before removing crontab. + +2007-08-17 mmaslano + + * crontab.5: Nicknames were mentioned in man pages (@yearly, + @hourly, etc). + +2007-08-17 mmaslano + + * crontab.c: Crontab is stat instead of fstat and crontab is + reopened for reading new stdin. This change should: Allowed editors + such as 'gedit' which do not modify original file, but which + rename(2) a temp file to original, to be used by crontab -e (bug + 129170). + +2007-08-17 mmaslano + + * do_command.c: In lower version of pam was re-open log needed + (rhel-4 and lower). + +2007-08-17 mmaslano + + * crond.pam: Change in pam configuration file (auth sufficient is + now used). + +2007-08-17 mmaslano + + * crontab.c, user.c: Selinux for crontab: checking users with + selinux. User is fixed from char to const char. + +2007-08-17 mmaslano + + * database.c: Hardwired 'system' wasn't needed. + +2007-08-17 mmaslano + + * Makefile, crond.pam: Add file crond.pam, which has rules for pam + sessions. In Makefile is now crond.pam installed. + +2007-08-17 mmaslano + + * Makefile: Now we compile with pam flags and libs. + +2007-08-17 mmaslano + + * cron.8, cron.h, do_command.c: PAM support was added: open sessions + and set credentials for users. + +2007-08-17 mmaslano + + * Makefile: In Makefile are now LIB = -lselinux and -DWITH_SELINUX flags. + +2007-08-17 mmaslano + + * FEATURES, cron.8, crontab.1, crontab.5: Features was added into + manual. + +2007-08-17 mmaslano + + * crontab.c: Remove header in user's crontab. + +2007-08-17 mmaslano + + * misc.c: Logs now inform about creating crontabs for users, which + aren't allowed to use crontab. It's for user in cron.{allow,deny} + +2007-08-17 mmaslano + + * crontab.c: Change behavior to allow crontab to take stdin with no + '-'. + +2007-08-17 mmaslano + + * database.c: RH_CROND was added in programme - stating directory. + In RH_CROND are system crontables. + +2007-08-17 mmaslano + + * misc.c: Use snprintf instead of sprintf. + +2007-08-17 mmaslano + + * do_command.c, popen.c: Use fork instead of vfork. Add signal: Our grandparent is watching for our parent's death + by catching SIGCHLD. Meanwhile, our parent will use wait explicitly and so has disabled SIGCHLD. So now it's time to reset SIGCHLD handling. + +2007-08-17 mmaslano + + * cron.h, database.c, do_command.c, funcs.h, structs.h, user.c: Add first selinux patch. Loading users through selinux scontext. + +2007-08-17 mmaslano + + * config.h: Redefine sendmail options. + +2007-08-17 mmaslano + + * pathnames.h: Change path names for redhat/fedora system. + +2007-08-17 mmaslano + + * Makefile: Changes in Makefile: flags, -pie option, permission and + installation paths for redhat/fedora system. + +2007-08-17 mmaslano + + * Source files of vixie-cron-4.1. + diff --git a/ChangeLog.anacron b/ChangeLog.anacron new file mode 100644 index 0000000..3ec49ec --- /dev/null +++ b/ChangeLog.anacron @@ -0,0 +1,39 @@ + 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 + + + Changes in Anacron 2.1/2.2 + -------------------------- +* Sean 'Shaleh' Perry is now maintainer +* if timestamp is from the future, re-run job +* ansi cleanup / code cleaning + + + Changes in Anacron 2.0.1 + ------------------------ +* Minor cosmetic changes to log messages. +* Jobs are now started with "/" as their working directory. This is + more compatible with older Anacron versions, avoids annoying errors on + some systems, and generally seems to make more sense. + + + Summary of major changes in Anacron 2.0 + --------------------------------------- +* Complete rewrite in C. Should be backwards compatible with existing + Anacron installations. +* First release as a "generic" Linux package (was a Debian package). +* No longer needs special lock-files. Locking is done on the timestamp + files. +* Sends log messages to syslogd. There's no log file now. +* Output of jobs, if any, is mailed to the user. +* Added command line options: -s -f -n -d -q -u -V -h. See the manpage. +* Specific jobs can now be selected on the command line. +* Added SIGUSR1 handling, to cleanly stop execution. +* Jobs will now be started with their current directory set to the home + of the user running Anacron (usually root). diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..9f40603 --- /dev/null +++ b/INSTALL @@ -0,0 +1,22 @@ +Basic Installation +================== + +In the vixie-cron directory run: +autoreconf +These commands create from configure.ac executable ./configure + +Then you can start installation: +make +make install + +The executable files will be installed in /usr/local/* + +Options +======= +In the default package are used configure options: +--with-pam +--with-selinux +--with-audit + + + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..05c541a --- /dev/null +++ b/Makefile.am @@ -0,0 +1,13 @@ +SUBDIRS = src man +if ANACRON +SUBDIRS += anacron +endif + +if PAM +pamdir = $(sysconfdir)/pam.d +dist_pam_DATA = pam/crond +endif + +EXTRA_DIST = cronie.init crond.sysconfig contrib/anacrontab \ + contrib/0anacron contrib/0hourly \ + contrib/dailyjobs diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..c298ffe --- /dev/null +++ b/README @@ -0,0 +1,11 @@ +17. January 2008 mmaslano (at) redhat (dot) com +Rename the fork on cronie. The source code could be found here: +http://mmaslano.fedorapeople.org/cronie/ or git archive here: +git://git.fedorahosted.org/git/cronie.git + +3. October 2007 mmaslano (at) redhat (dot) com +This is a clone of 'original' vixie-cron. It was used in Red Hat|Fedora +system and patched for a long time. Now was made clone tagged with +version 4.2. +Changes are mainly in git commit messages, some older changes could be +found in spec changelog (contrib/vixie-cron.spec). diff --git a/README.anacron b/README.anacron new file mode 100644 index 0000000..e1d5411 --- /dev/null +++ b/README.anacron @@ -0,0 +1,142 @@ + + What is Anacron ? + ----------------- + + 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 +commands are run at the specified intervals as closely as +machine-uptime permits. + + Every time Anacron is run, it reads a configuration file that +specifies the jobs Anacron controls, and their periods in days. If a +job wasn't executed in the last n days, where n is the period of that +job, Anacron executes it. Anacron then records the date in a special +timestamp file that it keeps for each job, so it can know when to run +it again. When all the executed commands terminate, Anacron exits. + + It is recommended to run Anacron from the system boot-scripts. +This way the jobs "whose time has come" will be run shortly after the +machine boots. A delay can be specified for each job so that the +machine isn't overloaded at boot time. + + In addition to running Anacron from the boot-scripts, it is also +recommended to schedule it as a daily cron-job (usually at an early +morning hour), so that if the machine is kept running for a night, +jobs for the next day will still be executed. + + + Why this may be useful ? + ------------------------ + + Most Unix-like systems have daily, weekly and monthly scripts that +take care of various "housekeeping chores" such as log-rotation, +updating the "locate" and "man" databases, etc. Daily scripts are +usually scheduled as cron-jobs to execute around 1-7 AM. Weekly +scripts are scheduled to run on Sundays. On machines that are turned +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 a special target called @monthly. + + + What Anacron is not ? + --------------------- + + Anacron is not an attempt to make cron redundant. It cannot +currently be used to schedule commands at intervals smaller than days. +It also does not guarantee that the commands will be executed at any +specific day or hour. + + It isn't a full-time daemon. It has to be executed from boot +scripts, from cron-jobs, or explicitly. + + + For more details, see the anacron(8) manpage. + + + Requirements + ------------ + + - A Linux system. (maybe other *NIX systems) + - A functioning syslog daemon. + - A functioning /usr/lib/sendmail command. (all MTAs should have + that). + + + Compilation and Installation + ---------------------------- + + - Untar the source package. + + - Check the Makefile. Edit as required. + + - Check the top of "global.h". You may want to change the syslog + facility and priorities, and the path to your MTA's sendmail + compatible command (/usr/lib/sendmail). + + - cd to the directory. + + - Type "make". + You can safely ignore warnings of the form: "*.d: No such file or + directory" + + - Become root. Type "make install". + + + Setup + ----- + +1. Locate your system's daily, weekly and monthly cron-jobs. + See your cron documentation for more details. + +2. Decide which of these jobs should be controlled by Anacron. + Remember that Anacron does not guarantee execution at any specific + day of the month, day of the week, or time of day. Jobs for which + the timing is critical should probably not be controlled by + Anacron. + +3. Comment these jobs out of their crontab files. (You may have to + use the "crontab" command for this. See the cron documentation.) + +4. Put them in /etc/anacrontab. Note that the format is not the same + as the crontab entries. See the anacrontab(5) manpage. Here's an + example from a typical Debian system: + +-----Cut +# /etc/anacrontab example +SHELL=/bin/sh +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 +@monthly 15 cron.monthly run-parts /etc/cron.monthly +-----Cut + +5. Put the command "anacron -s" somewhere in your boot-scripts. + Make sure that syslogd is started before this command. + +6. Schedule the command "anacron -s" as a daily cron-job (preferably + at some early morning hour). This will make sure that jobs are run + when the systems is left running for a night. + +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. + + + Credits + ------- + +Anacron was originally conceived and implemented by Christian Schwarz +. + +The current implementation is a complete rewrite by Itai Tzur +. + +Current code base maintained by Sean 'Shaleh' Perry . diff --git a/anacron/Makefile.am b/anacron/Makefile.am new file mode 100644 index 0000000..1a2ab5e --- /dev/null +++ b/anacron/Makefile.am @@ -0,0 +1,28 @@ +# Makefile.am - two binaries crond and crontab +sbin_PROGRAMS = anacron + +anacron_SOURCES = \ + gregor.c lock.c log.c main.c matchrx.c readtab.c runjob.c \ + $(common_src) +common_src = global.h gregor.h matchrx.h +common_nodist = anacron-paths.h +nodist_anacron_SOURCES = $(common_nodist) +BUILT_SOURCES = $(common_nodist) + + +LDADD = $(LIBSELINUX) $(LIBPAM) $(LIBAUDIT) + +# This header contains all the paths. +# If they are configurable, they are declared in configure script. +# Depends on this Makefile, because it uses make variables. +anacron-paths.h: Makefile + @echo 'creating $@' + @sed >$@ 's/ *\\$$//' <<\END #\ + /* This file has been automatically generated. Do not edit. */ \ + \ + #ifndef _ANACRON_PATHS_H_ \ + #define _ANACRON_PATHS_H_ \ + #define ANACRON_SPOOL_DIR "$(ANACRON_SPOOL_DIR)" \ + #define ANACRONTAB "$(ANACRONTAB)" \ + #endif /* _ANACRON_PATHS_H_ */ \ + END diff --git a/anacron/global.h b/anacron/global.h new file mode 100644 index 0000000..0fe3f9b --- /dev/null +++ b/anacron/global.h @@ -0,0 +1,159 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + Copyright (C) 2004 Pascal Hakim + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + +#ifndef _ANACRON_GLOBAL_H +#define _ANACRON_GLOBAL_H + +/* Syslog facility and priorities messages will be logged to (see syslog(3)). + * If you change these, please update the man page. */ +#define SYSLOG_FACILITY LOG_CRON +#define EXPLAIN_LEVEL LOG_NOTICE /* informational messages */ +#define COMPLAIN_LEVEL LOG_ERR /* error messages */ +#define DEBUG_LEVEL LOG_DEBUG /* only used when DEBUG is defined */ + +/* Mail interface. (All MTAs should supply this command) */ +#define SENDMAIL "/usr/sbin/sendmail" + +/* End of user-configurable section */ + + +#define FAILURE_EXIT 1 +#define MAX_MSG 150 + +#include +#include +#include "anacron-paths.h" + +/* Some declarations */ + +struct env_rec1 { + char *assign; + + struct env_rec1 *next; +}; +typedef struct env_rec1 env_rec; + +struct job_rec1 { + int period; + int named_period; + int delay; + char *ident; + char *command; + char *mailto; + + int tab_line; + int arg_num; + int timestamp_fd; + int input_fd; + int output_fd; + int mail_header_size; + pid_t job_pid; + pid_t mailer_pid; + int drop_job; + + struct job_rec1 *next; + env_rec *prev_env_rec; +}; +typedef struct job_rec1 job_rec; + +/* Global variables */ + +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,testing_only; +extern int day_now; +extern int year,month,day_of_month; +extern int in_background; + +extern job_rec *first_job_rec; +extern env_rec *first_env_rec; + +extern char **args; +extern int nargs; + +extern int njobs; +extern job_rec **job_array; + +extern int running_jobs,running_mailers; + +extern int complaints; + +extern time_t start_sec; + +/* time ranges for START_HOURS_RANGE */ +extern int range_start; +extern int range_stop; + +/* Function prototypes */ + +/* main.c */ +int xopen(int fd, const char *file_name, int flags); +void xclose(int fd); +pid_t xfork(void); + +#ifdef __GNUC__ +#define PRINTF_FORMAT(n, m) \ + __attribute__ ((format (printf, n, m))) +#else +#define PRINTF_FORMAT(n, m) +#endif + +/* log.c */ +void explain(const char *fmt, ...)PRINTF_FORMAT(1,2); +void explain_e(const char *fmt, ...)PRINTF_FORMAT(1,2); +void complain(const char *fmt, ...)PRINTF_FORMAT(1,2); +void complain_e(const char *fmt, ...)PRINTF_FORMAT(1,2); +void die(const char *fmt, ...)PRINTF_FORMAT(1,2); +void die_e(const char *fmt, ...)PRINTF_FORMAT(1,2); +void xdebug(const char *fmt, ...)PRINTF_FORMAT(1,2); +void xdebug_e(const char *fmt, ...)PRINTF_FORMAT(1,2); +void xcloselog(void); + +#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 */ + +/* readtab.c */ +void read_tab(int cwd); +void arrange_jobs(void); + +/* lock.c */ +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 */ +void tend_children(); +void launch_job(job_rec *jr); + +#endif diff --git a/anacron/gregor.c b/anacron/gregor.c new file mode 100644 index 0000000..91e4173 --- /dev/null +++ b/anacron/gregor.c @@ -0,0 +1,181 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + Copyright (C) 2004 Pascal Hakim + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +#include +#include +#include "gregor.h" + +static const int +days_in_month[] = { + 31, /* Jan */ + 28, /* Feb (non-leap) */ + 31, /* Mar */ + 30, /* Apr */ + 31, /* May */ + 30, /* Jun */ + 31, /* Jul */ + 31, /* Aug */ + 30, /* Sep */ + 31, /* Oct */ + 30, /* Nov */ + 31 /* Dec */ +}; + +static int leap(int year); + +int +day_num(int year, int month, int day) +/* Return the "day number" of the date year-month-day according to the + * "proleptic Gregorian calendar". + * If the given date is invalid, return -1. + * + * Here, "day number" is defined as the number of days since December 31, + * 1 B.C. (Gregorian). (January 1, 1 A.D. is day number 1 etc...) + * + * The Gregorian calendar was instituted by Pope Gregory XIII in 1582, + * and has gradually spread to become the international standard calendar. + * The proleptic Gregorian calendar is formed by projecting the date system + * of the Gregorian calendar to dates before its adoption. + * + * For more details, see: + * http://astro.nmsu.edu/~lhuber/leaphist.html + * http://www.magnet.ch/serendipity/hermetic/cal_stud/cal_art.htm + * and your local library. + */ +{ + int dn; + int i; + int isleap; /* save three calls to leap() */ + + /* Some validity checks */ + + /* we don't deal with B.C. years here */ + if (year < 1) return - 1; + /* conservative overflow estimate */ + if (year > (INT_MAX / 366)) return - 1; + if (month > 12 || month < 1) return - 1; + if (day < 1) return - 1; + + isleap = leap(year); + + if (month != 2) { + if(day > days_in_month[month - 1]) return - 1; + } + else if ((isleap && day > 29) || (!isleap && day > 28)) + return - 1; + + /* First calculate the day number of December 31 last year */ + + /* save us from doing (year - 1) over and over */ + i = year - 1; + /* 365 days in a "regular" year + number of leap days */ + dn = (i * 365) + ((i / 4) - (i / 100) + (i / 400)); + + /* Now, day number of the last day of the previous month */ + + for (i = month - 1; i > 0; --i) + dn += days_in_month[i - 1]; + /* Add 29 February ? */ + if (month > 2 && isleap) ++dn; + + /* How many days into month are we */ + + dn += day; + + return dn; +} + +static int +leap(int year) +/* Is this a leap year ? */ +{ + /* every year exactly divisible by 4 is "leap" */ + /* unless it is exactly divisible by 100 */ + /* 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 (¤t_time); + localtime_r (¤t_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 (¤t_time); + localtime_r (¤t_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 (¤t_time); + localtime_r (¤t_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 (¤t_time); + localtime_r (¤t_time, &time_record); + + if (leap(time_record.tm_year + 1900)) { + return 366; + } + + return 365; +} diff --git a/anacron/gregor.h b/anacron/gregor.h new file mode 100644 index 0000000..842c54d --- /dev/null +++ b/anacron/gregor.h @@ -0,0 +1,30 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + Copyright (C) 2004 Pascal Hakim + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +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); diff --git a/anacron/lock.c b/anacron/lock.c new file mode 100644 index 0000000..71aae09 --- /dev/null +++ b/anacron/lock.c @@ -0,0 +1,211 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + Copyirght (C) 2004 Pascal Hakim + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +/* Lock and timestamp management + */ + +#include +#include +#include +#include +#include +#include +#include +#include "global.h" +#include "gregor.h" + +static void +open_tsfile(job_rec *jr) +/* Open the timestamp file for job jr */ +{ + jr->timestamp_fd = open(jr->ident, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (jr->timestamp_fd == -1) + die_e("Can't open timestamp file for job %s", jr->ident); + fcntl(jr->timestamp_fd, F_SETFD, 1); /* set close-on-exec flag */ + /* We want to own this file, and set its mode to 0600. This is necessary + * in order to prevent other users from putting locks on it. */ + if (fchown(jr->timestamp_fd, getuid(), getgid())) + die_e("Can't chown timestamp file %s", jr->ident); + if (fchmod(jr->timestamp_fd, S_IRUSR | S_IWUSR)) + die_e("Can't chmod timestamp file %s", jr->ident); +} + +static int +lock_file(int fd) +/* Attempt to put an exclusive fcntl() lock on file "fd" + * Return 1 on success, 0 on failure. + */ +{ + int r; + struct flock sfl; + + sfl.l_type = F_WRLCK; + sfl.l_start = 0; + sfl.l_whence = SEEK_SET; + sfl.l_len = 0; /* we lock all the file */ + 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 +consider_job(job_rec *jr) +/* Check the timestamp of the job. If "its time has come", lock the job + * and return 1, if it's too early, or we can't get the lock, return 0. + */ +{ + char timestamp[9]; + int ts_year, ts_month, ts_day, dn; + ssize_t b; + + open_tsfile(jr); + + /* read timestamp */ + b = read(jr->timestamp_fd, timestamp, 8); + if (b == -1) die_e("Error reading timestamp file %s", jr->ident); + timestamp[8] = 0; + + /* is it too early? */ + if (!force && b == 8) + { + int day_delta; + time_t jobtime; + struct tm *t; + + if (sscanf(timestamp, "%4d%2d%2d", &ts_year, &ts_month, &ts_day) == 3) + dn = day_num(ts_year, ts_month, ts_day); + else + dn = 0; + + day_delta = day_now - dn; + + /* + * if day_delta is negative, we assume there was a clock skew + * and re-run any affected jobs + * otherwise we check if the job's time has come + */ + if (day_delta >= 0 && day_delta < jr->period) + { + /* yes, skip job */ + 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: /* monthly */ + period = days_last_month (); + bypass = days_this_month (); + break; + case 2: /* yearly, annualy */ + period = days_last_year (); + bypass = days_this_year (); + break; + case 3: /* daily */ + period = 1; + bypass = 1; + break; + case 4: /* weekly */ + period = 7; + bypass = 7; + break; + default: + die ("Unknown named period for %s (%d)", jr->ident, jr->named_period); + } + printf ("Checking against %d with %d\n", day_delta, period); + if (day_delta < period && day_delta != bypass) + { + /* Job is still too young */ + xclose (jr->timestamp_fd); + return 0; + } + } + + jobtime = start_sec + jr->delay * 60; + + t = localtime(&jobtime); + if (!now && range_start != -1 && range_stop != -1 && + (t->tm_hour < range_start || t->tm_hour >= range_stop)) + { + Debug(("The job `%s' falls out of the %02d:00-%02d:00 hours range, skipping.", + jr->ident, range_start, range_stop)); + xclose (jr->timestamp_fd); + return 0; + } + } + + /* no! try to grab the lock */ + if (lock_file(jr->timestamp_fd)) return 1; /* success */ + + /* didn't get lock */ + xclose(jr->timestamp_fd); + explain("Job `%s' locked by another anacron - skipping", jr->ident); + return 0; +} + +void +unlock(job_rec *jr) +{ + xclose(jr->timestamp_fd); +} + +void +update_timestamp(job_rec *jr) +/* We write the date "now". "Now" can be either the time when anacron + * started, or the time when the job finished. + * I'm not quite sure which is more "right", but I've decided on the first + * option. + * Note that this is not the way it was with anacron 1.0.3 to 1.0.7. + */ +{ + char stamp[10]; + + snprintf(stamp, 10, "%04d%02d%02d\n", year, month, day_of_month); + if (lseek(jr->timestamp_fd, 0, SEEK_SET)) + die_e("Can't lseek timestamp file for job %s", jr->ident); + if (write(jr->timestamp_fd, stamp, 9) != 9) + die_e("Can't write timestamp file for job %s", jr->ident); + if (ftruncate(jr->timestamp_fd, 9)) + die_e("ftruncate error"); +} + +void +fake_job(job_rec *jr) +/* We don't bother with any locking here. There's no point. */ +{ + open_tsfile(jr); + update_timestamp(jr); + xclose(jr->timestamp_fd); +} diff --git a/anacron/log.c b/anacron/log.c new file mode 100644 index 0000000..6f4be63 --- /dev/null +++ b/anacron/log.c @@ -0,0 +1,225 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + Copyright (C) 2004 Pascal Hakim + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +/* Error logging + * + * We have two levels of logging (plus debugging if DEBUG is defined): + * "explain" level for informational messages, and "complain" level for errors. + * + * We log everything to syslog, see the top of global.h for relevant + * definitions. + * + * Stderr gets "complain" messages when we're in the foreground, + * and "explain" messages when we're in the foreground, and not "quiet". + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "global.h" + +static char truncated[] = " (truncated)"; +static char msg[MAX_MSG + 1]; +static int log_open = 0; + +/* Number of complaints that we've seen */ +int complaints = 0; + +static void +xopenlog() +{ + if (!log_open) + { + openlog(program_name, LOG_PID, SYSLOG_FACILITY); + log_open = 1; + } +} + +void +xcloselog() +{ + if (log_open) closelog(); + log_open = 0; +} + +static void +make_msg(const char *fmt, va_list args) +/* Construct the message string from its parts */ +{ + int len; + + /* There's some confusion in the documentation about what vsnprintf + * returns when the buffer overflows. Hmmm... */ + len = vsnprintf(msg, sizeof(msg), fmt, args); + if (len >= sizeof(msg) - 1) + strcpy(msg + sizeof(msg) - sizeof(truncated), truncated); +} + +static void +slog(int priority, const char *fmt, va_list args) +/* Log a message, described by "fmt" and "args", with the specified + * "priority". */ +{ + make_msg(fmt, args); + xopenlog(); + syslog(priority, "%s", msg); + if (!in_background) + { + if (priority == EXPLAIN_LEVEL && !quiet) + fprintf(stderr, "%s\n", msg); + else if (priority == COMPLAIN_LEVEL) + fprintf(stderr, "%s: %s\n", program_name, msg); + } +} + +static void +log_e(int priority, const char *fmt, va_list args) +/* Same as slog(), but also appends an error description corresponding + * to "errno". */ +{ + int saved_errno; + + saved_errno = errno; + make_msg(fmt, args); + xopenlog(); + syslog(priority, "%s: %s", msg, strerror(saved_errno)); + if (!in_background) + { + if (priority == EXPLAIN_LEVEL && !quiet) + fprintf(stderr, "%s: %s\n", msg, strerror(saved_errno)); + else if (priority == COMPLAIN_LEVEL) + fprintf(stderr, "%s: %s: %s\n", + program_name, msg, strerror(saved_errno)); + } +} + +void +explain(const char *fmt, ...) +/* Log an "explain" level message */ +{ + va_list args; + + va_start(args, fmt); + slog(EXPLAIN_LEVEL, fmt, args); + va_end(args); +} + +void +explain_e(const char *fmt, ...) +/* Log an "explain" level message, with an error description */ +{ + va_list args; + + va_start(args, fmt); + log_e(EXPLAIN_LEVEL, fmt, args); + va_end(args); +} + +void +complain(const char *fmt, ...) +/* Log a "complain" level message */ +{ + va_list args; + + va_start(args, fmt); + slog(COMPLAIN_LEVEL, fmt, args); + va_end(args); + + complaints += 1; +} + +void +complain_e(const char *fmt, ...) +/* Log a "complain" level message, with an error description */ +{ + va_list args; + + va_start(args, fmt); + log_e(COMPLAIN_LEVEL, fmt, args); + va_end(args); + + complaints += 1; +} + +void +die(const char *fmt, ...) +/* Log a "complain" level message, and exit */ +{ + va_list args; + + va_start(args, fmt); + slog(COMPLAIN_LEVEL, fmt, args); + va_end(args); + if (getpid() == primary_pid) complain("Aborted"); + + exit(FAILURE_EXIT); +} + +void +die_e(const char *fmt, ...) +/* Log a "complain" level message, with an error description, and exit */ +{ + va_list args; + + va_start(args, fmt); + log_e(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, ...) +{ + va_list args; + + va_start(args, fmt); + slog(DEBUG_LEVEL, fmt, args); + va_end(args); +} + +void +xdebug_e(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + log_e(DEBUG_LEVEL, fmt, args); + va_end(args); +} + +#endif /* DEBUG */ diff --git a/anacron/main.c b/anacron/main.c new file mode 100644 index 0000000..00c6a4e --- /dev/null +++ b/anacron/main.c @@ -0,0 +1,516 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + Copyright (C) 2004 Pascal Hakim + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "global.h" +#include "gregor.h" + +pid_t primary_pid; +int day_now; +int year, month, day_of_month; /* date anacron started */ + +char *program_name; +char *anacrontab; +char *spooldir; +int serialize, force, update_only, now, + no_daemon, quiet, testing_only; /* command-line options */ +char **args; /* vector of "job" command-line arguments */ +int nargs; /* number of these */ +char *defarg = "*"; +int in_background; /* are we in the background? */ +int old_umask; /* umask when started */ +sigset_t old_sigmask; /* signal mask when started */ + +job_rec *first_job_rec; +env_rec *first_env_rec; + +time_t start_sec; /* time anacron started */ +static volatile int got_sigalrm, got_sigchld, got_sigusr1; +int running_jobs, running_mailers; /* , number of */ +int range_start = -1; +int range_stop = -1; + +static void +print_version() +{ + printf("Anacron \n" + "Copyright (C) 1998 Itai Tzur \n" + "Copyright (C) 1999 Sean 'Shaleh' Perry \n" + "Copyright (C) 2004 Pascal Hakim \n" + "\n" + "Mail comments, suggestions and bug reports to ." + "\n\n"); +} + +static void +print_usage() +{ + printf("Usage: anacron [-s] [-f] [-n] [-d] [-q] [-t anacrontab] [-S spooldir] [job] ...\n" + " anacron [-S spooldir] -u [job] ...\n" + " anacron [-V|-h]\n" + " anacron -T [-t anacrontab]\n" + "\n" + " -s Serialize execution of jobs\n" + " -f Force execution of jobs, even before their time\n" + " -n Run jobs with no delay, implies -s\n" + " -d Don't fork to the background\n" + " -q Suppress stderr messages, only applicable with -d\n" + " -u Update the timestamps without actually running anything\n" + " -t Use this anacrontab\n" + " -V Print version information\n" + " -h Print this message\n" + " -T Test an anacrontab\n" + " -S Select a different spool directory\n" + "\n" + "See the manpage for more details.\n" + "\n"); +} + +static void +parse_opts(int argc, char *argv[]) +/* Parse command-line options */ +{ + int opt; + + quiet = no_daemon = serialize = force = update_only = now = 0; + opterr = 0; + while ((opt = getopt(argc, argv, "sfundqt:TS:Vh")) != EOF) + { + switch (opt) + { + case 's': + serialize = 1; + break; + case 'f': + force = 1; + break; + case 'u': + update_only = 1; + break; + case 'n': + now = serialize = 1; + break; + case 'd': + no_daemon = 1; + break; + case 'q': + quiet = 1; + break; + 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); + case '?': + fprintf(stderr, "%s: invalid option: %c\n", + program_name, optopt); + fprintf(stderr, "type: `%s -h' for more information\n", + program_name); + exit(FAILURE_EXIT); + } + } + if (optind == argc) + { + /* no arguments. Equivalent to: `*' */ + nargs = 1; + args = &defarg; + } + else + { + nargs = argc - optind; + args = argv + optind; + } +} + +pid_t +xfork() +/* Like fork(), only never returns on failure */ +{ + pid_t pid; + + pid = fork(); + if (pid == -1) die_e("Can't fork"); + return pid; +} + +int +xopen(int fd, const char *file_name, int flags) +/* Like open, only it: + * a) never returns on failure, and + * b) if "fd" is non-negative, expect the file to open + * on file-descriptor "fd". + */ +{ + int rfd; + + rfd = open(file_name, flags); + if (fd >= 0 && rfd != fd) + die_e("Can't open %s on file-descriptor %d", file_name, fd); + else if (rfd < 0) + die_e("Can't open %s", file_name); + return rfd; +} + +void +xclose(int fd) +/* Like close(), only doesn't return on failure */ +{ + if (close(fd)) die_e("Can't close file descriptor %d", fd); +} + +static void +go_background() +/* Become a daemon. The foreground process exits successfully. */ +{ + pid_t pid; + + /* stdin is already closed */ + + if (fclose(stdout)) die_e("Can't close stdout"); + xopen(1, "/dev/null", O_WRONLY); + + if (fclose(stderr)) die_e("Can't close stderr"); + xopen(2, "/dev/null", O_WRONLY); + + pid = xfork(); + if (pid != 0) + { + /* parent */ + exit(0); + } + else + { + /* child */ + primary_pid = getpid(); + if (setsid() == -1) die_e("setsid() error"); + in_background = 1; + } +} + +void +handle_sigalrm() +{ + got_sigalrm = 1; +} + +void +handle_sigchld() +{ + got_sigchld = 1; +} + +void +handle_sigusr1() +{ + got_sigusr1 = 1; +} + +static void +set_signal_handling() +/* We only use SIGALRM, SIGCHLD and SIGUSR1, and we unblock them only + * in wait_signal(). + */ +{ + sigset_t ss; + struct sigaction sa; + + got_sigalrm = got_sigchld = got_sigusr1 = 0; + + /* block SIGALRM, SIGCHLD and SIGUSR1 */ + if (sigemptyset(&ss) || + sigaddset(&ss, SIGALRM) || + sigaddset(&ss, SIGCHLD) || + sigaddset(&ss, SIGUSR1)) die_e("sigset error"); + if (sigprocmask(SIG_BLOCK, &ss, NULL)) die_e ("sigprocmask error"); + + /* setup SIGALRM handler */ + sa.sa_handler = handle_sigalrm; + sa.sa_mask = ss; + sa.sa_flags = 0; + if (sigaction(SIGALRM, &sa, NULL)) die_e("sigaction error"); + + /* setup SIGCHLD handler */ + sa.sa_handler = handle_sigchld; + sa.sa_mask = ss; + sa.sa_flags = SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa, NULL)) die_e("sigaction error"); + + /* setup SIGUSR1 handler */ + sa.sa_handler = handle_sigusr1; + sa.sa_mask = ss; + sa.sa_flags = 0; + if (sigaction(SIGUSR1, &sa, NULL)) die_e("sigaction error"); +} + +static void +wait_signal() +/* Return after a signal is caught */ +{ + sigset_t ss; + + if (sigprocmask(0, NULL, &ss)) die_e("sigprocmask error"); + if (sigdelset(&ss, SIGALRM) || + sigdelset(&ss, SIGCHLD) || + sigdelset(&ss, SIGUSR1)) die_e("sigset error"); + sigsuspend(&ss); +} + +static void +wait_children() +/* Wait until we have no more children (of any kind) */ +{ + while (running_jobs > 0 || running_mailers > 0) + { + wait_signal(); + if (got_sigchld) tend_children(); + got_sigchld = 0; + if (got_sigusr1) explain("Received SIGUSR1"); + got_sigusr1 = 0; + } +} + +static void +orderly_termination() +/* Execution is diverted here, when we get SIGUSR1 */ +{ + explain("Received SIGUSR1"); + got_sigusr1 = 0; + wait_children(); + explain("Exited"); + exit(0); +} + +static void +xsleep(unsigned int n) +/* Sleep for n seconds, servicing SIGCHLDs and SIGUSR1s in the meantime. + * If n=0, return immediately. + */ +{ + if (n == 0) return; + alarm(n); + do + { + wait_signal(); + if (got_sigchld) tend_children(); + got_sigchld = 0; + if (got_sigusr1) orderly_termination(); + } + while (!got_sigalrm); + got_sigalrm = 0; +} + +static void +wait_jobs() +/* Wait until there are no running jobs, + * servicing SIGCHLDs and SIGUSR1s in the meantime. + */ +{ + while (running_jobs > 0) + { + wait_signal(); + if (got_sigchld) tend_children(); + got_sigchld = 0; + if (got_sigusr1) orderly_termination(); + } +} + +static void +record_start_time() +{ + struct tm *tm_now; + + start_sec = time(NULL); + tm_now = localtime(&start_sec); + year = tm_now->tm_year + 1900; + month = tm_now->tm_mon + 1; + 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 && !testing_only) + explain("Anacron started on %04d-%02d-%02d", + year, month, day_of_month); +} + +static int +time_till(job_rec *jr) +/* Return the number of seconds that we have to wait until it's time + * to start job jr. + */ +{ + unsigned int tj, tn; + + if (now) return 0; + tn = time(NULL); + tj = start_sec + jr->delay * 60; + if (tj < tn) return 0; + if (tj - tn > 3600*24) + { + explain("System time manipulation detected, job `%s' will run immediately", + jr->ident); + return 0; + } + return tj - tn; +} + +static void +fake_jobs() +{ + int j; + + j = 0; + while (j < njobs) + { + fake_job(job_array[j]); + explain("Updated timestamp for job `%s' to %04d-%02d-%02d", + job_array[j]->ident, year, month, day_of_month); + j++; + } +} + +static void +explain_intentions() +{ + int j; + + j = 0; + while (j < njobs) + { + if (now) + { + explain("Will run job `%s'", job_array[j]->ident); + } + else + { + explain("Will run job `%s' in %d min.", + job_array[j]->ident, job_array[j]->delay); + } + j++; + } + if (serialize && njobs > 0) + explain("Jobs will be executed sequentially"); +} + +int +main(int argc, char *argv[]) +{ + int j; + int cwd; + struct timeval tv; + struct timezone tz; + + anacrontab = NULL; + spooldir = NULL; + + setlocale(LC_ALL, ""); + + if (gettimeofday(&tv, &tz) != 0) + explain("Can't get exact time, failure."); + + srandom(getpid()+tv.tv_usec); + + if((program_name = strrchr(argv[0], '/')) == NULL) + program_name = argv[0]; + else + ++program_name; /* move pointer to char after '/' */ + + parse_opts(argc, argv); + + if (anacrontab == NULL) + anacrontab = strdup(ANACRONTAB); + + if (spooldir == NULL) + spooldir = strdup(ANACRON_SPOOL_DIR); + + 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 %s", spooldir ); + + old_umask = umask(0); + + if (sigprocmask(0, NULL, &old_sigmask)) die_e("sigset error"); + + if (fclose(stdin)) die_e("Can't close stdin"); + xopen(0, "/dev/null", O_RDONLY); + + if (!no_daemon && !testing_only) + go_background(); + else + primary_pid = getpid(); + + record_start_time(); + read_tab(cwd); + close(cwd); + arrange_jobs(); + + if (testing_only) + { + if (complaints) exit (1); + + exit (0); + } + + if (update_only) + { + fake_jobs(); + exit(0); + } + + explain_intentions(); + set_signal_handling(); + running_jobs = running_mailers = 0; + for(j = 0; j < njobs; ++j) + { + xsleep(time_till(job_array[j])); + if (serialize) wait_jobs(); + launch_job(job_array[j]); + } + wait_children(); + explain("Normal exit (%d job%s run)", njobs, njobs == 1 ? "" : "s"); + exit(0); +} diff --git a/anacron/matchrx.c b/anacron/matchrx.c new file mode 100644 index 0000000..2f41251 --- /dev/null +++ b/anacron/matchrx.c @@ -0,0 +1,90 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +#include +#include +#include +#include +#include +#include "matchrx.h" + +int +match_rx(const char *rx, char *string, int n_sub, /* char **substrings */...) +/* Return 1 if the regular expression "*rx" matches the string "*string", + * 0 if not, -1 on error. + * "Extended" regular expressions are used. + * Additionally, there should be "n_sub" "substrings" arguments. These, + * if not NULL, and if the match succeeds are set to point to the + * corresponding substrings of the regexp. + * The original string is changed, and the substrings must not overlap, + * or even be directly adjacent. + * This is not the most efficient, or elegant way of doing this. + */ +{ + int r, n; + regex_t crx; + va_list va; + char **substring; + regmatch_t *sub_offsets; + sub_offsets = malloc(sizeof(regmatch_t) * (n_sub + 1)); + if (sub_offsets == NULL) + return -1; + memset(sub_offsets, 0, sizeof(regmatch_t) * (n_sub + 1)); + + if (regcomp(&crx, rx, REG_EXTENDED)) { + free(sub_offsets); + return -1; + } + r = regexec(&crx, string, n_sub + 1, sub_offsets, 0); + if (r != 0 && r != REG_NOMATCH) { + free(sub_offsets); + return -1; + } + regfree(&crx); + if (r == REG_NOMATCH) { + free(sub_offsets); + return 0; + } + + va_start(va, n_sub); + n = 1; + while (n <= n_sub) + { + substring = va_arg(va, char**); + if (substring != NULL) + { + if (sub_offsets[n].rm_so == -1) { + va_end(va); + free(sub_offsets); + return - 1; + } + *substring = string + sub_offsets[n].rm_so; + *(string + sub_offsets[n].rm_eo) = 0; + } + n++; + } + va_end(va); + free(sub_offsets); + return 1; +} diff --git a/anacron/matchrx.h b/anacron/matchrx.h new file mode 100644 index 0000000..3ce8350 --- /dev/null +++ b/anacron/matchrx.h @@ -0,0 +1,26 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +int match_rx(const char *rx, char *string, + int n_sub, /* char **substrings */...); diff --git a/anacron/readtab.c b/anacron/readtab.c new file mode 100644 index 0000000..aa9eb93 --- /dev/null +++ b/anacron/readtab.c @@ -0,0 +1,393 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + Copyright (C) 2004 Pascal Hakim + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +/* /etc/anacrontab parsing, and job sorting + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "global.h" +#include "matchrx.h" + +static struct obstack input_o; /* holds input line */ +static struct obstack tab_o; /* holds processed data read from anacrontab */ +static FILE *tab; +job_rec **job_array; +int njobs; /* number of jobs to run */ +static int jobs_read; /* number of jobs read */ +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 */ + +static int random_number = 0; + +/* some definitions for the obstack macros */ +#define obstack_chunk_alloc xmalloc +#define obstack_chunk_free free + +static void * +xmalloc (size_t size) +/* Just like standard malloc(), only never returns NULL. */ +{ + void * ptr; + + ptr = malloc(size); + if (ptr == NULL) + die("Memory exhausted"); + return ptr; +} + +static int +conv2int(const char *s) +/* Return the int or -1 on over/under-flow + */ +{ + long l; + + errno = 0; + l = strtol(s, NULL, 10); + /* we use negative as error, so I am really returning unsigned int */ + if (errno == ERANGE || l < 0 || l > INT_MAX) return - 1; + return l; +} + +static char * +read_tab_line () +/* Read one line and return a pointer to it. +Return NULL if no more lines. + */ +{ + int c, prev=0; + + if (feof(tab)) return NULL; + 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); +} + +static int +job_arg_num(const char *ident) +/* Return the command-line-argument number refering to this job-identifier. + * If it isn't specified, return -1. + */ +{ + int i, r; + + for (i = 0; i < nargs; i++) + { + r = fnmatch(args[i], ident, 0); + if (r == 0) return i; + if (r != FNM_NOMATCH) die("fnmatch() error"); + } + return - 1; +} + +static void +register_env(const char *env_var, const char *value) +/* Store the environment assignment "env_var"="value" */ +{ + env_rec *er; + int var_len, val_len; + + var_len = strlen(env_var); + val_len = strlen(value); + er = obstack_alloc(&tab_o, sizeof(env_rec)); + er->assign = obstack_alloc(&tab_o, var_len + 1 + val_len + 1); + strcpy(er->assign, env_var); + er->assign[var_len] = '='; + strcpy(er->assign + var_len + 1, value); + er->assign[var_len + 1 + val_len] = 0; + 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)); +} + +static void +register_job(const char *periods, const char *delays, + const char *ident, char *command) +/* Store a job definition */ +{ + int period, delay; + job_rec *jr; + int ident_len, command_len; + + ident_len = strlen(ident); + command_len = strlen(command); + jobs_read++; + period = conv2int(periods); + delay = conv2int(delays); + if (period < 0 || delay < 0) + { + complain("%s: number out of range on line %d, skipping", + anacrontab, line_num); + return; + } + jr = obstack_alloc(&tab_o, sizeof(job_rec)); + jr->period = period; + jr->named_period = 0; + delay += random_number; + jr->delay = delay; + jr->tab_line = line_num; + jr->ident = obstack_alloc(&tab_o, ident_len + 1); + 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)); +} + +static void +register_period_job(const char *periods, const char *delays, + const char *ident, char *command) +/* Store a job definition with a named period */ +{ + int delay; + job_rec *jr; + int period_len, ident_len, command_len; + + period_len = strlen(periods); + ident_len = strlen(ident); + command_len = strlen(command); + jobs_read++; + delay = conv2int(delays); + if (delay < 0) + { + complain("%s: number out of range on line %d, skipping", + anacrontab, line_num); + return; + } + + jr = obstack_alloc(&tab_o, sizeof(job_rec)); + if (!strncmp ("@monthly", periods, 7)) { + jr->named_period = 1; + } else if (!strncmp("@yearly", periods, 7) || !strncmp("@annualy", periods, 8)) { + jr->named_period = 2; + } else if (!strncmp ("@daily", periods, 7)) { + jr->named_period = 3; + } else if (!strncmp ("@weekly", periods, 7)) { + jr->named_period = 4; + } else { + complain("%s: Unknown named period on line %d, skipping", + anacrontab, line_num); + } + jr->period = 0; + delay += random_number; + jr->delay = delay; + jr->tab_line = line_num; + jr->ident = obstack_alloc(&tab_o, ident_len + 1); + 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->named_period, jr->delay, jr->ident, jr->command)); +} + +static void +parse_tab_line(char *line) +{ + int r; + char *env_var; + char *value; + char *periods; + char *delays; + char *ident; + char *command; + char *from; + char *to; + + /* an empty line? */ + r = match_rx("^[ \t]*($|#)", line, 0); + if (r == -1) goto reg_err; + if (r) + { + Debug(("line %d empty", line_num)); + return; + } + + /* an environment assignment? */ + r = match_rx("^[ \t]*([^ \t=]+)[ \t]*=(.*)$", line, 2, + &env_var, &value); + if (r == -1) goto reg_err; + if (r) + { + if (strncmp(env_var, "START_HOURS_RANGE", 17) == 0) + { + r = match_rx("^([[:digit:]]+)-([[:digit:]]+)$", value, 2, &from, &to); + if ((r == -1) || (from == NULL) || (to == NULL)) goto reg_invalid; + range_start = atoi(from); + range_stop = atoi(to); + Debug(("Jobs will start in the %02d:00-%02d:00 range.", range_start, range_stop)); + } + if (strncmp(env_var, "RANDOM_DELAY", 12) == 0) { + r = match_rx("^([[:digit:]]+)$", value, 0); + if (r != -1) { + int i = random(); + double x = 0; + x = (double) i / (double) RAND_MAX * (double) (atoi(value)); + random_number = (int)x; + Debug(("Randomized delay set: %d", random_number)); + } + else goto reg_invalid; + } + register_env(env_var, value); + return; + } + + /* a job? */ + r = match_rx("^[ \t]*([[:digit:]]+)[ \t]+([[:digit:]]+)[ \t]+" + "([^ \t/]+)[ \t]+([^ \t].*)$", + line, 4, &periods, &delays, &ident, &command); + if (r == -1) goto reg_err; + if (r) + { + register_job(periods, delays, ident, command); + return; + } + + /* A period job? */ + r = match_rx("^[ \t]*(@[^ \t]+)[ \t]+([[:digit:]]+)[ \t]+" + "([^ \t/]+)[ \t]+([^ \t].*)$", + line, 4, &periods, &delays, &ident, &command); + if (r == -1) goto reg_err; + if (r) + { + register_period_job(periods, delays, ident, command); + return; + } + + reg_invalid: + complain("Invalid syntax in %s on line %d - skipping this line", + anacrontab, line_num); + return; + + reg_err: + die("Regex error reading %s", anacrontab); +} + +void +read_tab(int cwd) +/* Read the anacrontab file into memory */ +{ + char *tab_line; + + first_job_rec = last_job_rec = NULL; + first_env_rec = last_env_rec = NULL; + jobs_read = 0; + line_num = 0; + /* Open the anacrontab file */ + fchdir (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); + while ((tab_line = read_tab_line()) != NULL) + { + line_num++; + parse_tab_line(tab_line); + obstack_free(&input_o, tab_line); + } + if (fclose(tab)) die_e("Error closing %s", anacrontab); +} + +static int +execution_order(const job_rec **job1, const job_rec **job2) +/* Comparison function for sorting the jobs. + */ +{ + int d; + + d = (*job1)->arg_num - (*job2)->arg_num; + if (d != 0 && now) return d; + d = (*job1)->delay - (*job2)->delay; + if (d != 0) return d; + d = (*job1)->tab_line - (*job2)->tab_line; + return d; +} + +void +arrange_jobs() +/* Make an array of pointers to jobs that are going to be executed, + * and arrange them in the order of execution. + * Also lock these jobs. + */ +{ + job_rec *j; + + j = first_job_rec; + njobs = 0; + while (j != NULL) + { + if (j->arg_num != -1 && (update_only || testing_only || consider_job(j))) + { + njobs++; + obstack_grow(&tab_o, &j, sizeof(j)); + } + j = j->next; + } + job_array = obstack_finish(&tab_o); + + /* sort the jobs */ + qsort(job_array, njobs, sizeof(*job_array), + (int (*)(const void *, const void *))execution_order); +} diff --git a/anacron/runjob.c b/anacron/runjob.c new file mode 100644 index 0000000..ba4dc06 --- /dev/null +++ b/anacron/runjob.c @@ -0,0 +1,346 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "global.h" + +#include + +static int +temp_file(job_rec *jr) +/* Open a temporary file and return its file descriptor */ +{ + const int max_retries = 50; + char *name; + int fdin = -1; + int fdout, i; + + i = 0; + name = NULL; + do + { + i++; + free(name); + name = tempnam(NULL, NULL); + if (name == NULL) die("Can't find a unique temporary filename"); + fdout = open(name, O_WRONLY | O_CREAT | O_EXCL | O_APPEND, + S_IRUSR | S_IWUSR); + if ( fdout != -1 ) + fdin = open(name, O_RDONLY, S_IRUSR | S_IWUSR); + /* I'm not sure we actually need to be so persistent here */ + } while (fdout == -1 && errno == EEXIST && i < max_retries); + + if (fdout == -1) die_e("Can't open temporary file for writing"); + if (fdin == -1) die_e("Can't open temporary file for reading"); + if (unlink(name)) die_e("Can't unlink temporary file"); + free(name); + fcntl(fdout, F_SETFD, FD_CLOEXEC); /* set close-on-exec flag */ + fcntl(fdin, F_SETFD, FD_CLOEXEC); /* set close-on-exec flag */ + + jr->input_fd = fdin; + jr->output_fd = fdout; + + return fdout; +} + +static off_t +file_size(int fd) +/* Return the size of temporary file fd */ +{ + struct stat st; + + if (fstat(fd, &st)) die_e("Can't fstat temporary file"); + return st.st_size; +} + +static char * +username() +{ + struct passwd *ps; + + ps = getpwuid(geteuid()); + if (ps == NULL) die_e("getpwuid() error"); + return ps->pw_name; +} + +static void +xputenv(const char *s) +{ + if (putenv(s)) die_e("Can't set the environment"); +} + +static void +setup_env(const job_rec *jr) +/* Setup the environment for the job according to /etc/anacrontab */ +{ + env_rec *er; + + er = first_env_rec; + if (er == NULL || jr->prev_env_rec == NULL) return; + xputenv(er->assign); + while (er != jr->prev_env_rec) + { + er = er->next; + xputenv(er->assign); + } +} + +static void +run_job(const job_rec *jr) +/* This is called to start the job, after the fork */ +{ + /* setup stdout and stderr */ + xclose(1); + xclose(2); + 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 */ + if (chdir("/")) die_e("Can't chdir to '/'"); + + umask(old_umask); + if (sigprocmask(SIG_SETMASK, &old_sigmask, NULL)) + die_e("sigprocmask error"); + xcloselog(); + execl("/bin/sh", "/bin/sh", "-c", jr->command, (char *)NULL); + 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 + * exit status in "*status". If not, return 0. + */ +{ + pid_t r; + + r = waitpid(pid, status, WNOHANG); + if (r == -1) die_e("waitpid() error"); + if (r == 0) return 0; + return 1; +} + +static void +launch_mailer(job_rec *jr) +{ + pid_t pid; + struct stat buf; + int r; + + /* 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) + { + /* child */ + in_background = 1; + /* set stdin to the job's output */ + xclose(0); + if (dup2(jr->input_fd, 0) != 0) die_e("Can't dup2()"); + if (lseek(0, 0, SEEK_SET) != 0) die_e("Can't lseek()"); + umask(old_umask); + if (sigprocmask(SIG_SETMASK, &old_sigmask, NULL)) + die_e("sigprocmask error"); + xcloselog(); + + /* Ensure stdout/stderr are sane before exec-ing sendmail */ + xclose(1); xopen(1, "/dev/null", O_WRONLY); + xclose(2); xopen(2, "/dev/null", O_WRONLY); + xclose(jr->output_fd); + + /* Ensure stdin is not appendable ... ? */ + /* fdflags = fcntl(0, F_GETFL); fdflags &= ~O_APPEND; */ + /* fcntl(0, F_SETFL, fdflags ); */ + + /* Here, I basically mirrored the way /usr/sbin/sendmail is called + * by cron on a Debian system, except for the "-oem" and "-or0s" + * options, which don't seem to be appropriate here. + * Hopefully, this will keep all the MTAs happy. */ + execl(SENDMAIL, SENDMAIL, "-FAnacron", "-odi", + jr->mailto, (char *)NULL); + die_e("Can't exec " SENDMAIL); + } + /* parent */ + /* record mailer pid */ + jr->mailer_pid = pid; + running_mailers++; +} + +static void +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 status %d", + jr->ident, WEXITSTATUS(status)); + else if (!WIFEXITED(status) && WIFSIGNALED(status)) + complain("Tried to mail output of job `%s', " + "but mailer process (" SENDMAIL ") got signal %d", + jr->ident, WTERMSIG(status)); + else if (!WIFEXITED(status) && !WIFSIGNALED(status)) + complain("Tried to mail output of job `%s', " + "but mailer process (" SENDMAIL ") terminated abnormally" + , jr->ident); + + jr->mailer_pid = 0; + running_mailers--; +} + +void +launch_job(job_rec *jr) +{ + pid_t pid; + int fd; + char hostname[512]; + char *mailto; + + /* get hostname */ + if (gethostname(hostname, 512)) { + strcpy (hostname,"unknown machine"); + } + + setup_env(jr); + + /* Get the destination email address if set, or current user otherwise */ + mailto = getenv("MAILTO"); + + if (mailto) + jr->mailto = mailto; + else + jr->mailto = username (); + + /* create temporary file for stdout and stderr of the job */ + temp_file(jr); fd = jr->output_fd; + /* write mail header */ + xwrite(fd, "From: "); + xwrite(fd, "Anacron <"); + xwrite(fd, username()); + xwrite(fd, ">\n"); + xwrite(fd, "To: "); + if (mailto) { + xwrite(fd, mailto); + } else { + xwrite(fd, username()); + } + xwrite(fd, "\n"); + xwrite(fd, "Content-Type: text/plain; charset=\""); + xwrite(fd, nl_langinfo(CODESET)); + xwrite(fd, "\"\n"); + xwrite(fd, "Subject: Anacron job '"); + xwrite(fd, jr->ident); + xwrite(fd, "' on "); + xwrite(fd, hostname); + xwrite(fd, "\n\n"); + + jr->mail_header_size = file_size(fd); + + pid = xfork(); + if (pid == 0) + { + /* child */ + in_background = 1; + run_job(jr); + /* execution never gets here */ + } + /* parent */ + explain("Job `%s' started", jr->ident); + jr->job_pid = pid; + running_jobs++; +} + +static void +tend_job(job_rec *jr, int status) +/* Take care of a finished job */ +{ + int mail_output; + char *m; + + update_timestamp(jr); + unlock(jr); + if (file_size(jr->output_fd) > jr->mail_header_size) mail_output = 1; + else mail_output = 0; + + m = mail_output ? " (mailing output)" : ""; + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + explain("Job `%s' terminated%s", jr->ident, m); + else if (WIFEXITED(status)) + explain("Job `%s' terminated (exit status: %d)%s", + jr->ident, WEXITSTATUS(status), m); + else if (WIFSIGNALED(status)) + complain("Job `%s' terminated due to signal %d%s", + jr->ident, WTERMSIG(status), m); + else /* is this possible? */ + complain("Job `%s' terminated abnormally%s", jr->ident, m); + + jr->job_pid = 0; + running_jobs--; + if (mail_output) launch_mailer(jr); + xclose(jr->output_fd); + xclose(jr->input_fd); +} + +void +tend_children() +/* This is called whenever we get a SIGCHLD. + * Takes care of zombie children. + */ +{ + int j; + int status; + + j = 0; + while (j < njobs) + { + if (job_array[j]->mailer_pid != 0 && + xwait(job_array[j]->mailer_pid, &status)) + tend_mailer(job_array[j], status); + if (job_array[j]->job_pid != 0 && + xwait(job_array[j]->job_pid, &status)) + tend_job(job_array[j], status); + j++; + } +} diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..ae76299 --- /dev/null +++ b/configure.ac @@ -0,0 +1,244 @@ +AC_INIT([cronie],[1.4.8],[mmaslano@redhat.com]) +AC_CONFIG_HEADER([config.h]) +AC_PREREQ(2.60) + +AM_INIT_AUTOMAKE + +AC_CANONICAL_HOST + +dnl Checks for programs. + +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_LN_S + +dnl Check for _GNU_SOURCE +AC_USE_SYSTEM_EXTENSIONS + +AC_CHECK_HEADERS( \ + dirent.h \ + fcntl.h \ + getopt.h \ + glob.h \ + limits.h \ + paths.h \ + pty.h \ + selinux/selinux.h \ + stddef.h \ + stdint.h \ + sys/audit.h \ + sys/inotify.h \ + sys/stat.h \ + sys/stream.h \ + sys/stropts.h \ + sys/time.h \ + sys/timers.h \ + sys/types.h \ + sys/cdefs.h \ + sys/fcntl.h \ + time.h \ + unistd.h \ + util.h \ + utime.h \ +) + +AC_CHECK_FUNCS( \ + fcntl \ + lockf \ + flock \ + fchown \ + fchgrp \ +) + +dnl Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_TYPE_SIGNAL +AC_TYPE_UID_T +AC_TYPE_MODE_T +AC_TYPE_OFF_T +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_STRUCT_TM +AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,,[#include ]) + +dnl Checking for programs + +AC_MSG_CHECKING(username to run under) +AC_ARG_WITH(daemon_username, +[AS_HELP_STRING([--with-daemon_username=DAEMON_USERNAME], [Username to run under (default daemon) ])], +[ case "$withval" in + no) + AC_MSG_ERROR(Need DAEMON_USERNAME.) + ;; + yes) + DAEMON_USERNAME=daemon + AC_MSG_RESULT(daemon) + ;; + *) + DAEMON_USERNAME="$withval"; + AC_MSG_RESULT($withval) + ;; + esac ], + DAEMON_USERNAME=daemon + AC_MSG_RESULT(daemon) +) +AC_SUBST(DAEMON_USERNAME) + +AC_MSG_CHECKING(groupname to run under) +AC_ARG_WITH(daemon_groupname, +[AS_HELP_STRING([--with-daemon_groupname=DAEMON_GROUPNAME], [Groupname to run under (default daemon) ])], +[ case "$withval" in + no) + AC_MSG_ERROR(Need DAEMON_GROUPNAME.) + ;; + yes) + DAEMON_GROUPNAME=daemon + AC_MSG_RESULT(daemon) + ;; + *) + DAEMON_GROUPNAME="$withval"; + AC_MSG_RESULT($withval) + ;; + esac ], + DAEMON_GROUPNAME=daemon + AC_MSG_RESULT(daemon) +) +AC_SUBST(DAEMON_GROUPNAME) + +# Check whether inotify is accepted +AC_ARG_WITH(inotify, + [AS_HELP_STRING([--with-inotify], [ Enable inotify support])], + [ if test "x$withval" != "xno" ; then + AC_DEFINE(WITH_INOTIFY,1,[Define if you want inotify support.]) + AC_CHECK_HEADER([sys/inotify.h], , AC_MSG_ERROR(Inotify support requires sys/inotify.h header)) + AC_CHECK_FUNCS(inotify_init inotify_add_watch) + fi + ] +) + +AC_ARG_ENABLE(pie,CRONIE_HELP_STRING(--enable-pie,Build cronie as a Position Independent Executable)) +if test "x$enable_pie" = xyes; then + CFLAGS="$CFLAGS -fPIE -DPIE" + LDFLAGS="$LDFLAGS -pie" +fi + +AC_ARG_ENABLE(relro,CRONIE_HELP_STRING(--enable-relro,Build cronie with relro flag)) +if test "x$enable_relro" = xyes; then + LDFLAGS="-Wl,-z,relro -Wl,-z,now" +fi + +# Check whether user wants SELinux support +SELINUX_MSG="no" +LIBSELINUX="" +AC_ARG_WITH(selinux, + [AS_HELP_STRING([--with-selinux], [Enable SELinux support])], + [ if test "x$withval" != "xno" ; then + saved_LIBS="$LIBS" + AC_DEFINE(WITH_SELINUX,1,[Define if you want SELinux support.]) + SELINUX_MSG="yes" + AC_CHECK_HEADER([selinux/selinux.h], ,AC_MSG_ERROR(SELinux support requires selinux.h header)) + AC_CHECK_LIB(selinux, setexeccon, [ LIBSELINUX="-lselinux" ], + AC_MSG_ERROR(SELinux support requires libselinux library)) + AC_CHECK_FUNCS(getseuserbyname get_default_context_with_level) + LIBS="$saved_LIBS $LIBSELINUX" + AC_SUBST(LIBSELINUX) + fi ] +) + +AC_ARG_WITH(pam, [AS_HELP_STRING([--with-pam], [Build with PAM support])]) +AC_ARG_ENABLE(pam, [AS_HELP_STRING([--enable-pam], [Alias for --with-pam])]) + +# Check that with_pam and enable_pam are consistent. +# If neither one is set, the default is "no." +if test -z "$with_pam"; then + with_pam=${enable_pam:-no} +elif test -n "$enable_pam" && test "$with_pam" != "$enable_pam"; then + AC_MSG_ERROR( + [Contradicting --with/without-pam and --enable/disable-pam options.]) +fi + +AM_CONDITIONAL([PAM], [test "$with_pam" != no]) + +if test "$with_pam" != no; then + AC_DEFINE(WITH_PAM, 1, [Define if you want to enable PAM support]) + pam_appl_h_found=no + AC_CHECK_HEADERS([pam/pam_appl.h security/pam_appl.h], + [pam_appl_h_found=yes]) + test "$pam_appl_h_found" = yes || + AC_MSG_ERROR([PAM headers not found]) + + saved_LIBS="$LIBS" + AC_CHECK_LIB([dl], [dlopen], [libdl_found=yes], [libdl_found=no]) + AC_CHECK_LIB(pam, pam_set_item, , AC_MSG_ERROR([*** libpam missing])) + AC_CHECK_FUNCS([pam_getenvlist pam_putenv]) + LIBS="$saved_LIBS" + + case $libdl_found:" $LIBS " in #( + *" -ldl "*) LIBPAM= ;; #( + yes:*) LIBPAM=-ldl ;; # libdl found, but is not in $LIBS + esac + AC_SUBST([LIBPAM], ["-lpam $LIBPAM"]) +fi + +AC_DEFINE(DEBUGGING,1,[Code will be built with debug info.]) + +AC_DEFINE(MAILARG,"/usr/sbin/sendmail",[There will be path to sendmail.]) + +AC_DEFINE(MAILFMT,"%s -FCronDaemon -i -odi -oem -oi -t -f %s", +[-i = don't terminate on "." by itself +-Fx = Set full-name of sender +-odi = Option Deliverymode Interactive +-oem = Option Errors Mailedtosender +-oi = Ignore "." alone on a line +-t = Get recipient from headers +-f %s = Envelope sender address +-d = undocumented but common flag.]) + +AC_DEFINE(SYSLOG,1,[Using syslog for log messages.]) + +AC_DEFINE(CAPITALIZE_FOR_PS, 1, [if you have a tm_gmtoff member in struct tm]) + +# Check whether user wants Linux audit support +AC_ARG_WITH(audit, + [AS_HELP_STRING([--with-audit], [Enable audit trails])], + [ if test "x$withval" != "xno" ; then + saved_LIBS="$LIBS" + AC_DEFINE(WITH_AUDIT,1,[Define if you want Audit trails.]) + AC_CHECK_HEADER([libaudit.h], ,AC_MSG_ERROR(Audit trails requires libaudit.h header)) + AC_CHECK_LIB(audit, audit_open, [ LIBAUDIT="-laudit" ], + AC_MSG_ERROR(Audit support needs audit libraries.)) + LIBS="$saved_LIBS $LIBAUDIT" + AC_SUBST(LIBAUDIT) + fi ] +) + +dnl CRONIE_VAR_DEFAULT (VAR, DESCRIPTION, DEFAULT) +dnl -------------------------------------------- +AC_DEFUN([CRONIE_CONF_VAR], +[AC_ARG_VAR([$1], [$2 @<:@$3@:>@]) +if test "$$1" = ""; then + $1='$3' +fi +]) + +AC_DEFUN([ANACRON_CONF_VAR], +[AC_ARG_VAR([$1], [$2 @<:@$3@:>@]) +if test "$$1" = ""; then + $1='$3' + fi +]) + +CRONIE_CONF_VAR([SYSCRONTAB], [the current working directory of the running daemon], [${sysconfdir}/crontab]) +CRONIE_CONF_VAR([SYS_CROND_DIR], [the current working directory of the running daemon], [${sysconfdir}/cron.d]) +CRONIE_CONF_VAR([SPOOL_DIR], [the directory where all the user cron tabs reside], [${localstatedir}/spool/cron]) + +AC_ARG_ENABLE([anacron], [AS_HELP_STRING([--enable-anacron], [Build also anacron.])]) +AM_CONDITIONAL([ANACRON], [test "$enable_anacron" = yes]) +if test "$enable_anacron" != no; then + ANACRON_CONF_VAR([ANACRON_SPOOL_DIR],[The path for anacron locks.],[${localstatedir}/spool/anacron]) + ANACRON_CONF_VAR([ANACRONTAB],[The anacron table for regular jobs.],[${sysconfdir}/anacrontab]) +fi + +AC_CONFIG_FILES([Makefile src/Makefile man/Makefile anacron/Makefile]) +AC_OUTPUT + diff --git a/contrib/0anacron b/contrib/0anacron new file mode 100644 index 0000000..6835088 --- /dev/null +++ b/contrib/0anacron @@ -0,0 +1,18 @@ +#!/bin/bash +#in case file doesn't exist +if test -r /var/spool/anacron/cron.daily; then + day=`cat /var/spool/anacron/cron.daily` +fi +if [ `date +%Y%m%d` = "$day" ]; then + exit 0; +fi + +# in case anacron is already running, +# there will be log (daemon won't be running twice). +if test -x /usr/bin/on_ac_power; then + /usr/bin/on_ac_power &> /dev/null + if test $? -eq 1; then + exit 0 + fi +fi +/usr/sbin/anacron -s diff --git a/contrib/0hourly b/contrib/0hourly new file mode 100644 index 0000000..09039c1 --- /dev/null +++ b/contrib/0hourly @@ -0,0 +1,4 @@ +SHELL=/bin/bash +PATH=/sbin:/bin:/usr/sbin:/usr/bin +MAILTO=root +01 * * * * root run-parts /etc/cron.hourly diff --git a/contrib/anacrontab b/contrib/anacrontab new file mode 100644 index 0000000..78c6f8c --- /dev/null +++ b/contrib/anacrontab @@ -0,0 +1,16 @@ +# /etc/anacrontab: configuration file for anacron + +# See anacron(8) and anacrontab(5) for details. + +SHELL=/bin/sh +PATH=/sbin:/bin:/usr/sbin:/usr/bin +MAILTO=root +# the maximal random delay added to the base delay of the jobs +RANDOM_DELAY=45 +# the jobs will be started during the following hours only +START_HOURS_RANGE=3-22 + +#period in days delay in minutes job-identifier command +1 5 cron.daily nice run-parts /etc/cron.daily +7 25 cron.weekly nice run-parts /etc/cron.weekly +@monthly 45 cron.monthly nice run-parts /etc/cron.monthly diff --git a/contrib/dailyjobs b/contrib/dailyjobs new file mode 100644 index 0000000..ee9af24 --- /dev/null +++ b/contrib/dailyjobs @@ -0,0 +1,8 @@ +SHELL=/bin/bash +PATH=/sbin:/bin:/usr/sbin:/usr/bin +MAILTO=root + +# run-parts +02 4 * * * root [ ! -f /etc/cron.hourly/0anacron ] && run-parts /etc/cron.daily +22 4 * * 0 root [ ! -f /etc/cron.hourly/0anacron ] && run-parts /etc/cron.weekly +42 4 1 * * root [ ! -f /etc/cron.hourly/0anacron ] && run-parts /etc/cron.monthly diff --git a/crond.sysconfig b/crond.sysconfig new file mode 100644 index 0000000..ee23703 --- /dev/null +++ b/crond.sysconfig @@ -0,0 +1,3 @@ +# Settings for the CRON daemon. +# CRONDARGS= : any extra command-line startup arguments for crond +CRONDARGS= diff --git a/cronie.init b/cronie.init new file mode 100755 index 0000000..1c520b9 --- /dev/null +++ b/cronie.init @@ -0,0 +1,132 @@ +#!/bin/sh +# +# crond Start/Stop the cron clock daemon. +# +# chkconfig: 2345 90 60 +# description: cron is a standard UNIX program that runs user-specified \ +# programs at periodic scheduled times. vixie cron adds a \ +# number of features to the basic UNIX cron, including better \ +# security and more powerful configuration options. + +### BEGIN INIT INFO +# Provides: crond crontab +# Required-Start: $local_fs $syslog +# Required-Stop: $local_fs $syslog +# Default-Start: 2345 +# Default-Stop: 90 +# Short-Description: run cron daemon +# Description: cron is a standard UNIX program that runs user-specified +# programs at periodic scheduled times. vixie cron adds a +# number of features to the basic UNIX cron, including better +# security and more powerful configuration options. +### END INIT INFO + +[ -f /etc/sysconfig/crond ] || { + [ "$1" = "status" ] && exit 4 || exit 6 +} + +RETVAL=0 +prog="crond" +exec=/usr/sbin/crond +lockfile=/var/lock/subsys/crond +config=/etc/sysconfig/crond + +# Source function library. +. /etc/rc.d/init.d/functions + +[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog + +start() { + if [ $UID -ne 0 ] ; then + echo "User has insufficient privilege." + exit 4 + fi + [ -x $exec ] || exit 5 + [ -f $config ] || exit 6 + echo -n $"Starting $prog: " + daemon $prog $CRONDARGS + retval=$? + echo + [ $retval -eq 0 ] && touch $lockfile +} + +stop() { + if [ $UID -ne 0 ] ; then + echo "User has insufficient privilege." + exit 4 + fi + echo -n $"Stopping $prog: " + if [ -n "`pidfileofproc $exec`" ]; then + killproc $exec + RETVAL=3 + else + failure $"Stopping $prog" + fi + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile +} + +restart() { + stop + start +} + +reload() { + echo -n $"Reloading $prog: " + if [ -n "`pidfileofproc $exec`" ]; then + killproc $exec -HUP + else + failure $"Reloading $prog" + fi + retval=$? + echo +} + +force_reload() { + # new configuration takes effect after restart + restart +} + +rh_status() { + # run checks to determine if the service is running or use generic status + status -p /var/run/crond.pid $prog +} + +rh_status_q() { + rh_status >/dev/null 2>&1 +} + + +case "$1" in + start) + rh_status_q && exit 0 + $1 + ;; + stop) + rh_status_q || exit 0 + $1 + ;; + restart) + $1 + ;; + reload) + rh_status_q || exit 7 + $1 + ;; + force-reload) + force_reload + ;; + status) + rh_status + ;; + condrestart|try-restart) + rh_status_q || exit 0 + restart + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" + exit 2 +esac +exit $? + diff --git a/man/Makefile.am b/man/Makefile.am new file mode 100644 index 0000000..f52345b --- /dev/null +++ b/man/Makefile.am @@ -0,0 +1,6 @@ +dist_man_MANS = crontab.1 crontab.5 cron.8 crond.8 +if ANACRON +dist_man_MANS += anacrontab.5 anacron.8 +endif +noinst_MANS = bitstring.3 + diff --git a/man/anacron.8 b/man/anacron.8 new file mode 100644 index 0000000..5c347d8 --- /dev/null +++ b/man/anacron.8 @@ -0,0 +1,167 @@ +.TH ANACRON 8 2009-07-17 "Marcela Mašláňová" "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] [\fB-S spooldir\fR] [\fIjob\fR] +.br +.B anacron \fR[\fB-S spooldir\fR] -u [\fB-t anacrontab\fR] \fR[\fIjob\fR] +.br +.B anacron \fR[\fB-V\fR|\fB-h\fR] +.br +.B anacron -T \fR[\fB-t anacrontab\fR] +.SH DESCRIPTION +Anacron +is 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 are not running 24 hours a day +to control regular jobs as daily, weekly, and monthly jobs. +.PP +Anacron reads a list of jobs from the +.I /etc/anacrontab +configuration file (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 \fBn\fR days, where \fBn\fR is the time period specified +for that job. If a job has not been executed in \fBn\fR days or more, 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 (excludes the hour) in a special +timestamp file for that job, so it knows when to execute that job again. +.PP +When there are no more jobs to be run, Anacron exits. +.PP +Anacron only considers jobs whose identifier, as +specified in \fBanacrontab(5)\fR, matches any of +the +.I job +command-line arguments. The +.I job +command-line arguments can be represented by shell wildcard patterns (be sure to protect them from +your shell with adequate quoting). Specifying no +.I job +command-line arguments is equivalent to specifying "*" (that is, all jobs are +considered by Anacron). +.PP +Unless Anacron is run with the \fB-d\fR option (specified below), it forks to the +background when it starts, and any parent processes exit immediately. +.PP +Unless Anacron is run with the \fB-s\fR or \fB-n\fR options, it starts jobs +immediately when their delay is over. The execution of different jobs is +completely independent. +.PP +If an executed job generates any output to standard output or to standard error, +the output is mailed to the user under whom Anacron is running (usually root), or to +the address specified in the \fBMAILTO\fR environment variable in the +.I /etc/anacrontab +file, if such exists. If the \fBLOGNAME\fR environment variable is set, it is used in the From: field of the mail. +.PP +Any informative messages generated by Anacron are sent to \fBsyslogd(8)\fR +or \fBrsyslogd(8)\fR under with facility set to \fBcron\fR and priority set to \fBnotice\fR. Any error +messages are sent with the priority \fBerror\fR. +.PP +"Active" jobs (i.e. jobs that Anacron already decided +to run and are now waiting for their delay to pass, and jobs that are currently +being executed by +Anacron), are "locked", so that other copies of Anacron cannot run them +at the same time. +.SH OPTIONS +.TP +.B -f +Forces execution of all jobs, ignoring any timestamps. +.TP +.B -u +Updates the timestamps of all jobs to the current date, but +does not run any. +.TP +.B -s +Serializes execution of jobs. Anacron does not start a new job before the +previous one finished. +.TP +.B -n +Runs jobs immediately and ignores the specified delays in the +.I /etc/anacrontab +file. This options implies \fB-s\fR. +.TP +.B -d +Does not fork Anacron to the background. In this mode, Anacron will output informational +messages to standard error, as well as to syslog. The output of any job +is mailed by Anacron. +.TP +.B -q +Suppresses any messages to standard error. Only applicable with \fB-d\fR. +.TP +.B -t some_anacrontab +Uses the specified anacrontab, rather than the +.I /etc/anacrontab +default one. +.TP +.B -T +Anacrontab testing. Tests the +.I /etc/anacrontab +configuration file for validity. If +there is an error in the file, it is shown on the standard output and Anacron +returns the value of 1. Valid anacrontabs return the value of 0. +.TP +.B -S spooldir +Uses the specified spooldir to store timestamps in. This option is required for +users who wish to run anacron themselves. +.TP +.B -V +Prints version information, and exits. +.TP +.B -h +Prints short usage message, and exits. +.SH SIGNALS +After receiving a \fBSIGUSR1\fR signal, Anacron waits for any running +jobs to finish and then exits. This can be used to stop +Anacron cleanly. +.SH NOTES +Make sure your time-zone is set correctly before Anacron is +started since the time-zone affects the date. This is usually accomplished +by setting the TZ environment variable, or by installing a +.I /usr/lib/zoneinfo/localtime +file. See +.B tzset(3) +for more information. + +Timestamp files are created in the spool directory for each job specified in an anacrontab. These files are never removed automatically by Anacron, and should be removed by hand if a job is no longer being scheduled. +.SH FILES +.TP +.I /etc/anacrontab +Contains specifications of jobs. See \fBanacrontab(5)\fR for a complete +description. +.TP +.I /var/spool/anacron +This directory is used by Anacron for storing timestamp files. +.SH "SEE ALSO" +.BR anacrontab (5), cron (8), tzset (3) +.PP +The Anacron +.I README +file. +.SH BUGS +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). +.PP +Mail comments, suggestions and bug reports to Sean 'Shaleh' Perry . +.SH AUTHOR +Anacron was originally conceived and implemented by Christian Schwarz +. +.PP +The current implementation is a complete rewrite by Itai Tzur +. +.PP +The code base was maintained by Sean 'Shaleh' Perry . +.PP +Since 2004, it is maintained by Pascal Hakim . +.PP +For Fedora, Anacron is maintained by Marcela Mašláňová . diff --git a/man/anacrontab.5 b/man/anacrontab.5 new file mode 100644 index 0000000..2ed4446 --- /dev/null +++ b/man/anacrontab.5 @@ -0,0 +1,98 @@ +.TH ANACRONTAB 5 2009-08-17 "Marcela Mašláňová" "Anacron Users' Manual" +.SH NAME +/etc/anacrontab \- configuration file for Anacron +.SH DESCRIPTION +The +.I /etc/anacrontab +configuration file describes the jobs controlled by \fBanacron(8)\fR. It can contain three types of lines: +job-description lines, environment assignments, or empty lines. +.PP +Job-description lines can have the following format: +.PP + period in days delay in minutes job-identifier command +.PP +The +.I period in days +variable specifies the frequency of execution of a job in days. This variable can be represented by an integer or a macro (@daily, @weekly, @monthly), where @daily denotes the same value as the integer 1, @weekly the same as 7, and @monthly specifies that the job is run once a month, independent on the length of the month. +.PP +The +.I delay in minutes +variable specifies the number of minutes anacron waits, if necessary, before executing a job. This variable is represented by an integer where 0 means no delay. +.PP +The +.I job-identifier +variable specifies a unique name of a job which is used in the log files. +.PP +The +.I command +variable specifies the command to execute. The command can either be a command such as \fBls /proc >> /tmp/proc\fR or a command to execute a custom script. +.PP +Environment assignment lines can have the following format: +.PP + VAR=VALUE +.PP +Any spaces around +.I VAR +are removed. No spaces around +.I VALUE +are allowed (unless you want them to be part of the value). The specified assignment +takes effect from the next line until the end of the file, or to the next +assignment of the same variable. +.PP +The +.I START_HOURS_RANGE +variable defines an interval (in hours) when scheduled jobs can be run. In case this time interval is missed, for example, due to a power down, then scheduled jobs are not executed that day. +.PP +The +.I RANDOM_DELAY +variable denotes the maximum number of minutes that will be added to the delay in minutes variable which is specified for each job. A +.I RANDOM_DELAY +set to 12 would therefore add, randomly, between 0 and 12 minutes to the delay in minutes for each job in that particular anacrontab. When set to 0, no random delay is added. +.PP +Empty lines are either blank lines, line containing white spaces only, or +lines with white spaces followed by a '#' followed by an arbitrary comment. +.PP +You can continue a line onto the next line by adding a '\\' at the end of it. +.PP +In case you want to disable Anacron, add the +.I 0anacron +cron job (which is a part of +.IR crontabs(4) ) +into the +.I /etc/cron.hourly/jobs.deny +directory. +.SH EXAMPLE +This example shows how to set up an Anacron job similar in functionality to +.I /etc/crontab +which starts all regular jobs +between 6:00 and 8:00 +.I only. +A +.I RANDOM_DELAY +which can be 30 minutes at the most is specified. Jobs will run serialized in a queue where each job is started only after the previous one is finished. +.nf +# environment variables +SHELL=/bin/sh +PATH=/sbin:/bin:/usr/sbin:/usr/bin +MAILTO=root +RANDOM_DELAY=30 +# Anacron jobs will start between 6am and 8am. +START_HOURS_RANGE=6-8 +# delay will be 5 minutes + RANDOM_DELAY for cron.daily +1 5 cron.daily nice run-parts /etc/cron.daily +7 0 cron.weekly nice run-parts /etc/cron.weekly +@monthly 0 cron.monthly nice run-parts /etc/cron.monthly +.fi +.SH "SEE ALSO" +.BR anacron (8), +.BR crontabs (4) +.PP +The Anacron +.I README +file. +.SH AUTHOR +Itai Tzur +.PP +Currently maintained by Pascal Hakim . +.PP +For Fedora, maintained by Marcela Mašláňová . diff --git a/man/cron.8 b/man/cron.8 new file mode 100644 index 0000000..8f36a51 --- /dev/null +++ b/man/cron.8 @@ -0,0 +1,228 @@ +.\"/* Copyright 1988,1990,1993,1996 by Paul Vixie +.\" * All rights reserved +.\" */ +.\" +.\" Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") +.\" Copyright (c) 1997,2000 by Internet Software Consortium, Inc. +.\" +.\" 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 ISC DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. +.\" +.\" Modified 2010/09/12 by Colin Dean, Durham University IT Service, +.\" to add clustering support. +.\" +.\" $Id: cron.8,v 1.8 2004/01/23 19:03:32 vixie Exp $ +.\" +.TH CRON "8" "July 2010" "Marcela Mašláňová" "Cronie Users' Manual" +.SH NAME +crond \- daemon to execute scheduled commands +.SH SYNOPSIS +.B crond +.RB [ -n " | " -p " | " -s " | " -c " | " -m \fP\fI\fP ] + +.B crond +.B -x +.RB [ext,sch,proc,pars,load,misc,test,bit] +.br +.SH DESCRIPTION +.I Cron +is started from +.I /etc/rc.d/init.d +or +.I /etc/init.d +It returns immediately, thus, there is no need to need to start it with the '&' parameter. +.PP +.I Cron +searches +.I /var/spool/cron +for crontab files which are named after accounts in +.I /etc/passwd; +The found crontabs are loaded into the memory. +.I Cron +also searches for +.I /etc/anacrontab +and any files in the +.I /etc/cron.d +directory, which have a different format (see +.BR crontab (5)). +.I Cron +examines all stored crontabs and checks each job to see if it needs to be +run in the current minute. When executing +commands, any output is mailed to the owner of the crontab (or to the user +specified in the +.I MAILTO +environment variable in the crontab, if such exists). +Any job output can also be sent to syslog by using the +.B "\-s" +option. +.PP +There are two ways how changes in crontables are checked. The first +method is checking the modtime of a file. The second method is using the inotify support. +Using of inotify is logged in the +.I /var/log/cron +log after the daemon is started. The inotify support checks for changes in all crontables and accesses the +hard disk only when a change is detected. +.PP +When using the modtime option, +.I Cron +checks its crontables' modtimes every minute to check for any changes and reloads +the crontables which have changed. There is no need to restart +.I Cron +after some of the +crontables were modified. The modtime option is also used when inotify can not be initialized. +.PP +.I Cron +checks these files and directories: +.IR /etc/anacrontab +system crontab, usually used to run daily, weekly, monthly jobs. See +.BR anacrontab (5) +for more details. +.IR /etc/cron.d/ +directory that contains system cronjobs stored for different users. +.IR /var/spool/cron +directory that contains user crontables created by the +.IR crontab +command. + +Note that the +.BR crontab (1) +command updates the modtime of the spool directory whenever it changes a +crontab. +.PP +.SS Daylight Saving Time and other time changes +Local time changes of less than three hours, such as those caused +by the Daylight Saving Time changes, are handled in a special way. +This only applies to jobs that run at a specific time and jobs that +run with a granularity greater than one hour. Jobs that run +more frequently are scheduled normally. +.PP +If time was adjusted one hour forward, those jobs that would have run in the +interval that has been skipped will be run immediately. +Conversely, if time was adjusted backward, running the same job twice is avoided. +.PP +Time changes of more than 3 hours are considered to be corrections to +the clock or the timezone, and the new time is used immediately. +.PP +It is possible to use different time zones for crontables. See +.IR crontab (5) +for more information. +.SS PAM Access Control +.IR Cron +supports access control with PAM if the system has PAM installed. For more information, see +.IR pam (8). +A PAM configuration file for +.IR crond +is installed in +.IR /etc/pam.d/crond . +The daemon loads the PAM environment from the pam_env module. This +can be overridden by defining specific settings in the appropriate crontab file. +.SH "OPTIONS" +.TP +.B "\-m" +This option allows you to specify a shell command to use for sending +.I Cron +mail output instead of using +.BR sendmail (8) +This command must accept a fully formatted mail message (with headers) on standard input and send it +as a mail message to the recipients specified in the mail headers. Specifying +the string +.I "off" +(i.e. crond -m off) +will disable the sending of mail. +.TP +.B "\-n" +Tells the daemon to run in the foreground. This can be useful when starting it out of init. +.TP +.B "\-p" +Allows +.I Cron +to accept any user set crontables. +.TP +.B "\-c" +This option enables clustering support, as described below. +.TP +.B "\-s" +This option will direct +.I Cron +to send the job output to the system log using +.IR syslog (3). +This is useful if your system does not have +.BR sendmail (8), +installed or if mail is disabled. +.TP +.B "\-x" +This option allows you to set debug flags. +.SH SIGNALS +When the \s-2SIGHUP\s+2 is received, the +.I Cron +daemon will close and reopen its +log file. This proves to be useful in scripts which rotate and age log files. +Naturally, this is not relevant if +.I Cron +was built to use +.IR syslog (3). +.SH CLUSTERING SUPPORT +In this version of +.IR Cron +it is possible to use a network-mounted shared +.I /var/spool/cron +across a cluster of hosts and specify that only one of the hosts should +run the crontab jobs in this directory at any one time. This is done by starting +.I Cron +with the \fB-c\fP option, and have the +.I /var/spool/cron/.cron.hostname +file contain just one line, which represents the hostname of whichever host in the +cluster should run the jobs. If this file does not exist, or the hostname +in it does not match that returned by +.BR gethostname (2) , +then all crontab files in this directory are ignored. This has no effect on +cron jobs specified in the +.I /etc/crontab +file or on files in the +.I /etc/cron.d +directory. These files are always run and considered host-specific. +.PP +Rather than editing +.I /var/spool/cron/.cron.hostname +directly, use the \fB-n\fP option of +.BR crontab (1) +to specify the host. +.PP +You should ensure that all hosts in a cluster, and the file server from which +they mount the shared crontab directory, have closely synchronised clocks, +e.g. using +.BR ntpd (8) +, otherwise the results will be very unpredictable. +.PP +Using cluster sharing automatically disables inotify support, because inotify cannot be +relied on with network-mounted shared file systems. +.SH CAVEATS +All +.BR crontab +files have to be regular files or symlinks to regular files, they must not be executable +or writable for anyone else but the owner. +This requirement can be overridden by using the \fB-p\fP option on the crond command line. +If inotify support is in use, changes in the symlinked crontabs are not automatically +noticed by the cron daemon. The cron daemon must receive a SIGHUP signal to reload the crontabs. +This is a limitation of the inotify API. +.PP +The syslog output will be used instead of mail, when sendmail is not installed. +.SH "SEE ALSO" +.BR crontab (1), +.BR crontab (5), +.BR inotify (7), +.BR pam (8) +.SH AUTHOR +.nf +Paul Vixie +Marcela Mašláňová +Colin Dean diff --git a/man/crond.8 b/man/crond.8 new file mode 100644 index 0000000..64c7c26 --- /dev/null +++ b/man/crond.8 @@ -0,0 +1 @@ +.so man8/cron.8 diff --git a/man/crontab.1 b/man/crontab.1 new file mode 100644 index 0000000..d35331a --- /dev/null +++ b/man/crontab.1 @@ -0,0 +1,186 @@ +.\"/* Copyright 1988,1990,1993 by Paul Vixie +.\" * All rights reserved +.\" */ +.\" +.\" Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") +.\" Copyright (c) 1997,2000 by Internet Software Consortium, Inc. +.\" +.\" 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 ISC DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. +.\" +.\" Modified 2010/09/12 by Colin Dean, Durham University IT Service, +.\" to add clustering support. +.\" +.\" $Id: crontab.1,v 1.7 2004/01/23 19:03:32 vixie Exp $ +.\" +.TH CRONTAB 1 "22 September 2010" +.SH NAME +crontab \- maintains crontab files for individual users +.SH SYNOPSIS +.B crontab +.RB [ -u +.IR user ] " file" +.br +.B crontab +.RB [ -u +.IR user ] +.RB [ -l " | " -r " | " -e ]\ [ -i ] +.RB [ -s ] +.br +.B crontab +.BR -n\ [ +.IR "hostname " ] +.br +.B crontab +.BR -c +.SH DESCRIPTION +.I Crontab +is the program used to install, remove or list the tables +used to serve the +.BR cron (8) +daemon. Each user can have their own crontab, and though these are files in +.IR /var/spool/ , +they are not intended to be edited directly. For SELinux in MLS mode, you can define +more crontabs for each range. For more information, see +.BR selinux (8). +.PP +In this version of +.IR Cron +it is possible to use a network-mounted shared +.I /var/spool/cron +across a cluster of hosts and specify that only one of the hosts should +run the crontab jobs in the particular directory at any one time. You may also use +.BR crontab (1) +from any of these hosts to edit the same shared set of crontab files, and to +set and query which host should run the crontab jobs. +.PP +Running cron jobs can be allowed or disallowed for different users. For this purpose, use the +.I cron.allow +and +.I cron.deny +files. +If the +.I cron.allow +file exists, a user must be listed in it to be allowed to use cron +If the +.I cron.allow +file does not exist but the +.I cron.deny +file does exist, then a user must \fInot\fR be listed in the +.I cron.deny +file in order to use cron. If neither of these files exists, +only the super user is allowed to use cron. +Another way to restrict access to cron is to use PAM authentication to set up users, +which are allowed or disallowed to use +.I crontab +or modify system cron jobs in the +.IR /etc/cron.d/ +directory. +.PP +The temporary directory can be set in an environment variable. If it is not set +by the user, the +.I /tmp +directory is used. +.PP +.SH "OPTIONS" +.TP +.B "\-u" +Appends the name of the user whose crontab is to be modified. If this option +is not used, +.I crontab +examines "your" crontab, i.e., the crontab of the person executing the +command. Note that +.BR su (8) +may confuse +.IR crontab , +thus, when executing commands under +.BR su (8) +you should always use the +.B -u +option. If no crontab exists for a particular user, it is created for him the first time the +.B crontab -u +command is used under his username. +.TP +.B "\-l" +Displays the current crontab on standard output. +.TP +.B "\-r" +Removes the current crontab. +.TP +.B "\-e" +Edits the current crontab using the editor specified by +the \s-1VISUAL\s+1 or \s-1EDITOR\s+1 environment variables. After you exit +from the editor, the modified crontab will be installed automatically. +.TP +.B "\-i" +This option modifies the +.B "\-r" +option to prompt the user for a 'y/Y' response +before actually removing the crontab. +.TP +.B "\-s" +Appends the current SELinux security context string as an +MLS_LEVEL setting to the crontab file before editing / replacement +occurs - see the documentation of MLS_LEVEL in +.BR crontab(5)\. +.TP +.B "\-n" +This option is relevant only if +.BR cron (8) +was started with the \fB-c\fP option, to enable clustering support. It is +used to set the host in the cluster which should run the jobs specified in the +crontab files in the +.I /var/spool/cron +directory. +If a hostname is supplied, the host whose hostname returned by +.BR gethostname(2) +matches the supplied hostname, will be selected to run the selected cron jobs subsequently. If there +is no host in the cluster matching the supplied hostname, or you explicitly specify +an empty hostname, then the selected jobs will not be run at all. If the hostname +is omitted, the name of the local host returned by +.BR gethostname(2) +is used. Using this option has no effect on the +.I /etc/crontab +file and the files in the +.I /etc/cron.d +directory, which are always run, and considered host-specific. For more +information on clustering support, see +.BR cron (8)\. +.TP +.B "\-c" +This option is only relevant if +.BR cron (8) +was started with the \fB-c\fP option, to enable clustering support. It is +used to query which host in the cluster is currently set to run the jobs +specified in the crontab files in the directory +.I /var/spool/cron +, as set using the \fB-n\fP option. +.SH "SEE ALSO" +.BR crontab (5), cron (8) +.SH FILES +.nf +/etc/cron.allow +/etc/cron.deny +.fi +.SH STANDARDS +The +.I crontab +command conforms to IEEE Std1003.2-1992 (``POSIX''). This new command syntax +differs from previous versions of Vixie Cron, as well as from the classic +SVR3 syntax. +.SH DIAGNOSTICS +An informative usage message appears if you run a crontab with a faulty command +defined in it. +.SH AUTHOR +.nf +Paul Vixie +Colin Dean diff --git a/man/crontab.5 b/man/crontab.5 new file mode 100644 index 0000000..0132fc7 --- /dev/null +++ b/man/crontab.5 @@ -0,0 +1,309 @@ +.\"/* Copyright 1988,1990,1993,1994 by Paul Vixie +.\" * All rights reserved +.\" */ +.\" +.\" Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") +.\" Copyright (c) 1997,2000 by Internet Software Consortium, Inc. +.\" +.\" 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 ISC DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. +.\" +.\" $Id: crontab.5,v 1.6 2004/01/23 19:03:33 vixie Exp $ +.\" +.TH ANACRONTAB 5 "July 2010" "Marcela Mašláňová" "Cronie Users' Manual" +.SH NAME +crontab \- files used to schedule the execution of programs +.SH DESCRIPTION +A +.I crontab +file contains instructions for the +.BR cron (8) +daemon in the following simplified manner: "run this command at this time on this date". +Each user can define their own crontab. Commands defined in any given crontab are +executed under the user who owns that particular crontab. Uucp and News usually have +their own crontabs, eliminating the need for explicitly running +.BR su (1) +as part of a cron command. +.PP +Blank lines, leading spaces, and tabs are ignored. Lines whose first +non-white space character is a pound-sign (#) are comments, and are note processed. +Note that comments are not allowed on the same line as cron commands, since +they are considered a part of the command. Similarly, comments are not +allowed on the same line as environment variable settings. +.PP +An active line in a crontab is either an environment setting or a cron +command. An environment setting is of the form: +.PP + name = value +.PP +where the white spaces around the equal-sign (=) are optional, and any subsequent +non-leading white spaces in +.I value +is a part of the value assigned to +.IR name . +The +.I value +string may be placed in quotes (single or double, but matching) to preserve +leading or trailing white spaces. +.PP +Several environment variables are set up +automatically by the +.BR cron (8) +daemon. +.I SHELL +is set to /bin/sh, and +.I LOGNAME +and +.I HOME +are set from the /etc/passwd line of the crontab\'s owner. +.I HOME +and +.I SHELL +can be overridden by settings in the crontab; LOGNAME can not. +.PP +(Note: the +.I LOGNAME +variable is sometimes called +.I USER +on BSD systems and is also automatically set). +.PP +In addition to +.IR LOGNAME , +.IR HOME , +and +.IR SHELL , +.BR cron (8) +looks at the +.I MAILTO +variable if a mail needs to be send as a result of running +any commands in that particular crontab. If +.I MAILTO +is defined (and non-empty), mail is +sent to the specified address. If +.I MAILTO +is defined but empty (\fIMAILTO=""\fR), no +mail is sent. Otherwise, mail is sent to the owner of the crontab. This +option is useful if you decide to use /bin/mail instead of /usr/lib/sendmail as +your mailer. Note that /bin/mail does not provide aliasing and UUCP +usually does not read its mail. If +.I MAILFROM +is defined (and non-empty), it +is used as the envelope sender address, otherwise, ``root'' is used. +.PP +By default, cron sends a mail using the 'Content-Type:' header of 'text/plain' +with the 'charset=' parameter set to the 'charmap/codeset' of the locale in which +.BR crond (8) +is started up - i.e. either the default system locale, if no LC_* environment +variables are set, or the locale specified by the LC_* environment variables +(see +.BR locale (7)). +Different character encodings can be used for mailing cron job outputs by +setting the +.I CONTENT_TYPE +and +.I CONTENT_TRANSFER_ENCODING +variables in a crontab to the correct values of the mail headers of those names. +.PP +The +.I CRON_TZ +variable specifies the time zone specific for the cron table. +The user should enter a time according to the specified time zone into the table. +The time used for writing into a log file is taken from the local time zone, where the +daemon is running. +.PP +The +.I MLS_LEVEL +environment variable provides support for multiple per-job +SELinux security contexts in the same crontab. +By default, cron jobs execute with the default SELinux security context of the +user that created the crontab file. +When using multiple security levels and roles, this may not be sufficient, because +the same user may be running in different roles or in different security levels. +For more information about roles and SELinux MLS/MCS, see +.BR selinux (8) +and the crontab example mentioned later on in this text. +You can set the +.I MLS_LEVEL +variable to the SELinux security context string specifying +the particular SELinux security context in which you want jobs to be run. +.B crond +will then set the execution context of those jobs that meet the specifications of the particular security context. +For more information, see +.BR crontab (1)\ -s\ option. +.PP +The format of a cron command is similar to the V7 standard, with a number of +upward-compatible extensions. Each line has five time-and-date fields +followed by a +.BR user name +(if this is the +.BR system +crontab file), and followed by a command. Commands are executed by +.BR cron (8) +when the 'minute', 'hour', and 'month of the year' fields match the current time, +.I and +at least one of the two 'day' fields ('day of month', or 'day of week') +match the current time (see "Note" below). +.PP +Note that this means that non-existent times, such as the "missing hours" +during the daylight savings time conversion, will never match, causing jobs +scheduled during the "missing times" not to be run. Similarly, times +that occur more than once (again, during the daylight savings time conversion) +will cause matching jobs to be run twice. +.PP +.BR cron (8) +examines cron entries every minute. +.PP +The time and date fields are: +.IP +.ta 1.5i +field allowed values +.br +----- -------------- +.br +minute 0-59 +.br +hour 0-23 +.br +day of month 1-31 +.br +month 1-12 (or names, see below) +.br +day of week 0-7 (0 or 7 is Sunday, or use names) +.br +.PP +A field may contain an asterisk (*), which always stands for "first\-last". +.PP +Ranges of numbers are allowed. Ranges are two numbers separated +with a hyphen. The specified range is inclusive. For example, +8-11 for an 'hours' entry specifies execution at hours 8, 9, 10, +and 11. +.PP +Lists are allowed. A list is a set of numbers (or ranges) +separated by commas. Examples: "1,2,5,9", "0-4,8-12". +.PP +Step values can be used in conjunction with ranges. Following +a range with "/" specifies skips of the number's value +through the range. For example, "0-23/2" can be used in the 'hours' +field to specify command execution for every other hour (the alternative +in the V7 standard is "0,2,4,6,8,10,12,14,16,18,20,22"). Step values are +also permitted after an asterisk, so if specifying a job to be run every two +hours, you can use "*/2". +.PP +Names can also be used for the 'month' and 'day of week' +fields. Use the first three letters of the particular +day or month (case does not matter). Ranges or +lists of names are not allowed. +.PP +The "sixth" field (the rest of the line) specifies the command to be +run. +The entire command portion of the line, up to a newline or a "%" +character, will be executed by /bin/sh or by the shell +specified in the SHELL variable of the cronfile. +A "%" character in the command, unless escaped with a backslash +(\\), will be changed into newline characters, and all data +after the first % will be sent to the command as standard +input. +.PP +Note: The day of a command's execution can be specified in the following two +fields \(em 'day of month', and 'day of week'. If both fields are +restricted (i.e., do not contain the "*" character), the command will be run when +.I either +field matches the current time. For example, +.br +"30 4 1,15 * 5" +would cause a command to be run at 4:30 am on the 1st and 15th of each +month, plus every Friday. +.SH EXAMPLE CRON FILE +.nf +# use /bin/sh to run commands, no matter what /etc/passwd says +SHELL=/bin/sh +# mail any output to `paul', no matter whose crontab this is +MAILTO=paul +# +CRON_TZ=Japan +# run five minutes after midnight, every day +5 0 * * * $HOME/bin/daily.job >> $HOME/tmp/out 2>&1 +# run at 2:15pm on the first of every month -- output mailed to paul +15 14 1 * * $HOME/bin/monthly +# run at 10 pm on weekdays, annoy Joe +0 22 * * 1-5 mail -s "It's 10pm" joe%Joe,%%Where are your kids?% +23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday" +5 4 * * sun echo "run at 5 after 4 every sunday" +.fi +.SH Jobs in /etc/cron.d/ +The jobs in +.I cron.d +and +.I /etc/crontab +are system jobs, which are used usually for more than +one user, thus, the username is needed. MAILTO on the first line +is optional. +.SH EXAMPLE OF A JOB IN /etc/cron.d/job +.nf +#login as root +#create job with preferred editor (e.g. vim) +MAILTO=root +* * * * * root touch /tmp/file +.fi +.SH SELinux with multi level security (MLS) +In a crontab, it is important to specify a security level by \fIcrontab\ -s\fR or specifying +the required level on the first line of the crontab. Each level is specified +in \fI/etc/selinux/targeted/seusers\fR. When using crontab in the MLS mode, it is especially important to: +.br +- check/change the actual role, +.br +- set correct \fIrole for directory\fR, which is used for input/output. +.SH EXAMPLE FOR SELINUX MLS +.nf +# login as root +newrole -r sysadm_r +mkdir /tmp/SystemHigh +chcon -l SystemHigh /tmp/SystemHigh +crontab -e +# write in crontab file +MLS_LEVEL=SystemHigh +0-59 * * * * id -Z > /tmp/SystemHigh/crontest +.fi +.SH FILES +.I /etc/anacrontab +system crontab file for jobs like cron.daily, weekly, monthly. +.I /var/spool/cron/ +a directory for storing crontabs defined by users. +.I /etc/cron.d/ +a directory for storing system crontables. +.SH "SEE ALSO" +.BR cron (8), +.BR crontab (1) +.SH EXTENSIONS +These special time specification "nicknames" which replace +the 5 initial time and date fields, and are prefixed with the '@' character, are supported: +.nf +@reboot : Run once after reboot. +@yearly : Run once a year, ie. "0 0 1 1 *". +@annually : Run once a year, ie. "0 0 1 1 *". +@monthly : Run once a month, ie. "0 0 1 * *". +@weekly : Run once a week, ie. "0 0 * * 0". +@daily : Run once a day, ie. "0 0 * * *". +@hourly : Run once an hour, ie. "0 * * * *". +.fi +.SH CAVEATS +.BR crontab +files have to be regular files or symlinks to regular files, they must not be executable +or writable for anyone else but the owner. +This requirement can be overridden by using the \fB-p\fP option on the crond command line. +If inotify support is in use, changes in the symlinked crontabs are not automatically +noticed by the cron daemon. The cron daemon must receive a SIGHUP signal to reload the crontabs. +This is a limitation of the inotify API. + +.SH AUTHOR +.nf +Paul Vixie diff --git a/pam/crond b/pam/crond new file mode 100644 index 0000000..37a6906 --- /dev/null +++ b/pam/crond @@ -0,0 +1,10 @@ +# +# The PAM configuration file for the cron daemon +# +# +# No PAM authentication called, auth modules not needed +account required pam_access.so +account include password-auth +session required pam_loginuid.so +session include password-auth +auth include password-auth diff --git a/src/.indent.pro b/src/.indent.pro new file mode 100644 index 0000000..36235da --- /dev/null +++ b/src/.indent.pro @@ -0,0 +1,32 @@ +--blank-before-sizeof +--brace-indent0 +--braces-on-func-def-line +--braces-on-if-line +--braces-on-struct-decl-line +--break-after-boolean-operator +--case-brace-indentation0 +--case-indentation0 +--comment-indentation0 +--continuation-indentation4 +--cuddle-do-while +--declaration-comment-column0 +--declaration-indentation0 +--dont-break-function-decl-args +--dont-break-procedure-type +--dont-line-up-parentheses +--honour-newlines +--indent-level4 +--line-length80 +--paren-indentation4 +--preprocessor-indentation1 +--no-blank-lines-after-commas +--space-after-cast +--space-after-for +--space-after-if +--space-after-while +--space-special-semicolon +--no-space-after-parentheses +--no-space-after-function-call-names +--start-left-side-of-comments +--struct-brace-indentation4 +--tab-size4 diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..a3f57c8 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,73 @@ +# Makefile.am - two binaries crond and crontab + +sbin_PROGRAMS = crond +bin_PROGRAMS = crontab + +crond_SOURCES = \ + cron.c database.c user.c job.c do_command.c popen.c \ + $(common_src) +crontab_SOURCES = crontab.c $(common_src) +common_src = entry.c env.c misc.c pw_dup.c security.c cron.h \ + externs.h funcs.h globals.h macros.h pathnames.h structs.h \ + bitstring.h +common_nodist = cron-paths.h +nodist_crond_SOURCES = $(common_nodist) +nodist_crontab_SOURCES = $(common_nodist) +BUILT_SOURCES = $(common_nodist) + + +LDADD = $(LIBSELINUX) $(LIBPAM) $(LIBAUDIT) + +## if DEBUG +## noinst_PROGRAMS = debug +## endif + +# This header contains all the paths. +# If they are configurable, they are declared in configure script. +# Depends on this Makefile, because it uses make variables. +# CCD 2010/09/10 added CRON_HOSTNAME for clustered-cron. +cron-paths.h: Makefile + @echo 'creating $@' + @sed >$@ 's/ *\\$$//' <<\END #\ + /* This file has been automatically generated. Do not edit. */ \ + \ + #ifndef _CRON_PATHS_H_ \ + #define _CRON_PATHS_H_ \ + \ + /* SPOOLDIR is where the crontabs live. \ + * This directory will have its modtime updated \ + * whenever crontab(1) changes a crontab; this is \ + * the signal for cron(8) to look at each individual \ + * crontab file and reload those whose modtimes are \ + * newer than they were last time around (or which \ + * didn't exist last time around...) \ + * or it will be checked by inotify \ + */ \ + #define SPOOL_DIR "$(SPOOL_DIR)" \ + \ + /* CRON_HOSTNAME is file in SPOOL_DIR which, if it \ + * exists, and does not just contain a line matching \ + * the name returned by gethostname(), causes all \ + * crontabs in SPOOL_DIR to be ignored. This is \ + * intended to be used when clustering hosts sharing \ + * one NFS-mounted SPOOL_DIR, and where only one host \ + * should use the crontab files here at any one time. \ + */ \ + #define CRON_HOSTNAME ".cron.hostname" \ + \ + /* cron allow/deny file. At least cron.deny must \ + * exist for ordinary users to run crontab. \ + */ \ + #define CRON_ALLOW "$(sysconfdir)/cron.allow" \ + #define CRON_DENY "$(sysconfdir)/cron.deny" \ + \ + /* 4.3BSD-style crontab f.e. /etc/crontab */ \ + #define SYSCRONTAB "$(SYSCRONTAB)" \ + \ + /* system crontab dir f.e. /etc/cron.d/ */ \ + #define SYS_CROND_DIR "$(SYS_CROND_DIR)" \ + \ + #define SYSCONFDIR "$(sysconfdir)" \ + \ + #endif /* _CRON_PATHS_H_ */ \ + END diff --git a/src/bitstring.h b/src/bitstring.h new file mode 100644 index 0000000..43bd843 --- /dev/null +++ b/src/bitstring.h @@ -0,0 +1,141 @@ +/* $NetBSD: bitstring.h,v 1.3 2003/08/07 11:17:08 agc Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Paul Vixie. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)bitstring.h 8.1 (Berkeley) 7/19/93 + */ + +#ifndef _BITSTRING_H_ +#define _BITSTRING_H_ + +typedef unsigned char bitstr_t; + +/* internal macros */ + /* byte of the bitstring bit is in */ +#define _bit_byte(bit) \ + ((bit) >> 3) + + /* mask for the bit within its byte */ +#define _bit_mask(bit) \ + (1 << ((bit)&0x7)) + +/* external macros */ + /* bytes in a bitstring of nbits bits */ +#define bitstr_size(nbits) \ + ((((nbits) - 1) >> 3) + 1) + + /* allocate a bitstring */ +#define bit_alloc(nbits) \ + (bitstr_t *)calloc(1, \ + (unsigned int)bitstr_size(nbits) * sizeof(bitstr_t)) + + /* allocate a bitstring on the stack */ +#define bit_decl(name, nbits) \ + (name)[bitstr_size(nbits)] + + /* is bit N of bitstring name set? */ +#define bit_test(name, bit) \ + ((name)[_bit_byte(bit)] & _bit_mask(bit)) + + /* set bit N of bitstring name */ +#define bit_set(name, bit) \ + (name)[_bit_byte(bit)] |= _bit_mask(bit) + + /* clear bit N of bitstring name */ +#define bit_clear(name, bit) \ + (name)[_bit_byte(bit)] &= ~_bit_mask(bit) + + /* clear bits start ... stop in bitstring */ +#define bit_nclear(name, start, stop) { \ + register bitstr_t *_name = name; \ + register int _start = start, _stop = stop; \ + register int _startbyte = _bit_byte(_start); \ + register int _stopbyte = _bit_byte(_stop); \ + if (_startbyte == _stopbyte) { \ + _name[_startbyte] &= ((0xff >> (8 - (_start&0x7))) | \ + (0xff << ((_stop&0x7) + 1))); \ + } else { \ + _name[_startbyte] &= 0xff >> (8 - (_start&0x7)); \ + while (++_startbyte < _stopbyte) \ + _name[_startbyte] = 0; \ + _name[_stopbyte] &= 0xff << ((_stop&0x7) + 1); \ + } \ +} + + /* set bits start ... stop in bitstring */ +#define bit_nset(name, start, stop) { \ + register bitstr_t *_name = name; \ + register int _start = start, _stop = stop; \ + register int _startbyte = _bit_byte(_start); \ + register int _stopbyte = _bit_byte(_stop); \ + if (_startbyte == _stopbyte) { \ + _name[_startbyte] |= ((0xff << (_start&0x7)) & \ + (0xff >> (7 - (_stop&0x7)))); \ + } else { \ + _name[_startbyte] |= 0xff << ((_start)&0x7); \ + while (++_startbyte < _stopbyte) \ + _name[_startbyte] = 0xff; \ + _name[_stopbyte] |= 0xff >> (7 - (_stop&0x7)); \ + } \ +} + + /* find first bit clear in name */ +#define bit_ffc(name, nbits, value) { \ + register bitstr_t *_name = name; \ + register int _byte, _nbits = nbits; \ + register int _stopbyte = _bit_byte(_nbits), _value = -1; \ + for (_byte = 0; _byte <= _stopbyte; ++_byte) \ + if (_name[_byte] != 0xff) { \ + _value = _byte << 3; \ + for (_stopbyte = _name[_byte]; (_stopbyte&0x1); \ + ++_value, _stopbyte >>= 1); \ + break; \ + } \ + *(value) = _value; \ +} + + /* find first bit set in name */ +#define bit_ffs(name, nbits, value) { \ + register bitstr_t *_name = name; \ + register int _byte, _nbits = nbits; \ + register int _stopbyte = _bit_byte(_nbits), _value = -1; \ + for (_byte = 0; _byte <= _stopbyte; ++_byte) \ + if (_name[_byte]) { \ + _value = _byte << 3; \ + for (_stopbyte = _name[_byte]; !(_stopbyte&0x1); \ + ++_value, _stopbyte >>= 1); \ + break; \ + } \ + *(value) = _value; \ +} + +#endif /* !_BITSTRING_H_ */ diff --git a/src/cron.c b/src/cron.c new file mode 100644 index 0000000..7dc2958 --- /dev/null +++ b/src/cron.c @@ -0,0 +1,664 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* + * Modified 2010/09/12 by Colin Dean, Durham University IT Service, + * to add clustering support. + */ + +#define MAIN_PROGRAM + +#include + +#if defined WITH_INOTIFY +int inotify_enabled; +#else +# define inotify_enabled 0 +#endif + +enum timejump { negative, small, medium, large }; + +static void usage(void), +run_reboot_jobs(cron_db *), +find_jobs(int, cron_db *, int, int, long), +set_time(int), +cron_sleep(int, cron_db *), +sigchld_handler(int), +sighup_handler(int), +sigchld_reaper(void), quit(int), parse_args(int c, char *v[]); + +static volatile sig_atomic_t got_sighup, got_sigchld; +static int timeRunning, virtualTime, clockTime; +static long GMToff; +static int DisableInotify; + +#if defined WITH_INOTIFY + +/* + * Note that inotify isn't safe to use with clustering, as changes made + * to a shared filesystem on one system cannot be relied on to be notified + * on another system, so use of inotify is disabled at runtime if run with + * clustering enabled. + */ + +# define NUM_WATCHES 3 + +int wd[NUM_WATCHES]; +const char *watchpaths[NUM_WATCHES] = {SPOOL_DIR, SYS_CROND_DIR, SYSCRONTAB}; + +void set_cron_unwatched(int fd) { + int i; + + for (i = 0; i < sizeof (wd) / sizeof (wd[0]); ++i) { + if (wd[i] < 0) { + inotify_rm_watch(fd, wd[i]); + wd[i] = -1; + } + } +} + +void set_cron_watched(int fd) { + pid_t pid = getpid(); + int i; + + if (fd < 0) { + inotify_enabled = 0; + return; + } + + for (i = 0; i < sizeof (wd) / sizeof (wd[0]); ++i) { + int w; + + if (open(watchpaths[i], O_RDONLY | O_NONBLOCK, 0) != -1) { + w = inotify_add_watch(fd, watchpaths[i], + IN_CREATE | IN_CLOSE_WRITE | IN_ATTRIB | IN_MODIFY | IN_MOVED_TO | + IN_MOVED_FROM | IN_MOVE_SELF | IN_DELETE | IN_DELETE_SELF); + if (w < 0) { + if (wd[i] != -1) { + log_it("CRON", pid, "This directory or file can't be watched", + watchpaths[i], errno); + log_it("CRON", pid, "INFO", "running without inotify support", 0); + } + inotify_enabled = 0; + set_cron_unwatched(fd); + return; + } + wd[i] = w; + } + } + + if (!inotify_enabled) { + log_it("CRON", pid, "INFO", "running with inotify support", 0); + } + + inotify_enabled = 1; +} +#endif + +static void handle_signals(cron_db * database) { + if (got_sighup) { + got_sighup = 0; +#if defined WITH_INOTIFY + /* watches must be reinstated on reload */ + if (inotify_enabled && (EnableClustering != 1)) { + set_cron_unwatched(database->ifd); + inotify_enabled = 0; + } +#endif + database->mtime = (time_t) 0; + log_close(); + } + + if (got_sigchld) { + got_sigchld = 0; + sigchld_reaper(); + } +} + +static void usage(void) { + const char **dflags; + + fprintf(stderr, "usage: %s [-h] print this message \n \ + [-i] deamon runs without inotify support \n \ + [-m ] off or specify prefered client for sending mails \n \ + [-n] run in foreground \n \ + [-p] permit any crontab \n \ + [-c] enable clustering support \n \ + [-s] log into syslog instead of sending mails \n \ + [-x [", + ProgramName); + for (dflags = DebugFlagNames; *dflags; dflags++) + fprintf(stderr, "%s%s", *dflags, dflags[1] ? "," : "]"); + fprintf(stderr, "] print debug information\n"); + exit(ERROR_EXIT); +} + +int main(int argc, char *argv[]) { + struct sigaction sact; + cron_db database; + int fd; + char *cs; + pid_t pid = getpid(); + long oldGMToff; +#if defined WITH_INOTIFY + int i; +#endif + + ProgramName = argv[0]; + MailCmd[0] = '\0'; + cron_default_mail_charset[0] = '\0'; + + setlocale(LC_ALL, ""); + +#if defined(BSD) + setlinebuf(stdout); + setlinebuf(stderr); +#endif + + SyslogOutput = 0; + NoFork = 0; + parse_args(argc, argv); + + bzero((char *) &sact, sizeof sact); + sigemptyset(&sact.sa_mask); + sact.sa_flags = 0; +#ifdef SA_RESTART + sact.sa_flags |= SA_RESTART; +#endif + sact.sa_handler = sigchld_handler; + (void) sigaction(SIGCHLD, &sact, NULL); + sact.sa_handler = sighup_handler; + (void) sigaction(SIGHUP, &sact, NULL); + sact.sa_handler = quit; + (void) sigaction(SIGINT, &sact, NULL); + (void) sigaction(SIGTERM, &sact, NULL); + + acquire_daemonlock(0); + set_cron_uid(); + check_spool_dir(); + + if (putenv("PATH=" _PATH_DEFPATH) < 0) { + log_it("CRON", pid, "DEATH", "can't putenv PATH", errno); + exit(1); + } + + /* Get the default locale character set for the mail + * "Content-Type: ...; charset=" header + */ + setlocale(LC_ALL, ""); /* set locale to system defaults or to + * that specified by any LC_* env vars */ + if ((cs = nl_langinfo(CODESET)) != 0L) + strncpy(cron_default_mail_charset, cs, MAX_ENVSTR); + else + strcpy(cron_default_mail_charset, "US-ASCII"); + + /* if there are no debug flags turned on, fork as a daemon should. + */ + if (DebugFlags) { +#if DEBUGGING + (void) fprintf(stderr, "[%ld] cron started\n", (long) getpid()); +#endif + } + else if (NoFork == 0) { + switch (fork()) { + case -1: + log_it("CRON", pid, "DEATH", "can't fork", errno); + exit(0); + break; + case 0: + /* child process */ + (void) setsid(); + if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) >= 0) { + (void) dup2(fd, STDIN); + (void) dup2(fd, STDOUT); + (void) dup2(fd, STDERR); + if (fd != STDERR) + (void) close(fd); + } + log_it("CRON", getpid(), "STARTUP", PACKAGE_VERSION, 0); + break; + default: + /* parent process should just die */ + _exit(0); + } + } + + if (access("/usr/sbin/sendmail", X_OK) != 0) { + SyslogOutput=1; + log_it("CRON", pid, "INFO","Syslog will be used instead of sendmail.", errno); + } + + pid = getpid(); + acquire_daemonlock(0); + database.head = NULL; + database.tail = NULL; + database.mtime = (time_t) 0; + + load_database(&database); + + fd = -1; +#if defined WITH_INOTIFY + if (DisableInotify || EnableClustering) { + log_it("CRON", getpid(), "No inotify - daemon runs with -i or -c option", + "", 0); + } + else { + for (i = 0; i < sizeof (wd) / sizeof (wd[0]); ++i) { + /* initialize to negative number other than -1 + * so an eventual error is reported for the first time + */ + wd[i] = -2; + } + + database.ifd = fd = inotify_init(); + fcntl(fd, F_SETFD, FD_CLOEXEC); + if (fd < 0) + log_it("CRON", pid, "INFO", "Inotify init failed", errno); + set_cron_watched(fd); + } +#endif + + set_time(TRUE); + run_reboot_jobs(&database); + timeRunning = virtualTime = clockTime; + oldGMToff = GMToff; + + /* + * Too many clocks, not enough time (Al. Einstein) + * These clocks are in minutes since the epoch, adjusted for timezone. + * virtualTime: is the time it *would* be if we woke up + * promptly and nobody ever changed the clock. It is + * monotonically increasing... unless a timejump happens. + * At the top of the loop, all jobs for 'virtualTime' have run. + * timeRunning: is the time we last awakened. + * clockTime: is the time when set_time was last called. + */ + while (TRUE) { + int timeDiff; + enum timejump wakeupKind; + + /* ... wait for the time (in minutes) to change ... */ + do { + cron_sleep(timeRunning + 1, &database); + set_time(FALSE); + } while (clockTime == timeRunning); + timeRunning = clockTime; + + /* + * Calculate how the current time differs from our virtual + * clock. Classify the change into one of 4 cases. + */ + timeDiff = timeRunning - virtualTime; + check_orphans(&database); +#if defined WITH_INOTIFY + if (inotify_enabled) { + check_inotify_database(&database); + } + else { + if (load_database(&database) && (EnableClustering != 1)) + /* try reinstating the watches */ + set_cron_watched(fd); + } +#else + load_database(&database); +#endif + + /* shortcut for the most common case */ + if (timeDiff == 1) { + virtualTime = timeRunning; + oldGMToff = GMToff; + find_jobs(virtualTime, &database, TRUE, TRUE, oldGMToff); + } + else { + if (timeDiff > (3 * MINUTE_COUNT) || timeDiff < -(3 * MINUTE_COUNT)) + wakeupKind = large; + else if (timeDiff > 5) + wakeupKind = medium; + else if (timeDiff > 0) + wakeupKind = small; + else + wakeupKind = negative; + + switch (wakeupKind) { + case small: + /* + * case 1: timeDiff is a small positive number + * (wokeup late) run jobs for each virtual + * minute until caught up. + */ + Debug(DSCH, ("[%ld], normal case %d minutes to go\n", + (long) pid, timeDiff)) + do { + if (job_runqueue()) + sleep(10); + virtualTime++; + if (virtualTime >= timeRunning) + /* always run also the other timezone jobs in the last step */ + oldGMToff = GMToff; + find_jobs(virtualTime, &database, TRUE, TRUE, oldGMToff); + } while (virtualTime < timeRunning); + break; + + case medium: + /* + * case 2: timeDiff is a medium-sized positive + * number, for example because we went to DST + * run wildcard jobs once, then run any + * fixed-time jobs that would otherwise be + * skipped if we use up our minute (possible, + * if there are a lot of jobs to run) go + * around the loop again so that wildcard jobs + * have a chance to run, and we do our + * housekeeping. + */ + Debug(DSCH, ("[%ld], DST begins %d minutes to go\n", + (long) pid, timeDiff)) + /* run wildcard jobs for current minute */ + find_jobs(timeRunning, &database, TRUE, FALSE, GMToff); + + /* run fixed-time jobs for each minute missed */ + do { + if (job_runqueue()) + sleep(10); + virtualTime++; + if (virtualTime >= timeRunning) + /* always run also the other timezone jobs in the last step */ + oldGMToff = GMToff; + find_jobs(virtualTime, &database, FALSE, TRUE, oldGMToff); + set_time(FALSE); + } while (virtualTime < timeRunning && clockTime == timeRunning); + break; + + case negative: + /* + * case 3: timeDiff is a small or medium-sized + * negative num, eg. because of DST ending. + * Just run the wildcard jobs. The fixed-time + * jobs probably have already run, and should + * not be repeated. Virtual time does not + * change until we are caught up. + */ + Debug(DSCH, ("[%ld], DST ends %d minutes to go\n", + (long) pid, timeDiff)) + find_jobs(timeRunning, &database, TRUE, FALSE, GMToff); + break; + default: + /* + * other: time has changed a *lot*, + * jump virtual time, and run everything + */ + Debug(DSCH, ("[%ld], clock jumped\n", (long) pid)) + virtualTime = timeRunning; + oldGMToff = GMToff; + find_jobs(timeRunning, &database, TRUE, TRUE, GMToff); + } + } + + /* Jobs to be run (if any) are loaded; clear the queue. */ + job_runqueue(); + + handle_signals(&database); + } + +#if defined WITH_INOTIFY + if (inotify_enabled && (EnableClustering != 1)) + set_cron_unwatched(fd); + + if (fd >= 0 && close(fd) < 0) + log_it("CRON", pid, "INFO", "Inotify close failed", errno); +#endif +} + +static void run_reboot_jobs(cron_db * db) { + user *u; + entry *e; + int reboot; + pid_t pid = getpid(); + + /* lock exist - skip reboot jobs */ + if (access(REBOOT_LOCK, F_OK) == 0) { + log_it("CRON", pid, "INFO", + "@reboot jobs will be run at computer's startup.", 0); + return; + } + /* lock doesn't exist - create lock, run reboot jobs */ + if ((reboot = creat(REBOOT_LOCK, S_IRUSR & S_IWUSR)) < 0) + log_it("CRON", pid, "INFO", "Can't create lock for reboot jobs.", + errno); + else + close(reboot); + + for (u = db->head; u != NULL; u = u->next) { + for (e = u->crontab; e != NULL; e = e->next) { + if (e->flags & WHEN_REBOOT) + job_add(e, u); + } + } + (void) job_runqueue(); +} + +static void find_jobs(int vtime, cron_db * db, int doWild, int doNonWild, long vGMToff) { + char *orig_tz, *job_tz; + time_t virtualSecond = vtime * SECONDS_PER_MINUTE; + time_t virtualGMTSecond = virtualSecond - vGMToff; + struct tm *tm; + int minute, hour, dom, month, dow; + user *u; + entry *e; + const char *uname; + + /* The support for the job-specific timezones is not perfect. There will + * be jobs missed or run twice during the DST change in the job timezone. + * It is recommended not to schedule any jobs during the hour when + * the DST changes happen if job-specific timezones are used. + * + * Make 0-based values out of tm values so we can use them as indicies + */ +#define maketime(tz1, tz2) do { \ + char *t = tz1; \ + if (t != NULL && *t != '\0') { \ + setenv("TZ", t, 1); \ + tm = localtime(&virtualGMTSecond); \ + } else { if ((tz2) != NULL) \ + setenv("TZ", (tz2), 1); \ + else \ + unsetenv("TZ"); \ + tm = gmtime(&virtualSecond); \ + } \ + minute = tm->tm_min -FIRST_MINUTE; \ + hour = tm->tm_hour -FIRST_HOUR; \ + dom = tm->tm_mday -FIRST_DOM; \ + month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; \ + dow = tm->tm_wday -FIRST_DOW; \ + } while (0) + + orig_tz = getenv("TZ"); + maketime(NULL, orig_tz); + + Debug(DSCH, ("[%ld] tick(%d,%d,%d,%d,%d) %s %s\n", + (long) getpid(), minute, hour, dom, month, dow, + doWild ? " " : "No wildcard", doNonWild ? " " : "Wildcard only")) + /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the + * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* + * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this + * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. + * like many bizarre things, it's the standard. + */ + for (u = db->head; u != NULL; u = u->next) { + for (e = u->crontab; e != NULL; e = e->next) { + Debug(DSCH | DEXT, ("user [%s:%ld:%ld:...] cmd=\"%s\"\n", + e->pwd->pw_name, (long) e->pwd->pw_uid, + (long) e->pwd->pw_gid, e->cmd)) + uname = e->pwd->pw_name; + /* check if user exists in time of job is being run f.e. ldap */ + if (getpwnam(uname) != NULL) { + job_tz = env_get("CRON_TZ", e->envp); + maketime(job_tz, orig_tz); + /* here we test whether time is NOW */ + if (bit_test(e->minute, minute) && + bit_test(e->hour, hour) && + bit_test(e->month, month) && + (((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) + ? (bit_test(e->dow, dow) && bit_test(e->dom, dom)) + : (bit_test(e->dow, dow) || bit_test(e->dom, dom)) + ) + ) { + if (job_tz != NULL && vGMToff != GMToff) + /* do not try to run the jobs from different timezones + * during the DST switch of the default timezone. + */ + continue; + + if ((doNonWild && + !(e->flags & (MIN_STAR | HR_STAR))) || + (doWild && (e->flags & (MIN_STAR | HR_STAR)))) + job_add(e, u); /*will add job, if it isn't in queue already for NOW. */ + } + } + } + } + if (orig_tz != NULL) + setenv("TZ", orig_tz, 1); + else + unsetenv("TZ"); +} + +/* + * Set StartTime and clockTime to the current time. + * These are used for computing what time it really is right now. + * Note that clockTime is a unix wallclock time converted to minutes. + */ +static void set_time(int initialize) { + struct tm tm; + static int isdst; + + StartTime = time(NULL); + + /* We adjust the time to GMT so we can catch DST changes. */ + tm = *localtime(&StartTime); + if (initialize || tm.tm_isdst != isdst) { + isdst = tm.tm_isdst; + GMToff = get_gmtoff(&StartTime, &tm); + Debug(DSCH, ("[%ld] GMToff=%ld\n", (long) getpid(), (long) GMToff)) + } + clockTime = (StartTime + GMToff) / (time_t) SECONDS_PER_MINUTE; +} + +/* + * Try to just hit the next minute. + */ +static void cron_sleep(int target, cron_db * db) { + time_t t1, t2; + int seconds_to_wait; + + t1 = time(NULL) + GMToff; + seconds_to_wait = (int) (target * SECONDS_PER_MINUTE - t1) + 1; + Debug(DSCH, ("[%ld] Target time=%ld, sec-to-wait=%d\n", + (long) getpid(), (long) target * SECONDS_PER_MINUTE, + seconds_to_wait)) + + while (seconds_to_wait > 0 && seconds_to_wait < 65) { + sleep((unsigned int) seconds_to_wait); + + /* + * Check to see if we were interrupted by a signal. + * If so, service the signal(s) then continue sleeping + * where we left off. + */ + handle_signals(db); + + t2 = time(NULL) + GMToff; + seconds_to_wait -= (int) (t2 - t1); + t1 = t2; + } +} + +static void sighup_handler(int x) { + got_sighup = 1; +} + +static void sigchld_handler(int x) { + got_sigchld = 1; +} + +static void quit(int x) { + (void) unlink(_PATH_CRON_PID); + _exit(0); +} + +static void sigchld_reaper(void) { + WAIT_T waiter; + PID_T pid; + + do { + pid = waitpid(-1, &waiter, WNOHANG); + switch (pid) { + case -1: + if (errno == EINTR) + continue; + Debug(DPROC, ("[%ld] sigchld...no children\n", (long) getpid())) + break; + case 0: + Debug(DPROC, ("[%ld] sigchld...no dead kids\n", (long) getpid())) + break; + default: + Debug(DPROC, + ("[%ld] sigchld...pid #%ld died, stat=%d\n", + (long) getpid(), (long) pid, WEXITSTATUS(waiter))) + break; + } + } while (pid > 0); +} + +static void parse_args(int argc, char *argv[]) { + int argch; + + while (-1 != (argch = getopt(argc, argv, "hnpsix:m:c"))) { + switch (argch) { + case 'x': + if (!set_debug_flags(optarg)) + usage(); + break; + case 'n': + NoFork = 1; + break; + case 'p': + PermitAnyCrontab = 1; + break; + case 's': + SyslogOutput = 1; + break; + case 'i': + DisableInotify = 1; + break; + case 'm': + strncpy(MailCmd, optarg, MAX_COMMAND); + break; + case 'c': + EnableClustering = 1; + break; + case 'h': + default: + usage(); + break; + } + } +} diff --git a/src/cron.h b/src/cron.h new file mode 100644 index 0000000..dff8fc1 --- /dev/null +++ b/src/cron.h @@ -0,0 +1,51 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* cron.h - header for vixie's cron + * + * $Id: cron.h,v 1.6 2004/01/23 18:56:42 vixie Exp $ + * + * vix 14nov88 [rest of log is in RCS] + * vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley] + * vix 30dec86 [written] + */ + +#include "../config.h" +#include "externs.h" + +#ifdef WITH_SELINUX +#include +#endif + +#ifdef WITH_PAM +#include +#endif + +#ifdef WITH_INOTIFY +#include +#endif + +#include "pathnames.h" +#include "macros.h" +#include "structs.h" +#include "funcs.h" +#include "globals.h" + diff --git a/src/crontab.c b/src/crontab.c new file mode 100644 index 0000000..a5fc8df --- /dev/null +++ b/src/crontab.c @@ -0,0 +1,971 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* crontab - install and manage per-user crontab files + * vix 02may87 [RCS has the rest of the log] + * vix 26jan87 [original] + */ + +/* + * Modified 2010/09/10 by Colin Dean, Durham University IT Service, + * to add clustering support. + */ + +#define MAIN_PROGRAM + +#include +#ifdef WITH_SELINUX +# include +# include +# include +#endif + +#define NHEADER_LINES 0 + +enum opt_t {opt_unknown, opt_list, opt_delete, opt_edit, opt_replace, opt_hostset, opt_hostget}; + +#if DEBUGGING +static char *Options[] = {"???", "list", "delete", "edit", "replace", "hostset", "hostget"}; + +# ifdef WITH_SELINUX +static char *getoptargs = "u:lerisncx:"; +# else +static char *getoptargs = "u:lerincx:"; +# endif +#else +# ifdef WITH_SELINUX +static char *getoptargs = "u:lerisnc"; +# else +static char *getoptargs = "u:lerinc"; +# endif +#endif +static char *selinux_context = 0; + +static PID_T Pid; +static char User[MAX_UNAME], RealUser[MAX_UNAME]; +static char Filename[MAX_FNAME], TempFilename[MAX_FNAME]; +static char Host[MAXHOSTNAMELEN]; +static FILE *NewCrontab; +static int CheckErrorCount; +static int PromptOnDelete; +static int HostSpecified; +static enum opt_t Option; +static struct passwd *pw; +static void list_cmd(void), +delete_cmd(void), +edit_cmd(void), +poke_daemon(void), +check_error(const char *), parse_args(int c, char *v[]), die(int); +static int replace_cmd(void), hostset_cmd(void), hostget_cmd(void); +static char *host_specific_filename(char *filename, int prefix); +static char *tmp_path(void); + +static void usage(const char *msg) { + fprintf(stderr, "%s: usage error: %s\n", ProgramName, msg); + fprintf(stderr, "usage:\t%s [-u user] file\n", ProgramName); + fprintf(stderr, "\t%s [-u user] [ -e | -l | -r ]\n", ProgramName); + fprintf(stderr, "\t%s -n [ hostname ]\n", ProgramName); + fprintf(stderr, "\t%s -c\n", ProgramName); + fprintf(stderr, "\t\t(default operation is replace, per 1003.2)\n"); + fprintf(stderr, "\t-e\t(edit user's crontab)\n"); + fprintf(stderr, "\t-l\t(list user's crontab)\n"); + fprintf(stderr, "\t-r\t(delete user's crontab)\n"); + fprintf(stderr, "\t-i\t(prompt before deleting user's crontab)\n"); + fprintf(stderr, "\t-n\t(set host in cluster to run users' crontabs)\n"); + fprintf(stderr, "\t-c\t(get host in cluster to run users' crontabs)\n"); +#ifdef WITH_SELINUX + fprintf(stderr, "\t-s\t(selinux context)\n"); +#endif + exit(ERROR_EXIT); +} + +int main(int argc, char *argv[]) { + int exitstatus; + + Pid = getpid(); + ProgramName = argv[0]; + MailCmd[0] = '\0'; + cron_default_mail_charset[0] = '\0'; + + setlocale(LC_ALL, ""); + +#if defined(BSD) + setlinebuf(stderr); +#endif + char *n = "-"; /*set the n string to - so we have a valid string to use */ + /*should we desire to make changes to behavior later. */ + if (argv[1] == NULL) { /* change behavior to allow crontab to take stdin with no '-' */ + argv[1] = n; + } + parse_args(argc, argv); /* sets many globals, opens a file */ + check_spool_dir(); + if (!allowed(RealUser, CRON_ALLOW, CRON_DENY)) { + fprintf(stderr, + "You (%s) are not allowed to use this program (%s)\n", + User, ProgramName); + fprintf(stderr, "See crontab(1) for more information\n"); + log_it(RealUser, Pid, "AUTH", "crontab command not allowed", 0); + exit(ERROR_EXIT); + } + +#if defined(WITH_PAM) + if (cron_start_pam(pw) != PAM_SUCCESS) { + fprintf(stderr, + "You (%s) are not allowed to access to (%s) because of pam configuration.\n", + User, ProgramName); + exit(ERROR_EXIT); + }; +#endif + + exitstatus = OK_EXIT; + switch (Option) { + case opt_unknown: + exitstatus = ERROR_EXIT; + break; + case opt_list: + list_cmd(); + break; + case opt_delete: + delete_cmd(); + break; + case opt_edit: + edit_cmd(); + break; + case opt_replace: + if (replace_cmd() < 0) + exitstatus = ERROR_EXIT; + break; + case opt_hostset: + if (hostset_cmd() < 0) + exitstatus = ERROR_EXIT; + break; + case opt_hostget: + if (hostget_cmd() < 0) + exitstatus = ERROR_EXIT; + break; + default: + abort(); + } + cron_close_pam(); + exit(exitstatus); + /*NOTREACHED*/} + +static void parse_args(int argc, char *argv[]) { + int argch; + + if (!(pw = getpwuid(getuid()))) { + fprintf(stderr, "%s: your UID isn't in the passwd file.\n", + ProgramName); + fprintf(stderr, "bailing out.\n"); + exit(ERROR_EXIT); + } + if (strlen(pw->pw_name) >= sizeof User) { + fprintf(stderr, "username too long\n"); + exit(ERROR_EXIT); + } + strcpy(User, pw->pw_name); + strcpy(RealUser, User); + Filename[0] = '\0'; + Option = opt_unknown; + PromptOnDelete = 0; + HostSpecified = 0; + while (-1 != (argch = getopt(argc, argv, getoptargs))) { + switch (argch) { +#if DEBUGGING + case 'x': + if (!set_debug_flags(optarg)) + usage("bad debug option"); + break; +#endif + case 'u': + if (MY_UID(pw) != ROOT_UID) { + fprintf(stderr, "must be privileged to use -u\n"); + exit(ERROR_EXIT); + } + + if (crontab_security_access() != 0) { + fprintf(stderr, + "Access denied by SELinux, must be privileged to use -u\n"); + exit(ERROR_EXIT); + } + + if (Option == opt_hostset || Option == opt_hostget) { + fprintf(stderr, + "cannot use -u with -n or -c\n"); + exit(ERROR_EXIT); + } + + if (!(pw = getpwnam(optarg))) { + fprintf(stderr, "%s: user `%s' unknown\n", + ProgramName, optarg); + exit(ERROR_EXIT); + } + if (strlen(optarg) >= sizeof User) + usage("username too long"); + (void) strcpy(User, optarg); + break; + case 'l': + if (Option != opt_unknown) + usage("only one operation permitted"); + Option = opt_list; + break; + case 'r': + if (Option != opt_unknown) + usage("only one operation permitted"); + Option = opt_delete; + break; + case 'e': + if (Option != opt_unknown) + usage("only one operation permitted"); + Option = opt_edit; + break; + case 'i': + PromptOnDelete = 1; + break; +#ifdef WITH_SELINUX + case 's': + if (getprevcon((security_context_t *) & (selinux_context))) { + fprintf(stderr, "Cannot obtain SELinux process context\n"); + exit(ERROR_EXIT); + } + break; +#endif + case 'n': + if (MY_UID(pw) != ROOT_UID) { + fprintf(stderr, + "must be privileged to set host with -n\n"); + exit(ERROR_EXIT); + } + if (Option != opt_unknown) + usage("only one operation permitted"); + if (strcmp(User, RealUser) != 0) { + fprintf(stderr, + "cannot use -u with -n or -c\n"); + exit(ERROR_EXIT); + } + Option = opt_hostset; + break; + case 'c': + if (Option != opt_unknown) + usage("only one operation permitted"); + if (strcmp(User, RealUser) != 0) { + fprintf(stderr, + "cannot use -u with -n or -c\n"); + exit(ERROR_EXIT); + } + Option = opt_hostget; + break; + default: + usage("unrecognized option"); + } + } + + endpwent(); + + if (Option == opt_hostset && argv[optind] != NULL) { + HostSpecified = 1; + if (strlen(argv[optind]) >= sizeof Host) + usage("hostname too long"); + (void) strcpy(Host, argv[optind]); + optind++; + } + + if (Option != opt_unknown) { + if (argv[optind] != NULL) + usage("no arguments permitted after this option"); + } + else { + if (argv[optind] != NULL) { + Option = opt_replace; + if (strlen(argv[optind]) >= sizeof Filename) + usage("filename too long"); + (void) strcpy(Filename, argv[optind]); + } + else + usage("file name must be specified for replace"); + } + + if (Option == opt_replace) { + if (!strcmp(Filename, "-")) + NewCrontab = stdin; + else { + /* relinquish the setuid status of the binary during + * the open, lest nonroot users read files they should + * not be able to read. we can't use access() here + * since there's a race condition. thanks go out to + * Arnt Gulbrandsen for spotting + * the race. + */ + + if (swap_uids() < OK) { + perror("swapping uids"); + exit(ERROR_EXIT); + } + if (!(NewCrontab = fopen(Filename, "r"))) { + perror(Filename); + exit(ERROR_EXIT); + } + if (swap_uids_back() < OK) { + perror("swapping uids back"); + exit(ERROR_EXIT); + } + } + } + + Debug(DMISC, ("user=%s, file=%s, option=%s\n", + User, Filename, Options[(int) Option])) +} + +static void list_cmd(void) { + char n[MAX_FNAME]; + FILE *f; + int ch; + + log_it(RealUser, Pid, "LIST", User, 0); + if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { + fprintf(stderr, "path too long\n"); + exit(ERROR_EXIT); + } + if (!(f = fopen(n, "r"))) { + if (errno == ENOENT) + fprintf(stderr, "no crontab for %s\n", User); + else + perror(n); + exit(ERROR_EXIT); + } + + /* file is open. copy to stdout, close. + */ + Set_LineNum(1) + while (EOF != (ch = get_char(f))) + putchar(ch); + fclose(f); +} + +static void delete_cmd(void) { + char n[MAX_FNAME] = ""; + if (PromptOnDelete == 1) { + printf("crontab: really delete %s's crontab? ", User); + fflush(stdout); + if ((fgets(n, MAX_FNAME - 1, stdin) == 0L) + || ((n[0] != 'Y') && (n[0] != 'y')) + ) + exit(0); + } + + log_it(RealUser, Pid, "DELETE", User, 0); + if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { + fprintf(stderr, "path too long\n"); + exit(ERROR_EXIT); + } + if (unlink(n) != 0) { + if (errno == ENOENT) + fprintf(stderr, "no crontab for %s\n", User); + else + perror(n); + exit(ERROR_EXIT); + } + poke_daemon(); +} + +static void check_error(const char *msg) { + CheckErrorCount++; + fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber - 1, msg); +} + +static char *tmp_path() { + char *tmpdir = NULL; + + if ((getuid() == geteuid()) && (getgid() == getegid())) { + tmpdir = getenv("TMPDIR"); + } + return tmpdir ? tmpdir : "/tmp"; +} + +static char *host_specific_filename(char *filename, int prefix) +{ + /* + * For cluster-wide use, where there is otherwise risk of the same + * name being generated on more than one host at once, prefix with + * "hostname." or suffix with ".hostname" as requested, and return + * static buffer or NULL on failure. + */ + + static char safename[MAX_FNAME]; + char hostname[MAXHOSTNAMELEN]; + + if (gethostname(hostname, sizeof hostname) != 0) + return NULL; + + if (prefix) { + if (!glue_strings(safename, sizeof safename, hostname, filename, '.')) + return NULL; + } else { + if (!glue_strings(safename, sizeof safename, filename, hostname, '.')) + return NULL; + } + + return safename; +} + +static void edit_cmd(void) { + char n[MAX_FNAME], q[MAX_TEMPSTR], *editor; + FILE *f; + int ch = '\0', t; + struct stat statbuf; + struct utimbuf utimebuf; + WAIT_T waiter; + PID_T pid, xpid; + + log_it(RealUser, Pid, "BEGIN EDIT", User, 0); + if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { + fprintf(stderr, "path too long\n"); + exit(ERROR_EXIT); + } + if (!(f = fopen(n, "r"))) { + if (errno != ENOENT) { + perror(n); + exit(ERROR_EXIT); + } + fprintf(stderr, "no crontab for %s - using an empty one\n", User); + if (!(f = fopen(_PATH_DEVNULL, "r"))) { + perror(_PATH_DEVNULL); + exit(ERROR_EXIT); + } + } + + /* Turn off signals. */ + (void) signal(SIGHUP, SIG_IGN); + (void) signal(SIGINT, SIG_IGN); + (void) signal(SIGQUIT, SIG_IGN); + + if (!glue_strings(Filename, sizeof Filename, tmp_path(), + "crontab.XXXXXX", '/')) { + fprintf(stderr, "path too long\n"); + exit(ERROR_EXIT); + } + if (swap_uids() == -1) { + perror("swapping uids"); + exit(ERROR_EXIT); + } + if (-1 == (t = mkstemp(Filename))) { + perror(Filename); + goto fatal; + } + + if (swap_uids_back() == -1) { + perror("swapping uids back"); + goto fatal; + } + if (!(NewCrontab = fdopen(t, "r+"))) { + perror("fdopen"); + goto fatal; + } + + Set_LineNum(1) + /* + * NHEADER_LINES processing removed for clarity + * (NHEADER_LINES == 0 in all Red Hat crontabs) + */ + /* copy the rest of the crontab (if any) to the temp file. + */ + if (EOF != ch) + while (EOF != (ch = get_char(f))) + putc(ch, NewCrontab); + +#ifdef WITH_SELINUX + if (selinux_context) { + context_t ccon = NULL; + const char *level = NULL; + + if (!(ccon = context_new(selinux_context))) { + fprintf(stderr, "context_new failed\n"); + goto fatal; + } + + if (!(level = context_range_get(ccon))) { + fprintf(stderr, "context_range failed\n"); + goto fatal; + } + + fprintf(NewCrontab, "MLS_LEVEL=%s\n", level); + context_free(ccon); + freecon(selinux_context); + selinux_context = NULL; + } +#endif + + fclose(f); + if (fflush(NewCrontab) < OK) { + perror(Filename); + exit(ERROR_EXIT); + } + if (swap_uids() == -1) { + perror("swapping uids"); + exit(ERROR_EXIT); + } + /* Set it to 1970 */ + utimebuf.actime = 0; + utimebuf.modtime = 0; + utime(Filename, &utimebuf); + if (swap_uids_back() == -1) { + perror("swapping uids"); + exit(ERROR_EXIT); + } + again: + rewind(NewCrontab); + if (ferror(NewCrontab)) { + fprintf(stderr, "%s: error while writing new crontab to %s\n", + ProgramName, Filename); + fatal: + unlink(Filename); + exit(ERROR_EXIT); + } + + if (((editor = getenv("VISUAL")) == NULL || *editor == '\0') && + ((editor = getenv("EDITOR")) == NULL || *editor == '\0')) { + editor = EDITOR; + } + + /* we still have the file open. editors will generally rewrite the + * original file rather than renaming/unlinking it and starting a + * new one; even backup files are supposed to be made by copying + * rather than by renaming. if some editor does not support this, + * then don't use it. the security problems are more severe if we + * close and reopen the file around the edit. + */ + + switch (pid = fork()) { + case -1: + perror("fork"); + goto fatal; + case 0: + /* child */ + if (setgid(MY_GID(pw)) < 0) { + perror("setgid(getgid())"); + exit(ERROR_EXIT); + } + if (setuid(MY_UID(pw)) < 0) { + perror("setuid(getuid())"); + exit(ERROR_EXIT); + } + if (!glue_strings(q, sizeof q, editor, Filename, ' ')) { + fprintf(stderr, "%s: editor command line too long\n", ProgramName); + exit(ERROR_EXIT); + } + execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, (char *) 0); + perror(editor); + exit(ERROR_EXIT); + /*NOTREACHED*/ default: + /* parent */ + break; + } + + /* parent */ + for (;;) { + xpid = waitpid(pid, &waiter, 0); + if (xpid == -1) { + if (errno != EINTR) + fprintf(stderr, + "%s: waitpid() failed waiting for PID %ld from \"%s\": %s\n", + ProgramName, (long) pid, editor, strerror(errno)); + } + else if (xpid != pid) { + fprintf(stderr, "%s: wrong PID (%ld != %ld) from \"%s\"\n", + ProgramName, (long) xpid, (long) pid, editor); + goto fatal; + } + else if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) { + fprintf(stderr, "%s: \"%s\" exited with status %d\n", + ProgramName, editor, WEXITSTATUS(waiter)); + goto fatal; + } + else if (WIFSIGNALED(waiter)) { + fprintf(stderr, + "%s: \"%s\" killed; signal %d (%score dumped)\n", + ProgramName, editor, WTERMSIG(waiter), + WCOREDUMP(waiter) ? "" : "no "); + goto fatal; + } + else + break; + } + (void) signal(SIGHUP, SIG_DFL); + (void) signal(SIGINT, SIG_DFL); + (void) signal(SIGQUIT, SIG_DFL); + + /* lstat doesn't make any harm, because + * the file is stat'ed only when crontab is touched + */ + if (lstat(Filename, &statbuf) < 0) { + perror("lstat"); + goto fatal; + } + + if (!S_ISREG(statbuf.st_mode)) { + fprintf(stderr, "%s: illegal crontab\n", ProgramName); + goto remove; + } + + if (statbuf.st_mtime == 0) { + fprintf(stderr, "%s: no changes made to crontab\n", ProgramName); + goto remove; + } + + fprintf(stderr, "%s: installing new crontab\n", ProgramName); + fclose(NewCrontab); + if (swap_uids() < OK) { + perror("swapping uids"); + goto remove; + } + if (!(NewCrontab = fopen(Filename, "r+"))) { + perror("cannot read new crontab"); + goto remove; + } + if (swap_uids_back() < OK) { + perror("swapping uids back"); + exit(ERROR_EXIT); + } + if (NewCrontab == 0L) { + perror("fopen"); + goto fatal; + } + switch (replace_cmd()) { + case 0: + break; + case -1: + for (;;) { + printf("Do you want to retry the same edit? "); + fflush(stdout); + q[0] = '\0'; + if (fgets(q, sizeof q, stdin) == 0L) + continue; + switch (q[0]) { + case 'y': + case 'Y': + goto again; + case 'n': + case 'N': + goto abandon; + default: + fprintf(stderr, "Enter Y or N\n"); + } + } + /*NOTREACHED*/ case -2: + abandon: + fprintf(stderr, "%s: edits left in %s\n", ProgramName, Filename); + goto done; + default: + fprintf(stderr, "%s: panic: bad switch() in replace_cmd()\n", + ProgramName); + goto fatal; + } + remove: + unlink(Filename); + done: + log_it(RealUser, Pid, "END EDIT", User, 0); +} + +/* returns 0 on success + * -1 on syntax error + * -2 on install error + */ +static int replace_cmd(void) { + char n[MAX_FNAME], envstr[MAX_ENVSTR]; + FILE *tmp; + int ch, eof, fd; + int error = 0; + entry *e; + uid_t file_owner; + char **envp; + char *safename; + + + safename = host_specific_filename("tmp.XXXXXXXXXX", 1); + if (!safename || !glue_strings(TempFilename, sizeof TempFilename, SPOOL_DIR, + safename, '/')) { + TempFilename[0] = '\0'; + fprintf(stderr, "path too long\n"); + return (-2); + } + if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w+"))) { + perror(TempFilename); + if (fd != -1) { + close(fd); + unlink(TempFilename); + } + TempFilename[0] = '\0'; + return (-2); + } + + (void) signal(SIGHUP, die); + (void) signal(SIGINT, die); + (void) signal(SIGQUIT, die); + + /* write a signature at the top of the file. + * + * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code. + */ + /*fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n"); + *fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now)); + *fprintf(tmp, "# (Cron version %s)\n", CRON_VERSION); + */ +#ifdef WITH_SELINUX + if (selinux_context) + fprintf(tmp, "SELINUX_ROLE_TYPE=%s\n", selinux_context); +#endif + + /* copy the crontab to the tmp + */ + rewind(NewCrontab); + Set_LineNum(1) + while (EOF != (ch = get_char(NewCrontab))) + putc(ch, tmp); + if (ftruncate(fileno(tmp), ftell(tmp)) == -1) { + fprintf(stderr, "%s: error while writing new crontab to %s\n", + ProgramName, TempFilename); + fclose(tmp); + error = -2; + goto done; + } + fflush(tmp); + rewind(tmp); + if (ferror(tmp)) { + fprintf(stderr, "%s: error while writing new crontab to %s\n", + ProgramName, TempFilename); + fclose(tmp); + error = -2; + goto done; + } + + /* check the syntax of the file being installed. + */ + + /* BUG: was reporting errors after the EOF if there were any errors + * in the file proper -- kludged it by stopping after first error. + * vix 31mar87 + */ + Set_LineNum(1 - NHEADER_LINES) + CheckErrorCount = 0; + eof = FALSE; + + envp = env_init(); + if (envp == NULL) { + fprintf(stderr, "%s: Cannot allocate memory.\n", ProgramName); + fclose(tmp); + error = -2; + goto done; + } + + while (!CheckErrorCount && !eof) { + switch (load_env(envstr, tmp)) { + case ERR: + /* check for data before the EOF */ + if (envstr[0] != '\0') { + Set_LineNum(LineNumber + 1); + check_error("premature EOF"); + } + eof = TRUE; + break; + case FALSE: + e = load_entry(tmp, check_error, pw, envp); + if (e) + free_entry(e); + break; + case TRUE: + break; + } + } + env_free(envp); + + if (CheckErrorCount != 0) { + fprintf(stderr, "errors in crontab file, can't install.\n"); + fclose(tmp); + error = -1; + goto done; + } + + file_owner = (getgid() == getegid())? ROOT_UID : pw->pw_uid; + +#ifdef HAVE_FCHOWN + if (fchown(fileno(tmp), file_owner, -1) < OK) { + perror("fchown"); + fclose(tmp); + error = -2; + goto done; + } +#else + if (chown(TempFilename, file_owner, -1) < OK) { + perror("chown"); + fclose(tmp); + error = -2; + goto done; + } +#endif + + if (fclose(tmp) == EOF) { + perror("fclose"); + error = -2; + goto done; + } + + if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { + fprintf(stderr, "path too long\n"); + error = -2; + goto done; + } + if (rename(TempFilename, n)) { + fprintf(stderr, "%s: error renaming %s to %s\n", + ProgramName, TempFilename, n); + perror("rename"); + error = -2; + goto done; + } + TempFilename[0] = '\0'; + log_it(RealUser, Pid, "REPLACE", User, 0); + + poke_daemon(); + + done: + (void) signal(SIGHUP, SIG_DFL); + (void) signal(SIGINT, SIG_DFL); + (void) signal(SIGQUIT, SIG_DFL); + if (TempFilename[0]) { + (void) unlink(TempFilename); + TempFilename[0] = '\0'; + } + return (error); +} + +static int hostset_cmd(void) { + char n[MAX_FNAME]; + FILE *tmp; + int fd; + int error = 0; + char *safename; + + if (!HostSpecified) + gethostname(Host, sizeof Host); + + safename = host_specific_filename("tmp.XXXXXXXXXX", 1); + if (!safename || !glue_strings(TempFilename, sizeof TempFilename, SPOOL_DIR, + safename, '/')) { + TempFilename[0] = '\0'; + fprintf(stderr, "path too long\n"); + return (-2); + } + if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w"))) { + perror(TempFilename); + if (fd != -1) { + close(fd); + unlink(TempFilename); + } + TempFilename[0] = '\0'; + return (-2); + } + + (void) signal(SIGHUP, die); + (void) signal(SIGINT, die); + (void) signal(SIGQUIT, die); + + (void) fchmod(fd, 0600); /* not all mkstemp() implementations do this */ + + if (fprintf(tmp, "%s\n", Host) < 0 || fclose(tmp) == EOF) { + fprintf(stderr, "%s: error while writing to %s\n", + ProgramName, TempFilename); + error = -2; + goto done; + } + + if (!glue_strings(n, sizeof n, SPOOL_DIR, CRON_HOSTNAME, '/')) { + fprintf(stderr, "path too long\n"); + error = -2; + goto done; + } + + if (rename(TempFilename, n)) { + fprintf(stderr, "%s: error renaming %s to %s\n", + ProgramName, TempFilename, n); + perror("rename"); + error = -2; + goto done; + } + TempFilename[0] = '\0'; + log_it(RealUser, Pid, "SET HOST", Host, 0); + + poke_daemon(); + + done: + (void) signal(SIGHUP, SIG_DFL); + (void) signal(SIGINT, SIG_DFL); + (void) signal(SIGQUIT, SIG_DFL); + if (TempFilename[0]) { + (void) unlink(TempFilename); + TempFilename[0] = '\0'; + } + return (error); +} + +static int hostget_cmd(void) { + char n[MAX_FNAME]; + FILE *f; + + if (!glue_strings(n, sizeof n, SPOOL_DIR, CRON_HOSTNAME, '/')) { + fprintf(stderr, "path too long\n"); + return (-2); + } + + if (!(f = fopen(n, "r"))) { + if (errno == ENOENT) + fprintf(stderr, "File %s not found\n", n); + else + perror(n); + return (-2); + } + + if (get_string(Host, sizeof Host, f, "\n") == EOF) { + fprintf(stderr, "Error reading from %s\n", n); + fclose(f); + return (-2); + } + + fclose(f); + + printf("%s\n", Host); + fflush(stdout); + + log_it(RealUser, Pid, "GET HOST", Host, 0); + return (0); +} + +static void poke_daemon(void) { + if (utime(SPOOL_DIR, NULL) < OK) { + fprintf(stderr, "crontab: can't update mtime on spooldir\n"); + perror(SPOOL_DIR); + return; + } +} + +static void die(int x) { + if (TempFilename[0]) + (void) unlink(TempFilename); + _exit(ERROR_EXIT); +} diff --git a/src/database.c b/src/database.c new file mode 100644 index 0000000..b0f2254 --- /dev/null +++ b/src/database.c @@ -0,0 +1,623 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* vix 26jan87 [RCS has the log] + */ + +/* + * Modified 2010/09/12 by Colin Dean, Durham University IT Service, + * to add clustering support. + */ + +#include + +#define TMAX(a,b) ((a)>(b)?(a):(b)) + +/* size of the event structure, not counting name */ +#define EVENT_SIZE (sizeof (struct inotify_event)) + +/* reasonable guess as to size of 1024 events */ +#define BUF_LEN (1024 * (EVENT_SIZE + 16)) + +static void overwrite_database(cron_db *, cron_db *); + +static void process_crontab(const char *, const char *, + const char *, cron_db *, cron_db *); + +static int not_a_crontab(DIR_T * dp); +/* return 1 if we should skip this file */ + +static void max_mtime(char *dir_name, struct stat *max_st); +/* record max mtime of any file under dir_name in max_st */ + +static int +check_open(const char *tabname, const char *fname, const char *uname, + struct passwd *pw, time_t * mtime) { + struct stat statbuf; + int crontab_fd; + pid_t pid = getpid(); + + if ((crontab_fd = + open(tabname, O_RDONLY | O_NONBLOCK, 0)) == -1) { + log_it(uname, pid, "CAN'T OPEN", tabname, errno); + return (-1); + } + if (fstat(crontab_fd, &statbuf) < OK) { + log_it(uname, pid, "STAT FAILED", tabname, errno); + close(crontab_fd); + return (-1); + } + *mtime = statbuf.st_mtime; + if (PermitAnyCrontab == 0) { + if (!S_ISREG(statbuf.st_mode)) { + log_it(uname, pid, "NOT REGULAR", tabname, 0); + close(crontab_fd); + return (-1); + } + if ((statbuf.st_mode & 07533) != 0400) { + log_it(uname, pid, "BAD FILE MODE", tabname, 0); + close(crontab_fd); + return (-1); + } + if (statbuf.st_uid != ROOT_UID && (pw == NULL || + statbuf.st_uid != pw->pw_uid || + strcmp(uname, pw->pw_name) != 0)) { + log_it(uname, pid, "WRONG FILE OWNER", tabname, 0); + close(crontab_fd); + return (-1); + } + if (pw && statbuf.st_nlink != 1) { + log_it(uname, pid, "BAD LINK COUNT", tabname, 0); + close(crontab_fd); + return (-1); + } + } + return (crontab_fd); +} + +static orphan *orphans; + +static void +free_orphan(orphan *o) { + free(o->tabname); + free(o->fname); + free(o->uname); + free(o); +} + +void +check_orphans(cron_db *db) { + orphan *prev_orphan = NULL; + orphan *o = orphans; + + while (o != NULL) { + if (getpwnam(o->uname) != NULL) { + orphan *next = o->next; + + if (prev_orphan == NULL) { + orphans = next; + } else { + prev_orphan->next = next; + } + + process_crontab(o->uname, o->fname, o->tabname, + db, NULL); + + /* process_crontab could have added a new orphan */ + if (prev_orphan == NULL && orphans != next) { + prev_orphan = orphans; + } + free_orphan(o); + o = next; + } else { + prev_orphan = o; + o = o->next; + } + } +} + +static void +add_orphan(const char *uname, const char *fname, const char *tabname) { + orphan *o; + + o = calloc(1, sizeof(*o)); + if (o == NULL) + return; + + if (uname) + if ((o->uname=strdup(uname)) == NULL) + goto cleanup; + + if (fname) + if ((o->fname=strdup(fname)) == NULL) + goto cleanup; + + if (tabname) + if ((o->tabname=strdup(tabname)) == NULL) + goto cleanup; + + o->next = orphans; + orphans = o; + return; + +cleanup: + free_orphan(o); +} + +static void +process_crontab(const char *uname, const char *fname, const char *tabname, + cron_db * new_db, cron_db * old_db) { + struct passwd *pw = NULL; + int crontab_fd = -1; + user *u = NULL; + time_t mtime; + int crond_crontab = (fname == NULL) && (strcmp(tabname, SYSCRONTAB) != 0); + + if (fname == NULL) { + /* must be set to something for logging purposes. + */ + fname = "*system*"; + } + else if ((pw = getpwnam(uname)) == NULL) { + /* file doesn't have a user in passwd file. + */ + log_it(uname, getpid(), "ORPHAN", "no passwd entry", 0); + add_orphan(uname, fname, tabname); + + goto next_crontab; + } + + if ((crontab_fd = check_open(tabname, fname, uname, pw, &mtime)) == -1) + goto next_crontab; + + Debug(DLOAD, ("\t%s:", fname)) + + if (old_db != NULL) + u = find_user(old_db, fname, crond_crontab ? tabname : NULL); /* find user in old_db */ + + if (u != NULL) { + /* if crontab has not changed since we last read it + * in, then we can just use our existing entry. + */ + if (u->mtime == mtime) { + Debug(DLOAD, (" [no change, using old data]")) + unlink_user(old_db, u); + link_user(new_db, u); + goto next_crontab; + } + + /* before we fall through to the code that will reload + * the user, let's deallocate and unlink the user in + * the old database. This is more a point of memory + * efficiency than anything else, since all leftover + * users will be deleted from the old database when + * we finish with the crontab... + */ + Debug(DLOAD, (" [delete old data]")) + unlink_user(old_db, u); + free_user(u); + log_it(fname, getpid(), "RELOAD", tabname, 0); + } + + u = load_user(crontab_fd, pw, uname, fname, tabname); /* read the file */ + crontab_fd = -1; /* load_user takes care of closing the file */ + if (u != NULL) { + u->mtime = mtime; + link_user(new_db, u); + } + + next_crontab: + if (crontab_fd != -1) { + Debug(DLOAD, (" [done]\n")) + close(crontab_fd); + } +} + +static int +cluster_host_is_local(void) +{ + char filename[MAXNAMLEN+1]; + int is_local; + FILE *f; + char hostname[MAXHOSTNAMELEN], myhostname[MAXHOSTNAMELEN]; + + if (!EnableClustering) + return (1); + + /* to allow option of NFS-mounting SPOOL_DIR on a cluster of + * hosts and to only use crontabs here on any one host at a + * time, allow for existence of a CRON_HOSTNAME file, and if + * it doesn't exist, or exists but does not contain this + * host's hostname, then skip the crontabs. + * + * Note: for safety's sake, no CRON_HOSTNAME file means skip, + * otherwise its accidental deletion could result in multiple + * cluster hosts running the same cron jobs, which is + * potentially worse. + */ + + is_local = 0; + if (glue_strings(filename, sizeof filename, SPOOL_DIR, CRON_HOSTNAME, '/')) { + if ((f = fopen(filename, "r"))) { + + if (EOF != get_string(hostname, MAXHOSTNAMELEN, f, "\n") && + gethostname(myhostname, MAXHOSTNAMELEN) == 0) { + is_local = (strcmp(myhostname, hostname) == 0); + } else { + Debug(DLOAD, ("cluster: hostname comparison error\n")); + } + + fclose(f); + } else { + Debug(DLOAD, ("cluster: file %s not found\n", filename)); + } + } + + return (is_local); +} + +#if defined WITH_INOTIFY +void check_inotify_database(cron_db * old_db) { + cron_db new_db; + DIR_T *dp; + DIR *dir; + struct timeval time; + fd_set rfds; + int retval = 0; + char buf[BUF_LEN]; + pid_t pid = getpid(); + time.tv_sec = 0; + time.tv_usec = 0; + + FD_ZERO(&rfds); + FD_SET(old_db->ifd, &rfds); + + retval = select(old_db->ifd + 1, &rfds, NULL, NULL, &time); + if (retval == -1) { + if (errno != EINTR) + log_it("CRON", pid, "INOTIFY", "select failed", errno); + return; + } + else if (FD_ISSET(old_db->ifd, &rfds)) { + new_db.head = new_db.tail = NULL; + new_db.ifd = old_db->ifd; + while ((retval = read(old_db->ifd, buf, sizeof (buf))) == -1 && + errno == EINTR) ; + + if (retval == 0) { + /* this should not happen as the buffer is large enough */ + errno = ENOMEM; + } + + if (retval <= 0) { + log_it("CRON", pid, "INOTIFY", "read failed", errno); + /* something fatal must have occured we have no other reasonable + * way how to handle this failure than exit. + */ + (void) exit(ERROR_EXIT); + } + + /* we must reinstate the watches here - TODO reinstate only watches + * which get IN_IGNORED event + */ + set_cron_watched(old_db->ifd); + + /* TODO: parse the events and read only affected files */ + + process_crontab("root", NULL, SYSCRONTAB, &new_db, old_db); + + if (!(dir = opendir(SYS_CROND_DIR))) { + log_it("CRON", pid, "OPENDIR FAILED", SYS_CROND_DIR, errno); + } + else { + while (NULL != (dp = readdir(dir))) { + char tabname[MAXNAMLEN + 1]; + + if (not_a_crontab(dp)) + continue; + + if (!glue_strings(tabname, sizeof tabname, SYS_CROND_DIR, + dp->d_name, '/')) + continue; + process_crontab("root", NULL, tabname, &new_db, old_db); + } + closedir(dir); + } + + if (!(dir = opendir(SPOOL_DIR))) { + log_it("CRON", pid, "OPENDIR FAILED", SPOOL_DIR, errno); + } + else { + while (NULL != (dp = readdir(dir))) { + char fname[MAXNAMLEN + 1], tabname[MAXNAMLEN + 1]; + + if (not_a_crontab(dp)) + continue; + + strncpy(fname, dp->d_name, MAXNAMLEN); + + if (!glue_strings(tabname, sizeof tabname, SPOOL_DIR, + dp->d_name, '/')) + continue; + process_crontab(fname, fname, tabname, &new_db, old_db); + } + closedir(dir); + } + + /* if we don't do this, then when our children eventually call + * getpwnam() in do_command.c's child_process to verify MAILTO=, + * they will screw us up (and v-v). + */ + endpwent(); + } + else { + /* just return as no db reload is needed */ + return; + } + + overwrite_database(old_db, &new_db); + Debug(DLOAD, ("check_inotify_database is done\n")) +} +#endif + +static void overwrite_database(cron_db * old_db, cron_db * new_db) { + user *u, *nu; + /* whatever's left in the old database is now junk. + */ + Debug(DLOAD, ("unlinking old database:\n")) + for (u = old_db->head; u != NULL; u = nu) { + Debug(DLOAD, ("\t%s\n", u->name)) + nu = u->next; + unlink_user(old_db, u); + free_user(u); + } + + /* overwrite the database control block with the new one. + */ + *old_db = *new_db; +} + +int load_database(cron_db * old_db) { + struct stat statbuf, syscron_stat, crond_stat; + cron_db new_db; + DIR_T *dp; + DIR *dir; + pid_t pid = getpid(); + int is_local = 0; + + Debug(DLOAD, ("[%ld] load_database()\n", (long) pid)) + + /* before we start loading any data, do a stat on SPOOL_DIR + * so that if anything changes as of this moment (i.e., before we've + * cached any of the database), we'll see the changes next time. + */ + if (stat(SPOOL_DIR, &statbuf) < OK) { + log_it("CRON", pid, "STAT FAILED", SPOOL_DIR, errno); + statbuf.st_mtime = 0; + } + else { + /* As pointed out in Red Hat bugzilla 198019, with modern Linux it + * is possible to modify a file without modifying the mtime of the + * containing directory. Hence, we must check the mtime of each file: + */ + max_mtime(SPOOL_DIR, &statbuf); + } + + if (stat(SYS_CROND_DIR, &crond_stat) < OK) { + log_it("CRON", pid, "STAT FAILED", SYS_CROND_DIR, errno); + crond_stat.st_mtime = 0; + } + else { + max_mtime(SYS_CROND_DIR, &crond_stat); + } + + /* track system crontab file + */ + if (stat(SYSCRONTAB, &syscron_stat) < OK) + syscron_stat.st_mtime = 0; + + /* if spooldir's mtime has not changed, we don't need to fiddle with + * the database. + * + * Note that old_db->mtime is initialized to 0 in main(), and + * so is guaranteed to be different than the stat() mtime the first + * time this function is called. + */ + if (old_db->mtime == TMAX(crond_stat.st_mtime, + TMAX(statbuf.st_mtime, syscron_stat.st_mtime)) + ) { + Debug(DLOAD, ("[%ld] spool dir mtime unch, no load needed.\n", + (long) pid)) + return 0; + } + + /* something's different. make a new database, moving unchanged + * elements from the old database, reloading elements that have + * actually changed. Whatever is left in the old database when + * we're done is chaff -- crontabs that disappeared. + */ + new_db.mtime = TMAX(crond_stat.st_mtime, + TMAX(statbuf.st_mtime, syscron_stat.st_mtime)); + new_db.head = new_db.tail = NULL; +#if defined WITH_INOTIFY + new_db.ifd = old_db->ifd; +#endif + + if (syscron_stat.st_mtime) + process_crontab("root", NULL, SYSCRONTAB, &new_db, old_db); + + if (!(dir = opendir(SYS_CROND_DIR))) { + log_it("CRON", pid, "OPENDIR FAILED", SYS_CROND_DIR, errno); + } + else { + while (NULL != (dp = readdir(dir))) { + char tabname[MAXNAMLEN + 1]; + + if (not_a_crontab(dp)) + continue; + + if (!glue_strings(tabname, sizeof tabname, SYS_CROND_DIR, + dp->d_name, '/')) + continue; /* XXX log? */ + + process_crontab("root", NULL, tabname, &new_db, old_db); + } + closedir(dir); + } + + /* we used to keep this dir open all the time, for the sake of + * efficiency. however, we need to close it in every fork, and + * we fork a lot more often than the mtime of the dir changes. + */ + + if (!(dir = opendir(SPOOL_DIR))) { + log_it("CRON", pid, "OPENDIR FAILED", SPOOL_DIR, errno); + } + else { + + is_local = cluster_host_is_local(); + + while (is_local && NULL != (dp = readdir(dir))) { + char fname[MAXNAMLEN + 1], tabname[MAXNAMLEN + 1]; + + if (not_a_crontab(dp)) + continue; + + strncpy(fname, dp->d_name, MAXNAMLEN); + + if (!glue_strings(tabname, sizeof tabname, SPOOL_DIR, fname, '/')) + continue; /* XXX log? */ + + process_crontab(fname, fname, tabname, &new_db, old_db); + } + closedir(dir); + } + + /* if we don't do this, then when our children eventually call + * getpwnam() in do_command.c's child_process to verify MAILTO=, + * they will screw us up (and v-v). + */ + endpwent(); + + overwrite_database(old_db, &new_db); + Debug(DLOAD, ("load_database is done\n")) + return 1; +} + +void link_user(cron_db * db, user * u) { + if (db->head == NULL) + db->head = u; + if (db->tail) + db->tail->next = u; + u->prev = db->tail; + u->next = NULL; + db->tail = u; +} + +void unlink_user(cron_db * db, user * u) { + if (u->prev == NULL) + db->head = u->next; + else + u->prev->next = u->next; + + if (u->next == NULL) + db->tail = u->prev; + else + u->next->prev = u->prev; +} + +user *find_user(cron_db * db, const char *name, const char *tabname) { + user *u; + + for (u = db->head; u != NULL; u = u->next) + if ((strcmp(u->name, name) == 0) + && ((tabname == NULL) + || (strcmp(tabname, u->tabname) == 0) + ) + ) + break; + return (u); +} + +static int not_a_crontab(DIR_T * dp) { + int len; + + /* avoid file names beginning with ".". this is good + * because we would otherwise waste two guaranteed calls + * to getpwnam() for . and .., and there shouldn't be + * hidden files in here anyway + */ + if (dp->d_name[0] == '.') + return (1); + + /* ignore files starting with # and ending with ~ */ + if (dp->d_name[0] == '#') + return (1); + + /* ignore CRON_HOSTNAME file (in case doesn't start with ".") */ + if (0 == strcmp(dp->d_name, CRON_HOSTNAME)) + return(1); + + len = strlen(dp->d_name); + + if (len >= MAXNAMLEN) + return (1); /* XXX log? */ + + if ((len > 0) && (dp->d_name[len - 1] == '~')) + return (1); + + if ((len > 8) && (strncmp(dp->d_name + len - 8, ".rpmsave", 8) == 0)) + return (1); + if ((len > 8) && (strncmp(dp->d_name + len - 8, ".rpmorig", 8) == 0)) + return (1); + if ((len > 7) && (strncmp(dp->d_name + len - 7, ".rpmnew", 7) == 0)) + return (1); + + return (0); +} + +static void max_mtime(char *dir_name, struct stat *max_st) { + DIR *dir; + DIR_T *dp; + struct stat st; + + if (!(dir = opendir(dir_name))) { + max_st->st_mtime = 0; + return; + } + + while (NULL != (dp = readdir(dir))) { + char tabname[MAXNAMLEN + 1]; + + if ( not_a_crontab ( dp ) && strcmp(dp->d_name, CRON_HOSTNAME) != 0) + continue; + + if (!glue_strings(tabname, sizeof tabname, dir_name, dp->d_name, '/')) + continue; /* XXX log? */ + + if (stat(tabname, &st) < OK) + continue; /* XXX log? */ + + if (st.st_mtime > max_st->st_mtime) + max_st->st_mtime = st.st_mtime; + } + closedir(dir); +} diff --git a/src/do_command.c b/src/do_command.c new file mode 100644 index 0000000..86e8a89 --- /dev/null +++ b/src/do_command.c @@ -0,0 +1,571 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +#include + +static int child_process(entry *, user *, char **); +static int safe_p(const char *, const char *); + +void do_command(entry * e, user * u) { + pid_t pid = getpid(); + int ev; + char **jobenv = 0L; + + Debug(DPROC, ("[%ld] do_command(%s, (%s,%ld,%ld))\n", + (long) pid, e->cmd, u->name, + (long) e->pwd->pw_uid, (long) e->pwd->pw_gid)) + + /* fork to become asynchronous -- parent process is done immediately, + * and continues to run the normal cron code, which means return to + * tick(). the child and grandchild don't leave this function, alive. + * + * vfork() is unsuitable, since we have much to do, and the parent + * needs to be able to run off and fork other processes. + */ + switch (fork()) { + case -1: + log_it("CRON", pid, "CAN'T FORK", "do_command", errno); + break; + case 0: + /* child process */ + acquire_daemonlock(1); + /* Set up the Red Hat security context for both mail/minder and job processes: + */ + if (cron_set_job_security_context(e, u, &jobenv) != 0) { + _exit(ERROR_EXIT); + } + ev = child_process(e, u, jobenv); + cron_close_pam(); + env_free(jobenv); + Debug(DPROC, ("[%ld] child process done, exiting\n", (long) getpid())) + _exit(ev); + break; + default: + /* parent process */ + break; + } + Debug(DPROC, ("[%ld] main process returning to work\n", (long) pid)) +} + +static int child_process(entry * e, user * u, char **jobenv) { + int stdin_pipe[2], stdout_pipe[2]; + char *input_data, *usernm, *mailto, *mailfrom; + int children = 0; + pid_t pid = getpid(); + struct sigaction sa; + + /* Ignore SIGPIPE as we will be writing to pipes and do not want to terminate + prematurely */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sa, NULL); + + /* our parent is watching for our death by catching SIGCHLD. we + * do not care to watch for our children's deaths this way -- we + * use wait() explicitly. so we have to reset the signal (which + * was inherited from the parent). + */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + + + Debug(DPROC, ("[%ld] child_process('%s')\n", (long) getpid(), e->cmd)) +#ifdef CAPITALIZE_FOR_PS + /* mark ourselves as different to PS command watchers by upshifting + * our program name. This has no effect on some kernels. + */ + /*local */ { + char *pch; + + for (pch = ProgramName; *pch; pch++) + *pch = MkUpper(*pch); + } +#endif /* CAPITALIZE_FOR_PS */ + + /* discover some useful and important environment settings + */ + usernm = e->pwd->pw_name; + mailto = env_get("MAILTO", jobenv); + mailfrom = env_get("MAILFROM", e->envp); + + /* create some pipes to talk to our future child + */ + if (pipe(stdin_pipe) == -1) { /* child's stdin */ + log_it("CRON", pid, "PIPE() FAILED", "stdin_pipe", errno); + return ERROR_EXIT; + } + + if (pipe(stdout_pipe) == -1) { /* child's stdout */ + log_it("CRON", pid, "PIPE() FAILED", "stdout_pipe", errno); + return ERROR_EXIT; + } + + /* since we are a forked process, we can diddle the command string + * we were passed -- nobody else is going to use it again, right? + * + * if a % is present in the command, previous characters are the + * command, and subsequent characters are the additional input to + * the command. An escaped % will have the escape character stripped + * from it. Subsequent %'s will be transformed into newlines, + * but that happens later. + */ + /*local */ { + int escaped = FALSE; + int ch; + char *p; + + for (input_data = p = e->cmd; + (ch = *input_data) != '\0'; input_data++, p++) { + if (p != input_data) + *p = ch; + if (escaped) { + if (ch == '%') + *--p = ch; + escaped = FALSE; + continue; + } + if (ch == '\\') { + escaped = TRUE; + continue; + } + if (ch == '%') { + *input_data++ = '\0'; + break; + } + } + *p = '\0'; + } + + + /* fork again, this time so we can exec the user's command. + */ + switch (fork()) { + case -1: + log_it("CRON", pid, "CAN'T FORK", "child_process", errno); + return ERROR_EXIT; + /*NOTREACHED*/ + case 0: + Debug(DPROC, ("[%ld] grandchild process fork()'ed\n", (long) getpid())) + + /* write a log message. we've waited this long to do it + * because it was not until now that we knew the PID that + * the actual user command shell was going to get and the + * PID is part of the log message. + */ + if ((e->flags & DONT_LOG) == 0) { + char *x = mkprints((u_char *) e->cmd, strlen(e->cmd)); + + log_it(usernm, getpid(), "CMD", x, 0); + free(x); + } + + if (cron_change_user_permanently(e->pwd, env_get("HOME", jobenv)) < 0) + _exit(ERROR_EXIT); + + /* get new pgrp, void tty, etc. + */ + (void) setsid(); + + /* reset the SIGPIPE back to default so the child will terminate + * if it tries to write to a closed pipe + */ + sa.sa_handler = SIG_DFL; + sigaction(SIGPIPE, &sa, NULL); + + /* close the pipe ends that we won't use. this doesn't affect + * the parent, who has to read and write them; it keeps the + * kernel from recording us as a potential client TWICE -- + * which would keep it from sending SIGPIPE in otherwise + * appropriate circumstances. + */ + close(stdin_pipe[WRITE_PIPE]); + close(stdout_pipe[READ_PIPE]); + + /* grandchild process. make std{in,out} be the ends of + * pipes opened by our daddy; make stderr go to stdout. + */ + if (stdin_pipe[READ_PIPE] != STDIN) { + dup2(stdin_pipe[READ_PIPE], STDIN); + close(stdin_pipe[READ_PIPE]); + } + if (stdout_pipe[WRITE_PIPE] != STDOUT) { + dup2(stdout_pipe[WRITE_PIPE], STDOUT); + close(stdout_pipe[WRITE_PIPE]); + } + dup2(STDOUT, STDERR); + + /* + * Exec the command. + */ + { + char *shell = env_get("SHELL", jobenv); + +#if DEBUGGING + if (DebugFlags & DTEST) { + fprintf(stderr, "debug DTEST is on, not exec'ing command.\n"); + fprintf(stderr, "\tcmd='%s' shell='%s'\n", e->cmd, shell); + _exit(OK_EXIT); + } +#endif /*DEBUGGING*/ + execle(shell, shell, "-c", e->cmd, (char *) 0, jobenv); + fprintf(stderr, "execl: couldn't exec `%s'\n", shell); + perror("execl"); + _exit(ERROR_EXIT); + } + break; + default: + cron_restore_default_security_context(); + /* parent process */ + break; + } + + children++; + + /* middle process, child of original cron, parent of process running + * the user's command. + */ + + Debug(DPROC, ("[%ld] child continues, closing pipes\n", (long) getpid())) + + /* close the ends of the pipe that will only be referenced in the + * grandchild process... + */ + close(stdin_pipe[READ_PIPE]); + close(stdout_pipe[WRITE_PIPE]); + + /* + * write, to the pipe connected to child's stdin, any input specified + * after a % in the crontab entry. while we copy, convert any + * additional %'s to newlines. when done, if some characters were + * written and the last one wasn't a newline, write a newline. + * + * Note that if the input data won't fit into one pipe buffer (2K + * or 4K on most BSD systems), and the child doesn't read its stdin, + * we would block here. thus we must fork again. + */ + + if (*input_data && fork() == 0) { + FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); + int need_newline = FALSE; + int escaped = FALSE; + int ch; + + Debug(DPROC, ("[%ld] child2 sending data to grandchild\n", + (long) getpid())) + + /* reset the SIGPIPE back to default so the child will terminate + * if it tries to write to a closed pipe + */ + sa.sa_handler = SIG_DFL; + sigaction(SIGPIPE, &sa, NULL); + + /* close the pipe we don't use, since we inherited it and + * are part of its reference count now. + */ + close(stdout_pipe[READ_PIPE]); + + if (cron_change_user_permanently(e->pwd, env_get("HOME", jobenv)) < 0) + _exit(ERROR_EXIT); + /* translation: + * \% -> % + * % -> \n + * \x -> \x for all x != % + */ + while ((ch = *input_data++) != '\0') { + if (escaped) { + if (ch != '%') + putc('\\', out); + } + else { + if (ch == '%') + ch = '\n'; + } + + if (!(escaped = (ch == '\\'))) { + putc(ch, out); + need_newline = (ch != '\n'); + } + } + if (escaped) + putc('\\', out); + if (need_newline) + putc('\n', out); + + /* close the pipe, causing an EOF condition. fclose causes + * stdin_pipe[WRITE_PIPE] to be closed, too. + */ + fclose(out); + + Debug(DPROC, ("[%ld] child2 done sending to grandchild\n", + (long) getpid())) + _exit(0); + } + + /* close the pipe to the grandkiddie's stdin, since its wicked uncle + * ernie back there has it open and will close it when he's done. + */ + close(stdin_pipe[WRITE_PIPE]); + + children++; + + /* + * read output from the grandchild. it's stderr has been redirected to + * it's stdout, which has been redirected to our pipe. if there is any + * output, we'll be mailing it to the user whose crontab this is... + * when the grandchild exits, we'll get EOF. + */ + + Debug(DPROC, ("[%ld] child reading output from grandchild\n", + (long) getpid())) + + /*local */ { + FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); + int ch = getc(in); + + if (ch != EOF) { + FILE *mail = NULL; + int bytes = 1; + int status = 0; +#if defined(SYSLOG) + char logbuf[1024]; + int bufidx = 0; + if (SyslogOutput) { + if (ch != '\n') + logbuf[bufidx++] = ch; + } +#endif + + Debug(DPROC | DEXT, + ("[%ld] got data (%x:%c) from grandchild\n", + (long) getpid(), ch, ch)) + + /* get name of recipient. this is MAILTO if set to a + * valid local username; USER otherwise. + */ + if (mailto) { + /* MAILTO was present in the environment + */ + if (!*mailto) { + /* ... but it's empty. set to NULL + */ + mailto = NULL; + } + } + else { + /* MAILTO not present, set to USER. + */ + mailto = usernm; + } + + /* get sender address. this is MAILFROM if set (and safe), + * the user account name otherwise. + */ + if (!mailfrom || !*mailfrom || !safe_p(usernm, mailfrom)) { + mailfrom = e->pwd->pw_name; + } + + /* if we are supposed to be mailing, MAILTO will + * be non-NULL. only in this case should we set + * up the mail command and subjects and stuff... + */ + + /* Also skip it if MailCmd is set to "off" */ + if (mailto && safe_p(usernm, mailto) + && strncmp(MailCmd,"off",4)) { + char **env; + char mailcmd[MAX_COMMAND]; + char hostname[MAXHOSTNAMELEN]; + char *content_type = env_get("CONTENT_TYPE", jobenv), + *content_transfer_encoding = + env_get("CONTENT_TRANSFER_ENCODING", jobenv); + + gethostname(hostname, MAXHOSTNAMELEN); + + if (MailCmd[0] == '\0') { + if (snprintf(mailcmd, sizeof mailcmd, MAILFMT, MAILARG, mailfrom) + >= sizeof mailcmd) { + fprintf(stderr, "mailcmd too long\n"); + (void) _exit(ERROR_EXIT); + } + } + else { + strncpy(mailcmd, MailCmd, MAX_COMMAND); + } + if (!(mail = cron_popen(mailcmd, "w", e->pwd))) { + perror(mailcmd); + (void) _exit(ERROR_EXIT); + } + + fprintf(mail, "From: %s (Cron Daemon)\n", mailfrom); + fprintf(mail, "To: %s\n", mailto); + fprintf(mail, "Subject: Cron <%s@%s> %s\n", + usernm, first_word(hostname, "."), e->cmd); + +#ifdef MAIL_DATE + fprintf(mail, "Date: %s\n", arpadate(&StartTime)); +#endif /*MAIL_DATE */ + if (content_type == 0L) { + fprintf(mail, "Content-Type: text/plain; charset=%s\n", + cron_default_mail_charset); + } + else { /* user specified Content-Type header. + * disallow new-lines for security reasons + * (else users could specify arbitrary mail headers!) + */ + char *nl = content_type; + size_t ctlen = strlen(content_type); + while ((*nl != '\0') + && ((nl = strchr(nl, '\n')) != 0L) + && (nl < (content_type + ctlen)) + ) + *nl = ' '; + fprintf(mail, "Content-Type: %s\n", content_type); + } + if (content_transfer_encoding != 0L) { + char *nl = content_transfer_encoding; + size_t ctlen = strlen(content_transfer_encoding); + while ((*nl != '\0') + && ((nl = strchr(nl, '\n')) != 0L) + && (nl < (content_transfer_encoding + ctlen)) + ) + *nl = ' '; + fprintf(mail, "Content-Transfer-Encoding: %s\n", + content_transfer_encoding); + } + + /* The Auto-Submitted header is + * defined (and suggested by) RFC3834. + */ + fprintf(mail, "Auto-Submitted: auto-generated\n"); + + for (env = jobenv; *env; env++) + fprintf(mail, "X-Cron-Env: <%s>\n", *env); + fprintf(mail, "\n"); + + /* this was the first char from the pipe + */ + putc(ch, mail); + } + + /* we have to read the input pipe no matter whether + * we mail or not, but obviously we only write to + * mail pipe if we ARE mailing. + */ + + while (EOF != (ch = getc(in))) { + bytes++; + if (mail) + putc(ch, mail); +#if defined(SYSLOG) + if (SyslogOutput) { + logbuf[bufidx++] = ch; + if ((ch == '\n') || (bufidx == sizeof(logbuf)-1)) { + if (ch == '\n') + logbuf[bufidx-1] = '\0'; + else + logbuf[bufidx] = '\0'; + log_it(usernm, getpid(), "CMDOUT", logbuf, 0); + bufidx = 0; + } + } +#endif + } + /* only close pipe if we opened it -- i.e., we're + * mailing... + */ + + if (mail) { + Debug(DPROC, ("[%ld] closing pipe to mail\n", (long) getpid())) + /* Note: the pclose will probably see + * the termination of the grandchild + * in addition to the mail process, since + * it (the grandchild) is likely to exit + * after closing its stdout. + */ + status = cron_pclose(mail); + } +#if defined(SYSLOG) + if (SyslogOutput) { + if (bufidx) { + logbuf[bufidx] = '\0'; + log_it(usernm, getpid(), "CMDOUT", logbuf, 0); + } + } +#endif + + /* if there was output and we could not mail it, + * log the facts so the poor user can figure out + * what's going on. + */ + if (mail && status) { + char buf[MAX_TEMPSTR]; + + sprintf(buf, + "mailed %d byte%s of output but got status 0x%04x\n", + bytes, (bytes == 1) ? "" : "s", status); + log_it(usernm, getpid(), "MAIL", buf, 0); + } + + } /*if data from grandchild */ + + Debug(DPROC, ("[%ld] got EOF from grandchild\n", (long) getpid())) + + fclose(in); /* also closes stdout_pipe[READ_PIPE] */ + } + + /* wait for children to die. + */ + for (; children > 0; children--) { + WAIT_T waiter; + PID_T pid; + + Debug(DPROC, ("[%ld] waiting for grandchild #%d to finish\n", + (long) getpid(), children)) + while ((pid = wait(&waiter)) < OK && errno == EINTR) ; + if (pid < OK) { + Debug(DPROC, + ("[%ld] no more grandchildren--mail written?\n", + (long) getpid())) + break; + } + Debug(DPROC, ("[%ld] grandchild #%ld finished, status=%04x", + (long) getpid(), (long) pid, WEXITSTATUS(waiter))) + if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) + Debug(DPROC, (", dumped core")) + Debug(DPROC, ("\n")) + } + return OK_EXIT; +} + +static int safe_p(const char *usernm, const char *s) { + static const char safe_delim[] = "@!:%-.,_+"; /* conservative! */ + const char *t; + int ch, first; + + for (t = s, first = 1; (ch = *t++) != '\0'; first = 0) { + if (isascii(ch) && isprint(ch) && + (isalnum(ch) || (!first && strchr(safe_delim, ch)))) + continue; + log_it(usernm, getpid(), "UNSAFE", s, 0); + return (FALSE); + } + return (TRUE); +} diff --git a/src/entry.c b/src/entry.c new file mode 100644 index 0000000..5d377c6 --- /dev/null +++ b/src/entry.c @@ -0,0 +1,580 @@ +/* + * Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* vix 26jan87 [RCS'd; rest of log is in RCS file] + * vix 01jan87 [added line-level error recovery] + * vix 31dec86 [added /step to the from-to range, per bob@acornrc] + * vix 30dec86 [written] + */ + +#include + +typedef enum ecode { + e_none, e_minute, e_hour, e_dom, e_month, e_dow, + e_cmd, e_timespec, e_username, e_option, e_memory +} ecode_e; + +static const char *ecodes[] = { + "no error", + "bad minute", + "bad hour", + "bad day-of-month", + "bad month", + "bad day-of-week", + "bad command", + "bad time specifier", + "bad username", + "bad option", + "out of memory" +}; + +static int get_list(bitstr_t *, int, int, const char *[], int, FILE *), +get_range(bitstr_t *, int, int, const char *[], int, FILE *), +get_number(int *, int, const char *[], int, FILE *, const char *), +set_element(bitstr_t *, int, int, int); + +void free_entry(entry * e) { + free(e->cmd); + free(e->pwd); + env_free(e->envp); + free(e); +} + +/* return NULL if eof or syntax error occurs; + * otherwise return a pointer to a new entry. + */ +entry *load_entry(FILE * file, void (*error_func) (), struct passwd *pw, + char **envp) { + /* this function reads one crontab entry -- the next -- from a file. + * it skips any leading blank lines, ignores comments, and returns + * NULL if for any reason the entry can't be read and parsed. + * + * the entry is also parsed here. + * + * syntax: + * user crontab: + * minutes hours doms months dows cmd\n + * system crontab (/etc/crontab): + * minutes hours doms months dows USERNAME cmd\n + */ + + ecode_e ecode = e_none; + entry *e; + int ch; + char cmd[MAX_COMMAND]; + char envstr[MAX_ENVSTR]; + char **tenvp; + + Debug(DPARS, ("load_entry()...about to eat comments\n")) + + skip_comments(file); + + ch = get_char(file); + if (ch == EOF) + return (NULL); + + /* ch is now the first useful character of a useful line. + * it may be an @special or it may be the first character + * of a list of minutes. + */ + + e = (entry *) calloc(sizeof (entry), sizeof (char)); + + /* check for '-' as a first character, this option will disable + * writing a syslog message about command getting executed + */ + if (ch == '-') { + /* if we are editing system crontab or user uid is 0 (root) + * we are allowed to disable logging + */ + if (pw == NULL || pw->pw_uid == 0) + e->flags |= DONT_LOG; + else { + log_it("CRON", getpid(), "ERROR", "Only privileged user can disable logging", 0); + ecode = e_option; + goto eof; + } + ch = get_char(file); + if (ch == EOF) + return NULL; + } + + if (ch == '@') { + /* all of these should be flagged and load-limited; i.e., + * instead of @hourly meaning "0 * * * *" it should mean + * "close to the front of every hour but not 'til the + * system load is low". Problems are: how do you know + * what "low" means? (save me from /etc/cron.conf!) and: + * how to guarantee low variance (how low is low?), which + * means how to we run roughly every hour -- seems like + * we need to keep a history or let the first hour set + * the schedule, which means we aren't load-limited + * anymore. too much for my overloaded brain. (vix, jan90) + * HINT + */ + ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); + if (!strcmp("reboot", cmd)) { + e->flags |= WHEN_REBOOT; + } + else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)) { + bit_set(e->minute, 0); + bit_set(e->hour, 0); + bit_set(e->dom, 0); + bit_set(e->month, 0); + bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW); + e->flags |= DOW_STAR; + } + else if (!strcmp("monthly", cmd)) { + bit_set(e->minute, 0); + bit_set(e->hour, 0); + bit_set(e->dom, 0); + bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH); + bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW); + e->flags |= DOW_STAR; + } + else if (!strcmp("weekly", cmd)) { + bit_set(e->minute, 0); + bit_set(e->hour, 0); + bit_nset(e->dom, 0, LAST_DOM - FIRST_DOM); + bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH); + bit_set(e->dow, 0); + e->flags |= DOW_STAR; + } + else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { + bit_set(e->minute, 0); + bit_set(e->hour, 0); + bit_nset(e->dom, 0, LAST_DOM - FIRST_DOM); + bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH); + bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW); + } + else if (!strcmp("hourly", cmd)) { + bit_set(e->minute, 0); + bit_nset(e->hour, 0, LAST_HOUR - FIRST_HOUR); + bit_nset(e->dom, 0, LAST_DOM - FIRST_DOM); + bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH); + bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW); + e->flags |= HR_STAR; + } + else { + ecode = e_timespec; + goto eof; + } + /* Advance past whitespace between shortcut and + * username/command. + */ + Skip_Blanks(ch, file); + if (ch == EOF || ch == '\n') { + ecode = e_cmd; + goto eof; + } + } + else { + Debug(DPARS, ("load_entry()...about to parse numerics\n")) + + if (ch == '*') + e->flags |= MIN_STAR; + ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, PPC_NULL, ch, file); + if (ch == EOF) { + ecode = e_minute; + goto eof; + } + + /* hours + */ + + if (ch == '*') + e->flags |= HR_STAR; + ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, PPC_NULL, ch, file); + if (ch == EOF) { + ecode = e_hour; + goto eof; + } + + /* DOM (days of month) + */ + + if (ch == '*') + e->flags |= DOM_STAR; + ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, file); + if (ch == EOF) { + ecode = e_dom; + goto eof; + } + + /* month + */ + + ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, MonthNames, ch, file); + if (ch == EOF) { + ecode = e_month; + goto eof; + } + + /* DOW (days of week) + */ + + if (ch == '*') + e->flags |= DOW_STAR; + ch = get_list(e->dow, FIRST_DOW, LAST_DOW, DowNames, ch, file); + if (ch == EOF) { + ecode = e_dow; + goto eof; + } + } + + /* make sundays equivalent */ + if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { + bit_set(e->dow, 0); + bit_set(e->dow, 7); + } + + /* check for permature EOL and catch a common typo */ + if (ch == '\n' || ch == '*') { + ecode = e_cmd; + goto eof; + } + + /* ch is the first character of a command, or a username */ + unget_char(ch, file); + + if (!pw) { + char *username = cmd; /* temp buffer */ + + Debug(DPARS, ("load_entry()...about to parse username\n")) + ch = get_string(username, MAX_COMMAND, file, " \t\n"); + + Debug(DPARS, ("load_entry()...got %s\n", username)) + if (ch == EOF || ch == '\n' || ch == '*') { + ecode = e_cmd; + goto eof; + } + + pw = getpwnam(username); + if (pw == NULL) { + ecode = e_username; + goto eof; + } + Debug(DPARS, ("load_entry()...uid %ld, gid %ld\n", + (long) pw->pw_uid, (long) pw->pw_gid)) + } + + if ((e->pwd = pw_dup(pw)) == NULL) { + ecode = e_memory; + goto eof; + } + bzero(e->pwd->pw_passwd, strlen(e->pwd->pw_passwd)); + + /* copy and fix up environment. some variables are just defaults and + * others are overrides. + */ + if ((e->envp = env_copy(envp)) == NULL) { + ecode = e_memory; + goto eof; + } + if (!env_get("SHELL", e->envp)) { + if (glue_strings(envstr, sizeof envstr, "SHELL", _PATH_BSHELL, '=')) { + if ((tenvp = env_set(e->envp, envstr)) == NULL) { + ecode = e_memory; + goto eof; + } + e->envp = tenvp; + } + else + log_it("CRON", getpid(), "ERROR", "can't set SHELL", 0); + } + if (!env_get("HOME", e->envp)) { + if (glue_strings(envstr, sizeof envstr, "HOME", pw->pw_dir, '=')) { + if ((tenvp = env_set(e->envp, envstr)) == NULL) { + ecode = e_memory; + goto eof; + } + e->envp = tenvp; + } + else + log_it("CRON", getpid(), "ERROR", "can't set HOME", 0); + } +#ifndef LOGIN_CAP + /* If login.conf is in used we will get the default PATH later. */ + if (!env_get("PATH", e->envp)) { + if (glue_strings(envstr, sizeof envstr, "PATH", _PATH_DEFPATH, '=')) { + if ((tenvp = env_set(e->envp, envstr)) == NULL) { + ecode = e_memory; + goto eof; + } + e->envp = tenvp; + } + else + log_it("CRON", getpid(), "ERROR", "can't set PATH", 0); + } +#endif /* LOGIN_CAP */ + if (glue_strings(envstr, sizeof envstr, "LOGNAME", pw->pw_name, '=')) { + if ((tenvp = env_set(e->envp, envstr)) == NULL) { + ecode = e_memory; + goto eof; + } + e->envp = tenvp; + } + else + log_it("CRON", getpid(), "ERROR", "can't set LOGNAME", 0); +#if defined(BSD) || defined(__linux) + if (glue_strings(envstr, sizeof envstr, "USER", pw->pw_name, '=')) { + if ((tenvp = env_set(e->envp, envstr)) == NULL) { + ecode = e_memory; + goto eof; + } + e->envp = tenvp; + } + else + log_it("CRON", getpid(), "ERROR", "can't set USER", 0); +#endif + + Debug(DPARS, ("load_entry()...about to parse command\n")) + + /* Everything up to the next \n or EOF is part of the command... + * too bad we don't know in advance how long it will be, since we + * need to malloc a string for it... so, we limit it to MAX_COMMAND. + */ + ch = get_string(cmd, MAX_COMMAND, file, "\n"); + + /* a file without a \n before the EOF is rude, so we'll complain... + */ + if (ch == EOF) { + ecode = e_cmd; + goto eof; + } + + /* got the command in the 'cmd' string; save it in *e. + */ + if ((e->cmd = strdup(cmd)) == NULL) { + ecode = e_memory; + goto eof; + } + + Debug(DPARS, ("load_entry()...returning successfully\n")) + + /* success, fini, return pointer to the entry we just created... + */ + return (e); + + eof: + if (e->envp) + env_free(e->envp); + if (e->pwd) + free(e->pwd); + if (e->cmd) + free(e->cmd); + free(e); + while (ch != '\n' && !feof(file)) + ch = get_char(file); + if (ecode != e_none && error_func) + (*error_func) (ecodes[(int) ecode]); + return (NULL); +} + +static int +get_list(bitstr_t * bits, int low, int high, const char *names[], + int ch, FILE * file) { + int done; + + /* we know that we point to a non-blank character here; + * must do a Skip_Blanks before we exit, so that the + * next call (or the code that picks up the cmd) can + * assume the same thing. + */ + + Debug(DPARS | DEXT, ("get_list()...entered\n")) + + /* list = range {"," range} + */ + /* clear the bit string, since the default is 'off'. + */ + bit_nclear(bits, 0, (high - low + 1)); + + /* process all ranges + */ + done = FALSE; + while (!done) { + if (EOF == (ch = get_range(bits, low, high, names, ch, file))) + return (EOF); + if (ch == ',') + ch = get_char(file); + else + done = TRUE; + } + + /* exiting. skip to some blanks, then skip over the blanks. + */ + Skip_Nonblanks(ch, file) + Skip_Blanks(ch, file) + + Debug(DPARS | DEXT, ("get_list()...exiting w/ %02x\n", ch)) + + return (ch); +} + + +static int +get_range(bitstr_t * bits, int low, int high, const char *names[], + int ch, FILE * file) { + /* range = number | number "-" number [ "/" number ] + */ + + int i, num1, num2, num3; + + Debug(DPARS | DEXT, ("get_range()...entering, exit won't show\n")) + + if (ch == '*') { + /* '*' means "first-last" but can still be modified by /step + */ + num1 = low; + num2 = high; + ch = get_char(file); + if (ch == EOF) + return (EOF); + } + else { + ch = get_number(&num1, low, names, ch, file, ",- \t\n"); + if (ch == EOF) + return (EOF); + + if (ch != '-') { + /* not a range, it's a single number. + */ + if (EOF == set_element(bits, low, high, num1)) { + unget_char(ch, file); + return (EOF); + } + return (ch); + } + else { + /* eat the dash + */ + ch = get_char(file); + if (ch == EOF) + return (EOF); + + /* get the number following the dash + */ + ch = get_number(&num2, low, names, ch, file, "/, \t\n"); + if (ch == EOF || num1 > num2) + return (EOF); + } + } + + /* check for step size + */ + if (ch == '/') { + /* eat the slash + */ + ch = get_char(file); + if (ch == EOF) + return (EOF); + + /* get the step size -- note: we don't pass the + * names here, because the number is not an + * element id, it's a step size. 'low' is + * sent as a 0 since there is no offset either. + */ + ch = get_number(&num3, 0, PPC_NULL, ch, file, ", \t\n"); + if (ch == EOF || num3 == 0) + return (EOF); + } + else { + /* no step. default==1. + */ + num3 = 1; + } + + /* range. set all elements from num1 to num2, stepping + * by num3. (the step is a downward-compatible extension + * proposed conceptually by bob@acornrc, syntactically + * designed then implemented by paul vixie). + */ + for (i = num1; i <= num2; i += num3) + if (EOF == set_element(bits, low, high, i)) { + unget_char(ch, file); + return (EOF); + } + + return (ch); +} + +static int +get_number(int *numptr, int low, const char *names[], int ch, FILE * file, + const char *terms) { + char temp[MAX_TEMPSTR], *pc; + int len, i; + + pc = temp; + len = 0; + + /* first look for a number */ + while (isdigit((unsigned char) ch)) { + if (++len >= MAX_TEMPSTR) + goto bad; + *pc++ = ch; + ch = get_char(file); + } + *pc = '\0'; + if (len != 0) { + /* got a number, check for valid terminator */ + if (!strchr(terms, ch)) + goto bad; + *numptr = atoi(temp); + return (ch); + } + + /* no numbers, look for a string if we have any */ + if (names) { + while (isalpha((unsigned char) ch)) { + if (++len >= MAX_TEMPSTR) + goto bad; + *pc++ = ch; + ch = get_char(file); + } + *pc = '\0'; + if (len != 0 && strchr(terms, ch)) { + for (i = 0; names[i] != NULL; i++) { + Debug(DPARS | DEXT, + ("get_num, compare(%s,%s)\n", names[i], temp)) + if (!strcasecmp(names[i], temp)) { + *numptr = i + low; + return (ch); + } + } + } + } + + bad: + unget_char(ch, file); + return (EOF); +} + +static int set_element(bitstr_t * bits, int low, int high, int number) { + Debug(DPARS | DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)) + + if (number < low || number > high) + return (EOF); + + bit_set(bits, (number - low)); + return (OK); +} diff --git a/src/env.c b/src/env.c new file mode 100644 index 0000000..d187ed9 --- /dev/null +++ b/src/env.c @@ -0,0 +1,242 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +#include + +char **env_init(void) { + char **p = (char **) malloc(sizeof (char *)); + + if (p != NULL) + p[0] = NULL; + return (p); +} + +void env_free(char **envp) { + char **p; + + for (p = envp; *p != NULL; p++) + free(*p); + free(envp); +} + +char **env_copy(char **envp) { + int count, i, save_errno; + char **p; + + for (count = 0; envp[count] != NULL; count++) ; + + p = (char **) malloc((count + 1) * sizeof (char *)); /* 1 for the NULL */ + if (p != NULL) { + for (i = 0; i < count; i++) + if ((p[i] = strdup(envp[i])) == NULL) { + save_errno = errno; + while (--i >= 0) + free(p[i]); + free(p); + errno = save_errno; + return (NULL); + } + p[count] = NULL; + } + return (p); +} + +char **env_set(char **envp, char *envstr) { + int count, found; + char **p, *envtmp; + + /* + * count the number of elements, including the null pointer; + * also set 'found' to -1 or index of entry if already in here. + */ + found = -1; + for (count = 0; envp[count] != NULL; count++) { + if (!strcmp_until(envp[count], envstr, '=')) + found = count; + } + count++; /* for the NULL */ + + if (found != -1) { + /* + * it exists already, so just free the existing setting, + * save our new one there, and return the existing array. + */ + if ((envtmp = strdup(envstr)) == NULL) + return (NULL); + free(envp[found]); + envp[found] = envtmp; + return (envp); + } + + /* + * it doesn't exist yet, so resize the array, move null pointer over + * one, save our string over the old null pointer, and return resized + * array. + */ + if ((envtmp = strdup(envstr)) == NULL) + return (NULL); + p = (char **) realloc((void *) envp, + (size_t) ((count + 1) * sizeof (char *))); + if (p == NULL) { + free(envtmp); + return (NULL); + } + p[count] = p[count - 1]; + p[count - 1] = envtmp; + return (p); +} + +/* The following states are used by load_env(), traversed in order: */ +enum env_state { + NAMEI, /* First char of NAME, may be quote */ + NAME, /* Subsequent chars of NAME */ + EQ1, /* After end of name, looking for '=' sign */ + EQ2, /* After '=', skipping whitespace */ + VALUEI, /* First char of VALUE, may be quote */ + VALUE, /* Subsequent chars of VALUE */ + FINI, /* All done, skipping trailing whitespace */ + ERROR, /* Error */ +}; + +/* return ERR = end of file + * FALSE = not an env setting (file was repositioned) + * TRUE = was an env setting + */ +int load_env(char *envstr, FILE * f) { + long filepos; + int fileline; + enum env_state state; + char name[MAX_ENVSTR], val[MAX_ENVSTR]; + char quotechar, *c, *str; + + filepos = ftell(f); + fileline = LineNumber; + skip_comments(f); + if (EOF == get_string(envstr, MAX_ENVSTR, f, "\n")) + return (ERR); + + Debug(DPARS, ("load_env, read <%s>\n", envstr)) + + bzero(name, sizeof name); + bzero(val, sizeof val); + str = name; + state = NAMEI; + quotechar = '\0'; + c = envstr; + while (state != ERROR && *c) { + switch (state) { + case NAMEI: + case VALUEI: + if (*c == '\'' || *c == '"') + quotechar = *c++; + state++; + /* FALLTHROUGH */ + case NAME: + case VALUE: + if (quotechar) { + if (*c == quotechar) { + state++; + c++; + break; + } + if (state == NAME && *c == '=') { + state = ERROR; + break; + } + } + else { + if (state == NAME) { + if (isspace((unsigned char) *c)) { + c++; + state++; + break; + } + if (*c == '=') { + state++; + break; + } + } + } + *str++ = *c++; + break; + + case EQ1: + if (*c == '=') { + state++; + str = val; + quotechar = '\0'; + } + else { + if (!isspace((unsigned char) *c)) + state = ERROR; + } + c++; + break; + + case EQ2: + case FINI: + if (isspace((unsigned char) *c)) + c++; + else + state++; + break; + + default: + abort(); + } + } + if (state != FINI && !(state == VALUE && !quotechar)) { + Debug(DPARS, ("load_env, not an env var, state = %d\n", state)) + fseek(f, filepos, 0); + Set_LineNum(fileline); + return (FALSE); + } + if (state == VALUE) { + /* End of unquoted value: trim trailing whitespace */ + c = val + strlen(val); + while (c > val && isspace((unsigned char) c[-1])) + *(--c) = '\0'; + } + + /* 2 fields from parser; looks like an env setting */ + + /* + * This can't overflow because get_string() limited the size of the + * name and val fields. Still, it doesn't hurt to be careful... + */ + if (!glue_strings(envstr, MAX_ENVSTR, name, val, '=')) + return (FALSE); + Debug(DPARS, ("load_env, <%s> <%s> -> <%s>\n", name, val, envstr)) + return (TRUE); +} + +char *env_get(char *name, char **envp) { + int len = strlen(name); + char *p, *q; + + while ((p = *envp++) != NULL) { + if (!(q = strchr(p, '='))) + continue; + if ((q - p) == len && !strncmp(p, name, len)) + return (q + 1); + } + return (NULL); +} diff --git a/src/externs.h b/src/externs.h new file mode 100644 index 0000000..9c01d2c --- /dev/null +++ b/src/externs.h @@ -0,0 +1,127 @@ +/* Copyright 1993,1994 by Paul Vixie + * All rights reserved + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* reorder these #include's at your peril */ + +#include +#include +#include +#include +#ifdef HAVE_SYS_FCNTL_H +#include +#endif +#include +/* stat is used even, when --with-inotify */ +#include + +#include +#include +#ifndef isascii +#define isascii(c) ((unsigned)(c)<=0177) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(SYSLOG) +# include +#endif + +#if defined(LOGIN_CAP) +# include +#endif /*LOGIN_CAP*/ + +#if defined(BSD_AUTH) +# include +#endif /*BSD_AUTH*/ + +/* include locale stuff for mailer "Content-Type": + */ +#include +#include +#include + +#define DIR_T struct dirent +#define WAIT_T int +#define SIG_T sig_t +#define TIME_T time_t +#define PID_T pid_t + +#ifndef TZNAME_ALREADY_DEFINED +extern char *tzname[2]; +#endif +#define TZONE(tm) tzname[(tm).tm_isdst] + +#if (defined(BSD)) && (BSD >= 199103) || defined(__linux) || defined(__sun) || defined(_AIX) +# define HAVE_SAVED_UIDS +#endif + +#define MY_UID(pw) getuid() +#define MY_GID(pw) getgid() + +/* getopt() isn't part of POSIX. some systems define it in anyway. + * of those that do, some complain that our definition is different and some + * do not. to add to the misery and confusion, some systems define getopt() + * in ways that we cannot predict or comprehend, yet do not define the adjunct + * external variables needed for the interface. + */ +#if (!defined(BSD) || (BSD < 198911)) +int getopt(int, char * const *, const char *); +#endif + +#if (!defined(BSD) || (BSD < 199103)) +extern char *optarg; +extern int optind, opterr, optopt; +#endif + +/* digital unix needs this but does not give us a way to identify it. + */ +extern int flock(int, int); + +/* not all systems who provide flock() provide these definitions. + */ +#ifndef LOCK_SH +# define LOCK_SH 1 +#endif +#ifndef LOCK_EX +# define LOCK_EX 2 +#endif +#ifndef LOCK_NB +# define LOCK_NB 4 +#endif +#ifndef LOCK_UN +# define LOCK_UN 8 +#endif + +#ifndef WCOREDUMP +# define WCOREDUMP(st) (((st) & 0200) != 0) +#endif diff --git a/src/funcs.h b/src/funcs.h new file mode 100644 index 0000000..aee4a99 --- /dev/null +++ b/src/funcs.h @@ -0,0 +1,113 @@ +/* + * $Id: funcs.h,v 1.9 2004/01/23 18:56:42 vixie Exp $ + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* Notes: + * This file has to be included by cron.h after data structure defs. + * We should reorg this into sections by module. + */ + +void set_cron_uid(void), + check_spool_dir(void), + open_logfile(void), + sigpipe_func(void), + job_add(entry *, user *), + do_command(entry *, user *), + link_user(cron_db *, user *), + unlink_user(cron_db *, user *), + free_user(user *), + env_free(char **), + unget_char(int, FILE *), + free_entry(entry *), + acquire_daemonlock(int), + skip_comments(FILE *), + log_it(const char *, PID_T, const char *, const char *, int), + log_close(void), + check_orphans(cron_db *); +#if defined WITH_INOTIFY +void set_cron_watched(int ), + set_cron_unwatched(int ), + check_inotify_database(cron_db *); +#endif + +int load_database(cron_db *), + job_runqueue(void), + set_debug_flags(const char *), + get_char(FILE *), + get_string(char *, int, FILE *, char *), + swap_uids(void), + swap_uids_back(void), + load_env(char *, FILE *), + cron_pclose(FILE *), + glue_strings(char *, size_t, const char *, const char *, char), + strcmp_until(const char *, const char *, char), + allowed(const char * ,const char * ,const char *), + strdtb(char *); + +size_t strlens(const char *, ...); + +char *env_get(char *, char **), + *arpadate(time_t *), + *mkprints(unsigned char *, unsigned int), + *first_word(char *, char *), + **env_init(void), + **env_copy(char **), + **env_set(char **, char *); + +user *load_user(int, struct passwd *, const char *, const char *, const char *), + *find_user(cron_db *, const char *, const char *); + +entry *load_entry(FILE *, void (*)(), struct passwd *, char **); + +FILE *cron_popen(char *, const char *, struct passwd *); + +struct passwd *pw_dup(const struct passwd *); + +#ifndef HAVE_STRUCT_TM_TM_GMTOFF +long get_gmtoff(time_t *, struct tm *); +#endif + +/* Red Hat security stuff (security.c): + */ +void cron_restore_default_security_context( void ); + +int cron_set_job_security_context( entry *e, user *u, char ***jobenvp ); + +int cron_open_security_session( struct passwd *pw ); + +void cron_close_security_session( void ); + +int cron_change_groups( struct passwd *pw ); + +int cron_change_user_permanently( struct passwd *pw, char *homedir ); + +int get_security_context(const char *name, + int crontab_fd, + security_context_t *rcontext, + const char *tabname + ); + +void free_security_context( security_context_t *scontext ); + +int crontab_security_access(void); + +/* PAM */ +int cron_start_pam(struct passwd *pw); +void cron_close_pam(void); diff --git a/src/globals.h b/src/globals.h new file mode 100644 index 0000000..c7a811d --- /dev/null +++ b/src/globals.h @@ -0,0 +1,89 @@ +/* + * $Id: globals.h,v 1.10 2004/01/23 19:03:33 vixie Exp $ + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* + * Modified 2010/09/12 by Colin Dean, Durham University IT Service, + * to add clustering support. + */ + +#ifdef MAIN_PROGRAM +# define XTRN +# define INIT(x) = x +#else +# define XTRN extern +# define INIT(x) +#endif + +XTRN const char *copyright[] +#ifdef MAIN_PROGRAM + = { + "@(#) ISC Cron V4.1", + "@(#) Copyright 1988,1989,1990,1993,1994 by Paul Vixie", + "@(#) Copyright 1997,2000 by Internet Software Consortium, Inc.", + "@(#) Copyright 2004 by Internet Systems Consortium, Inc.", + "@(#) All rights reserved", + NULL + } +#endif + ; + +XTRN const char *MonthNames[] +#ifdef MAIN_PROGRAM + = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + NULL + } +#endif + ; + +XTRN const char *DowNames[] +#ifdef MAIN_PROGRAM + = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", + NULL + } +#endif + ; + +XTRN char *ProgramName; +XTRN int LineNumber; +XTRN int SyslogOutput; +XTRN time_t StartTime; +XTRN int NoFork; +XTRN int PermitAnyCrontab; +XTRN char MailCmd[MAX_COMMAND]; +XTRN char cron_default_mail_charset[MAX_ENVSTR]; +XTRN int EnableClustering; + +#if DEBUGGING +XTRN int DebugFlags INIT(0); +XTRN const char *DebugFlagNames[] +#ifdef MAIN_PROGRAM + = { + "ext", "sch", "proc", "pars", "load", "misc", "test", "bit", + NULL + } +#endif + ; +#else +#define DebugFlags 0 +#endif /* DEBUGGING */ diff --git a/src/job.c b/src/job.c new file mode 100644 index 0000000..5ea7d98 --- /dev/null +++ b/src/job.c @@ -0,0 +1,67 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +#include + +typedef struct _job { + struct _job *next; + entry *e; + user *u; +} job; + +static job *jhead = NULL, *jtail = NULL; + +void job_add(entry * e, user * u) { + job *j; + + /* if already on queue, keep going */ + for (j = jhead; j != NULL; j = j->next) + if (j->e == e && j->u == u) + return; + + /* build a job queue element */ + if ((j = (job *) malloc(sizeof (job))) == NULL) + return; + j->next = NULL; + j->e = e; + j->u = u; + + /* add it to the tail */ + if (jhead == NULL) + jhead = j; + else + jtail->next = j; + jtail = j; +} + +int job_runqueue(void) { + job *j, *jn; + int run = 0; + + for (j = jhead; j; j = jn) { + do_command(j->e, j->u); + jn = j->next; + free(j); + run++; + } + jhead = jtail = NULL; + return (run); +} diff --git a/src/macros.h b/src/macros.h new file mode 100644 index 0000000..4981a01 --- /dev/null +++ b/src/macros.h @@ -0,0 +1,132 @@ +/* + * $Id: macros.h,v 1.9 2004/01/23 18:56:43 vixie Exp $ + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ +#ifdef HAVE_LIMITS_H +#include +#endif + /* these are really immutable, and are + * defined for symbolic convenience only + * TRUE, FALSE, and ERR must be distinct + * ERR must be < OK. + */ +#define TRUE 1 +#define FALSE 0 + /* system calls return this on success */ +#define OK 0 + /* or this on error */ +#define ERR (-1) + + /* turn this on to get '-x' code */ +#ifndef DEBUGGING +#define DEBUGGING FALSE +#endif + +#define INIT_PID 1 /* parent of orphans */ +#define READ_PIPE 0 /* which end of a pipe pair do you read? */ +#define WRITE_PIPE 1 /* or write to? */ +#define STDIN 0 /* what is stdin's file descriptor? */ +#define STDOUT 1 /* stdout's? */ +#define STDERR 2 /* stderr's? */ +#define ERROR_EXIT 1 /* exit() with this will scare the shell */ +#define OK_EXIT 0 /* exit() with this is considered 'normal' */ +#define MAX_FNAME PATH_MAX/* max length of internally generated fn */ +#define MAX_COMMAND 131072 /* max length of internally generated cmd (max sh cmd line length) */ +#define MAX_ENVSTR 131072 /* max length of envvar=value\0 strings */ +#define MAX_TEMPSTR 131072 /* obvious */ +#define MAX_UNAME 256 /* max length of username */ +#define ROOT_UID 0 /* don't change this, it really must be root */ +#define ROOT_USER "root" /* ditto */ + + /* NOTE: these correspond to DebugFlagNames, + * defined below. + */ +#define DEXT 0x0001 /* extend flag for other debug masks */ +#define DSCH 0x0002 /* scheduling debug mask */ +#define DPROC 0x0004 /* process control debug mask */ +#define DPARS 0x0008 /* parsing debug mask */ +#define DLOAD 0x0010 /* database loading debug mask */ +#define DMISC 0x0020 /* misc debug mask */ +#define DTEST 0x0040 /* test mode: don't execute any commands */ + +#define PPC_NULL ((const char **)NULL) + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +#define Skip_Blanks(c, f) \ + while (c == '\t' || c == ' ') \ + c = get_char(f); + +#define Skip_Nonblanks(c, f) \ + while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \ + c = get_char(f); + +#if DEBUGGING +# define Debug(mask, message) \ + if ((DebugFlags & (mask)) != 0) \ + printf message; +#else /* !DEBUGGING */ +# define Debug(mask, message) \ + ; +#endif /* DEBUGGING */ + +#define MkUpper(ch) (islower(ch) ? toupper(ch) : ch) +#define Set_LineNum(ln) {Debug(DPARS|DEXT,("linenum=%d\n",ln)); \ + LineNumber = ln; \ + } + +#ifdef HAVE_STRUCT_TM_TM_GMTOFF +#define get_gmtoff(c, t) ((t)->tm_gmtoff) +#endif + +#define SECONDS_PER_MINUTE 60 +#define SECONDS_PER_HOUR 3600 + +#define FIRST_MINUTE 0 +#define LAST_MINUTE 59 +#define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1) + +#define FIRST_HOUR 0 +#define LAST_HOUR 23 +#define HOUR_COUNT (LAST_HOUR - FIRST_HOUR + 1) + +#define FIRST_DOM 1 +#define LAST_DOM 31 +#define DOM_COUNT (LAST_DOM - FIRST_DOM + 1) + +#define FIRST_MONTH 1 +#define LAST_MONTH 12 +#define MONTH_COUNT (LAST_MONTH - FIRST_MONTH + 1) + +/* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */ +#define FIRST_DOW 0 +#define LAST_DOW 7 +#define DOW_COUNT (LAST_DOW - FIRST_DOW + 1) + +/* + * Because crontab/at files may be owned by their respective users we + * take extreme care in opening them. If the OS lacks the O_NOFOLLOW + * we will just have to live without it. In order for this to be an + * issue an attacker would have to subvert group CRON_GROUP. + */ +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif diff --git a/src/misc.c b/src/misc.c new file mode 100644 index 0000000..a617709 --- /dev/null +++ b/src/misc.c @@ -0,0 +1,754 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* vix 26jan87 [RCS has the rest of the log] + * vix 30dec86 [written] + */ + +#include +#ifdef WITH_AUDIT +# include +#endif + +#ifdef HAVE_FCNTL_H /* fcntl(2) */ +# include +#endif +#ifdef HAVE_UNISTD_H /* lockf(3) */ +# include +#endif +#ifdef HAVE_FLOCK /* flock(2) */ +# include +#endif +#include + +#if defined(SYSLOG) && defined(LOG_FILE) +# undef LOG_FILE +#endif + +#if defined(LOG_DAEMON) && !defined(LOG_CRON) +# define LOG_CRON LOG_DAEMON +#endif + +#ifndef FACILITY +# define FACILITY LOG_CRON +#endif + +static int LogFD = ERR; + +#if defined(SYSLOG) +static int syslog_open = FALSE; +#endif + +#if defined(HAVE_FCNTL) && defined(F_SETLK) +static int trylock_file(int fd) { + struct flock fl; + + memset(&fl, '\0', sizeof (fl)); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + return fcntl(fd, F_SETLK, &fl); +} +#elif defined(HAVE_LOCKF) +# define trylock_file(fd) lockf((fd), F_TLOCK, 0) +#elif defined(HAVE_FLOCK) +# define trylock_file(fd) flock((fd), LOCK_EX|LOCK_NB) +#endif + +/* + * glue_strings is the overflow-safe equivalent of + * sprintf(buffer, "%s%c%s", a, separator, b); + * + * returns 1 on success, 0 on failure. 'buffer' MUST NOT be used if + * glue_strings fails. + */ +int +glue_strings(char *buffer, size_t buffer_size, const char *a, const char *b, + char separator) { + char *buf; + char *buf_end; + + if (buffer_size <= 0) + return (0); + buf_end = buffer + buffer_size; + buf = buffer; + + for ( /* nothing */ ; buf < buf_end && *a != '\0'; buf++, a++) + *buf = *a; + if (buf == buf_end) + return (0); + if (separator != '/' || buf == buffer || buf[-1] != '/') + *buf++ = separator; + if (buf == buf_end) + return (0); + for ( /* nothing */ ; buf < buf_end && *b != '\0'; buf++, b++) + *buf = *b; + if (buf == buf_end) + return (0); + *buf = '\0'; + return (1); +} + +int strcmp_until(const char *left, const char *right, char until) { + while (*left && *left != until && *left == *right) { + left++; + right++; + } + + if ((*left == '\0' || *left == until) && (*right == '\0' || + *right == until)) { + return (0); + } + return (*left - *right); +} + +/* strdtb(s) - delete trailing blanks in string 's' and return new length + */ +int strdtb(char *s) { + char *x = s; + + /* scan forward to the null + */ + while (*x) + x++; + + /* scan backward to either the first character before the string, + * or the last non-blank in the string, whichever comes first. + */ + do { + x--; + } while (x >= s && isspace((unsigned char) *x)); + + /* one character beyond where we stopped above is where the null + * goes. + */ + *++x = '\0'; + + /* the difference between the position of the null character and + * the position of the first character of the string is the length. + */ + return (x - s); +} + +int set_debug_flags(const char *flags) { + /* debug flags are of the form flag[,flag ...] + * + * if an error occurs, print a message to stdout and return FALSE. + * otherwise return TRUE after setting ERROR_FLAGS. + */ + +#if !DEBUGGING + + printf("this program was compiled without debugging enabled\n"); + return (FALSE); + +#else /* DEBUGGING */ + + const char *pc = flags; + + DebugFlags = 0; + + while (*pc) { + const char **test; + int mask; + + /* try to find debug flag name in our list. + */ + for (test = DebugFlagNames, mask = 1; + *test != NULL && strcmp_until(*test, pc, ','); test++, mask <<= 1) ; + + if (!*test) { + fprintf(stderr, "unrecognized debug flag <%s> <%s>\n", flags, pc); + return (FALSE); + } + + DebugFlags |= mask; + + /* skip to the next flag + */ + while (*pc && *pc != ',') + pc++; + if (*pc == ',') + pc++; + } + + if (DebugFlags) { + int flag; + + fprintf(stderr, "debug flags enabled:"); + + for (flag = 0; DebugFlagNames[flag]; flag++) + if (DebugFlags & (1 << flag)) + fprintf(stderr, " %s", DebugFlagNames[flag]); + fprintf(stderr, "\n"); + } + + return (TRUE); + +#endif /* DEBUGGING */ +} + +void set_cron_uid(void) { +#if defined(BSD) || defined(POSIX) + if (seteuid(ROOT_UID) < OK) { + perror("seteuid"); + exit(ERROR_EXIT); + } +#else + if (setuid(ROOT_UID) < OK) { + perror("setuid"); + exit(ERROR_EXIT); + } +#endif +} + +void check_spool_dir(void) { + struct stat sb; +#ifdef CRON_GROUP + struct group *grp = NULL; + + grp = getgrnam(CRON_GROUP); +#endif + /* check SPOOL_DIR existence + */ + if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) { + perror(SPOOL_DIR); + if (OK == mkdir(SPOOL_DIR, 0700)) { + fprintf(stderr, "%s: created\n", SPOOL_DIR); + if (stat(SPOOL_DIR, &sb) < OK) { + perror("stat retry"); + exit(ERROR_EXIT); + } + } + else { + fprintf(stderr, "%s: ", SPOOL_DIR); + perror("mkdir"); + exit(ERROR_EXIT); + } + } + if (!S_ISDIR(sb.st_mode)) { + fprintf(stderr, "'%s' is not a directory, bailing out.\n", SPOOL_DIR); + exit(ERROR_EXIT); + } +#ifdef CRON_GROUP + if (grp != NULL) { + if (sb.st_gid != grp->gr_gid) + if (chown(SPOOL_DIR, -1, grp->gr_gid) == -1) { + fprintf(stderr, "chown %s failed: %s\n", SPOOL_DIR, + strerror(errno)); + exit(ERROR_EXIT); + } + if (sb.st_mode != 01730) + if (chmod(SPOOL_DIR, 01730) == -1) { + fprintf(stderr, "chmod 01730 %s failed: %s\n", SPOOL_DIR, + strerror(errno)); + exit(ERROR_EXIT); + } + } +#endif +} + +/* acquire_daemonlock() - write our PID into /etc/cron.pid, unless + * another daemon is already running, which we detect here. + * + * note: main() calls us twice; once before forking, once after. + * we maintain static storage of the file pointer so that we + * can rewrite our PID into _PATH_CRON_PID after the fork. + */ +void acquire_daemonlock(int closeflag) { + static int fd = -1; + char buf[3 * MAX_FNAME]; + const char *pidfile; + char *ep; + long otherpid = -1; + ssize_t num, len; + pid_t pid = getpid(); + + if (closeflag) { + /* close stashed fd for child so we don't leak it. */ + if (fd != -1) { + close(fd); + fd = -1; + } + return; + } + + if (fd == -1) { + pidfile = _PATH_CRON_PID; + /* Initial mode is 0600 to prevent flock() race/DoS. */ + if ((fd = open(pidfile, O_RDWR | O_CREAT, 0600)) == -1) { + int save_errno = errno; + sprintf(buf, "can't open or create %s", pidfile); + fprintf(stderr, "%s: %s: %s\n", ProgramName, buf, + strerror(save_errno)); + log_it("CRON", pid, "DEATH", buf, save_errno); + exit(ERROR_EXIT); + } + + if (trylock_file(fd) < OK) { + int save_errno = errno; + + bzero(buf, sizeof (buf)); + if ((num = read(fd, buf, sizeof (buf) - 1)) > 0 && + (otherpid = strtol(buf, &ep, 10)) > 0 && + ep != buf && *ep == '\n' && otherpid != LONG_MAX) { + snprintf(buf, sizeof (buf), + "can't lock %s, otherpid may be %ld", pidfile, otherpid); + } + else { + snprintf(buf, sizeof (buf), + "can't lock %s, otherpid unknown", pidfile); + } + fprintf(stderr, "%s: %s: %s\n", ProgramName, buf, + strerror(save_errno)); + log_it("CRON", pid, "DEATH", buf, save_errno); + exit(ERROR_EXIT); + } + (void) fchmod(fd, 0644); + (void) fcntl(fd, F_SETFD, 1); + } + + sprintf(buf, "%ld\n", (long) pid); + (void) lseek(fd, (off_t) 0, SEEK_SET); + len = strlen(buf); + if ((num = write(fd, buf, len)) != len) + log_it("CRON", pid, "ERROR", "write() failed", errno); + else { + if (ftruncate(fd, num) == -1) + log_it("CRON", pid, "ERROR", "ftruncate() failed", errno); + } + + /* abandon fd even though the file is open. we need to keep + * it open and locked, but we don't need the handles elsewhere. + */ +} + +/* get_char(file) : like getc() but increment LineNumber on newlines + */ +int get_char(FILE * file) { + int ch; + + ch = getc(file); + if (ch == '\n') + Set_LineNum(LineNumber + 1) + return (ch); +} + +/* unget_char(ch, file) : like ungetc but do LineNumber processing + */ +void unget_char(int ch, FILE * file) { + ungetc(ch, file); + if (ch == '\n') + Set_LineNum(LineNumber - 1) +} + +/* get_string(str, max, file, termstr) : like fgets() but + * (1) has terminator string which should include \n + * (2) will always leave room for the null + * (3) uses get_char() so LineNumber will be accurate + * (4) returns EOF or terminating character, whichever + */ +int get_string(char *string, int size, FILE * file, char *terms) { + int ch; + + while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) { + if (size > 1) { + *string++ = (char) ch; + size--; + } + } + + if (size > 0) + *string = '\0'; + + return (ch); +} + +/* skip_comments(file) : read past comment (if any) + */ +void skip_comments(FILE * file) { + int ch; + + while (EOF != (ch = get_char(file))) { + /* ch is now the first character of a line. + */ + while (ch == ' ' || ch == '\t') + ch = get_char(file); + + if (ch == EOF) + break; + + /* ch is now the first non-blank character of a line. + */ + + if (ch != '\n' && ch != '#') + break; + + /* ch must be a newline or comment as first non-blank + * character on a line. + */ + + while (ch != '\n' && ch != EOF) + ch = get_char(file); + + /* ch is now the newline of a line which we're going to + * ignore. + */ + } + if (ch != EOF) + unget_char(ch, file); +} + +/* int in_file(const char *string, FILE *file, int error) + * return TRUE if one of the lines in file matches string exactly, + * FALSE if no lines match, and error on error. + */ +static int in_file(const char *string, FILE * file, int error) { + char line[MAX_TEMPSTR]; + char *endp; + + if (fseek(file, 0L, SEEK_SET)) + return (error); + while (fgets(line, MAX_TEMPSTR, file)) { + if (line[0] != '\0') { + endp = &line[strlen(line) - 1]; + if (*endp != '\n') + return (error); + *endp = '\0'; + if (0 == strcmp(line, string)) + return (TRUE); + } + } + if (ferror(file)) + return (error); + return (FALSE); +} + +/* int allowed(const char *username, const char *allow_file, const char *deny_file) + * returns TRUE if (allow_file exists and user is listed) + * or (deny_file exists and user is NOT listed). + * root is always allowed. + */ +int allowed(const char *username, const char *allow_file, + const char *deny_file) { + FILE *fp; + int isallowed; + char buf[128]; + + if (getuid() == 0) + return TRUE; + isallowed = FALSE; + if ((fp = fopen(allow_file, "r")) != NULL) { + isallowed = in_file(username, fp, FALSE); + fclose(fp); + if ((getuid() == 0) && (!isallowed)) { + snprintf(buf, sizeof (buf), + "root used -u for user %s not in cron.allow", username); + log_it("crontab", getpid(), "warning", buf, 0); + isallowed = TRUE; + } + } + else if ((fp = fopen(deny_file, "r")) != NULL) { + isallowed = !in_file(username, fp, FALSE); + fclose(fp); + if ((getuid() == 0) && (!isallowed)) { + snprintf(buf, sizeof (buf), + "root used -u for user %s in cron.deny", username); + log_it("crontab", getpid(), "warning", buf, 0); + isallowed = TRUE; + } + } +#ifdef WITH_AUDIT + if (isallowed == FALSE) { + int audit_fd = audit_open(); + audit_log_user_message(audit_fd, AUDIT_USER_START, "cron deny", + NULL, NULL, NULL, 0); + close(audit_fd); + } +#endif + return (isallowed); +} + +void log_it(const char *username, PID_T xpid, const char *event, + const char *detail, int err) { +#if defined(LOG_FILE) || DEBUGGING + PID_T pid = xpid; +#endif +#if defined(LOG_FILE) + char *msg; + TIME_T now = time((TIME_T) 0); + struct tm *t = localtime(&now); + int msg_size; +#endif + +#if defined(LOG_FILE) + /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation. + */ + msg = malloc(msg_size = (strlen(username) + + strlen(event) + + strlen(detail) + + MAX_TEMPSTR) + ); + if (msg == NULL) { /* damn, out of mem and we did not test that before... */ + fprintf(stderr, "%s: Run OUT OF MEMORY while %s\n", + ProgramName, __FUNCTION__); + return; + } + if (LogFD < OK) { + LogFD = open(LOG_FILE, O_WRONLY | O_APPEND | O_CREAT, 0600); + if (LogFD < OK) { + fprintf(stderr, "%s: can't open log file\n", ProgramName); + perror(LOG_FILE); + } + else { + (void) fcntl(LogFD, F_SETFD, 1); + } + } + + /* we have to snprintf() it because fprintf() doesn't always write + * everything out in one chunk and this has to be atomically appended + * to the log file. + */ + snprintf(msg, msg_size, + "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)%s%s\n", username, + t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, pid, + event, detail, err != 0 ? ": " : "", err != 0 ? strerror(err) : ""); + + /* we have to run strlen() because sprintf() returns (char*) on old BSD + */ + if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) { + if (LogFD >= OK) + perror(LOG_FILE); + fprintf(stderr, "%s: can't write to log file\n", ProgramName); + write(STDERR, msg, strlen(msg)); + } + + free(msg); +#endif /*LOG_FILE */ + +#if defined(SYSLOG) + if (!syslog_open) { +# ifdef LOG_DAEMON + openlog(ProgramName, LOG_PID, FACILITY); +# else + openlog(ProgramName, LOG_PID); +# endif + syslog_open = TRUE; /* assume openlog success */ + } + + syslog(err != 0 ? LOG_ERR : LOG_INFO, + "(%s) %s (%s)%s%s", username, event, detail, + err != 0 ? ": " : "", err != 0 ? strerror(err) : ""); + + +#endif /*SYSLOG*/ +#if DEBUGGING + if (DebugFlags) { + fprintf(stderr, "log_it: (%s %ld) %s (%s)%s%s\n", + username, (long) pid, event, detail, + err != 0 ? ": " : "", err != 0 ? strerror(err) : ""); + } +#endif +} + +void log_close(void) { + if (LogFD != ERR) { + close(LogFD); + LogFD = ERR; + } +#if defined(SYSLOG) + closelog(); + syslog_open = FALSE; +#endif /*SYSLOG*/ +} + +/* char *first_word(char *s, char *t) + * return pointer to first word + * parameters: + * s - string we want the first word of + * t - terminators, implicitly including \0 + * warnings: + * (1) this routine is fairly slow + * (2) it returns a pointer to static storage + */ +char *first_word(char *s, char *t) { + static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */ + static int retsel = 0; + char *rb, *rp; + + /* select a return buffer */ + retsel = 1 - retsel; + rb = &retbuf[retsel][0]; + rp = rb; + + /* skip any leading terminators */ + while (*s && (NULL != strchr(t, *s))) { + s++; + } + + /* copy until next terminator or full buffer */ + while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) { + *rp++ = *s++; + } + + /* finish the return-string and return it */ + *rp = '\0'; + return (rb); +} + +/* warning: + * heavily ascii-dependent. + */ +void mkprint(char *dst, unsigned char *src, int len) { +/* + * XXX + * We know this routine can't overflow the dst buffer because mkprints() + * allocated enough space for the worst case. +*/ + while (len-- > 0) { + unsigned char ch = *src++; + + if (ch < ' ') { /* control character */ + *dst++ = '^'; + *dst++ = ch + '@'; + } + else if (ch < 0177) { /* printable */ + *dst++ = ch; + } + else if (ch == 0177) { /* delete/rubout */ + *dst++ = '^'; + *dst++ = '?'; + } + else { /* parity character */ + sprintf(dst, "\\%03o", ch); + dst += 4; + } + } + *dst = '\0'; +} + +/* warning: + * returns a pointer to malloc'd storage, you must call free yourself. + */ +char *mkprints(unsigned char *src, unsigned int len) { + char *dst = malloc(len * 4 + 1); + + if (dst) + mkprint(dst, src, len); + + return (dst); +} + +#ifdef MAIL_DATE +/* Sat, 27 Feb 1993 11:44:51 -0800 (CST) + * 1234567890123456789012345678901234567 + */ +char *arpadate(time_t *clock) { + time_t t = clock ? *clock : time((TIME_T) 0); + struct tm tm = *localtime(&t); + long gmtoff = get_gmtoff(&t, &tm); + int hours = gmtoff / SECONDS_PER_HOUR; + int minutes = + (gmtoff - (hours * SECONDS_PER_HOUR)) / SECONDS_PER_MINUTE; + static char ret[64]; /* zone name might be >3 chars */ + + (void) sprintf(ret, "%s, %2d %s %2d %02d:%02d:%02d %.2d%.2d (%s)", + DowNames[tm.tm_wday], + tm.tm_mday, + MonthNames[tm.tm_mon], + tm.tm_year + 1900, + tm.tm_hour, tm.tm_min, tm.tm_sec, hours, minutes, TZONE(tm)); + return (ret); +} +#endif /*MAIL_DATE */ + +#ifdef HAVE_SAVED_UIDS + static uid_t save_euid; + static gid_t save_egid; + +int swap_uids(void) { + save_egid = getegid(); + save_euid = geteuid(); + return ((setegid(getgid()) || seteuid(getuid()))? -1 : 0); +} + +int swap_uids_back(void) { + return ((setegid(save_egid) || seteuid(save_euid)) ? -1 : 0); +} + +#else /*HAVE_SAVED_UIDS */ + +int swap_uids(void) { + return ((setregid(getegid(), getgid()) + || setreuid(geteuid(), getuid())) ? -1 : 0); +} + +int swap_uids_back(void) { + return (swap_uids()); +} +#endif /*HAVE_SAVED_UIDS */ + +size_t strlens(const char *last, ...) { + va_list ap; + size_t ret = 0; + const char *str; + + va_start(ap, last); + for (str = last; str != NULL; str = va_arg(ap, const char *)) + ret += strlen(str); + va_end(ap); + return (ret); +} + +/* Return the offset from GMT in seconds (algorithm taken from sendmail). + * + * warning: + * clobbers the static storage space used by localtime() and gmtime(). + * If the local pointer is non-NULL it *must* point to a local copy. + */ +#ifndef HAVE_STRUCT_TM_TM_GMTOFF +long get_gmtoff(time_t * clock, struct tm *local) { + struct tm gmt; + long offset; + + gmt = *gmtime(clock); + if (local == NULL) + local = localtime(clock); + + offset = (local->tm_sec - gmt.tm_sec) + + ((local->tm_min - gmt.tm_min) * 60) + + ((local->tm_hour - gmt.tm_hour) * 3600); + + /* Timezone may cause year rollover to happen on a different day. */ + if (local->tm_year < gmt.tm_year) + offset -= 24 * 3600; + else if (local->tm_year > gmt.tm_year) + offset += 24 * 3600; + else if (local->tm_yday < gmt.tm_yday) + offset -= 24 * 3600; + else if (local->tm_yday > gmt.tm_yday) + offset += 24 * 3600; + + return (offset); +} +#endif /* HAVE_STRUCT_TM_TM_GMTOFF */ diff --git a/src/pathnames.h b/src/pathnames.h new file mode 100644 index 0000000..b5d5c70 --- /dev/null +++ b/src/pathnames.h @@ -0,0 +1,73 @@ +/* Copyright 1993,1994 by Paul Vixie + * All rights reserved + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* + * $Id: pathnames.h,v 1.9 2004/01/23 18:56:43 vixie Exp $ + */ + +#ifndef _PATHNAMES_H_ +#define _PATHNAMES_H_ + +#if (defined(BSD)) && (BSD >= 199103) || defined(__linux) || defined(AIX) +# include +#endif /*BSD*/ + +#include "cron-paths.h" + + /* where should the daemon stick its PID? + * PIDDIR must end in '/'. + * (Don't ask why the default is "/etc/".) + */ +#ifdef _PATH_VARRUN +# define PIDDIR _PATH_VARRUN +#else +# define PIDDIR SYSCONFDIR "/" +#endif +#define PIDFILE "crond.pid" +#define _PATH_CRON_PID PIDDIR PIDFILE +#define REBOOT_LOCK PIDDIR "cron.reboot" + + /* what editor to use if no EDITOR or VISUAL + * environment variable specified. + */ +#if defined(_PATH_VI) +# define EDITOR _PATH_VI +#else +# define EDITOR "/usr/ucb/vi" +#endif + +#ifndef _PATH_BSHELL +# define _PATH_BSHELL "/bin/sh" +#endif + +#ifndef _PATH_DEFPATH +# define _PATH_DEFPATH "/usr/bin:/bin" +#endif + +#ifndef _PATH_TMP +# define _PATH_TMP "/tmp" +#endif + +#ifndef _PATH_DEVNULL +# define _PATH_DEVNULL "/dev/null" +#endif + +#endif /* _PATHNAMES_H_ */ diff --git a/src/popen.c b/src/popen.c new file mode 100644 index 0000000..baf0724 --- /dev/null +++ b/src/popen.c @@ -0,0 +1,165 @@ +/* $NetBSD: popen.c,v 1.9 2005/03/16 02:53:55 xtraeme Exp $ */ + +/* + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software written by Ken Arnold and + * published in UNIX Review, Vol. 6, No. 8. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifdef HAVE_SYS_CDEFS_H +# include +#endif + +#include +#include + +/* + * Special version of popen which avoids call to shell. This insures noone + * may create a pipe to a hidden program as a side effect of a list or dir + * command. + */ +static PID_T *pids; +static int fds; + +#define MAX_ARGS 1024 + +FILE *cron_popen(char *program, const char *type, struct passwd *pw) { + char *cp; + FILE *iop; + int argc, pdes[2]; + PID_T pid; + char *argv[MAX_ARGS]; + ssize_t out; + char buf[PIPE_BUF]; + struct sigaction sa; + +#ifdef __GNUC__ + (void) &iop; /* Avoid fork clobbering */ +#endif + + if ((*type != 'r' && *type != 'w') || type[1]) + return (NULL); + + if (!pids) { + if ((fds = getdtablesize()) <= 0) + return (NULL); + if (!(pids = (PID_T *) malloc((u_int) (fds * sizeof (PID_T))))) + return (NULL); + bzero((char *) pids, fds * sizeof (PID_T)); + } + if (pipe(pdes) < 0) + return (NULL); + + /* break up string into pieces */ + for (argc = 0, cp = program; argc < MAX_ARGS; cp = NULL) + if (!(argv[argc++] = strtok(cp, " \t\n"))) + break; + + iop = NULL; + switch (pid = fork()) { + case -1: /* error */ + (void) close(pdes[0]); + (void) close(pdes[1]); + goto pfree; + /* NOTREACHED */ + case 0: /* child */ + if (*type == 'r') { + if (pdes[1] != STDOUT) { + dup2(pdes[1], STDOUT); + dup2(pdes[1], STDERR); /* stderr, too! */ + (void) close(pdes[1]); + } + (void) close(pdes[0]); + } + else { + if (pdes[0] != STDIN) { + dup2(pdes[0], STDIN); + (void) close(pdes[0]); + } + (void) close(pdes[1]); + } + + /* reset SIGPIPE to default for the child */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigaction(SIGPIPE, &sa, NULL); + + if (cron_change_user_permanently(pw, pw->pw_dir) != 0) + _exit(2); + + if (execvp(argv[0], argv) < 0) { + int save_errno = errno; + + log_it("CRON", getpid(), "EXEC FAILED", program, save_errno); + if (*type != 'r') { + while (0 != (out = read(STDIN, buf, PIPE_BUF))) { + if ((out == -1) && (errno != EINTR)) + break; + } + } + } + _exit(1); + } + /* parent; assume fdopen can't fail... */ + if (*type == 'r') { + iop = fdopen(pdes[0], type); + (void) close(pdes[1]); + } + else { + iop = fdopen(pdes[1], type); + (void) close(pdes[0]); + } + pids[fileno(iop)] = pid; + + pfree: + return (iop); +} + +int cron_pclose(FILE * iop) { + int fdes; + sigset_t oset, nset; + WAIT_T stat_loc; + PID_T pid; + + /* + * pclose returns -1 if stream is not associated with a + * `popened' command, or, if already `pclosed'. + */ + if (pids == 0 || pids[fdes = fileno(iop)] == 0) + return (-1); + (void) fclose(iop); + + sigemptyset(&nset); + sigaddset(&nset, SIGINT); + sigaddset(&nset, SIGQUIT); + sigaddset(&nset, SIGHUP); + (void) sigprocmask(SIG_BLOCK, &nset, &oset); + while ((pid = wait(&stat_loc)) != pids[fdes] && pid != -1) ; + (void) sigprocmask(SIG_SETMASK, &oset, NULL); + pids[fdes] = 0; + return (pid == -1 ? -1 : WEXITSTATUS(stat_loc)); +} diff --git a/src/pw_dup.c b/src/pw_dup.c new file mode 100644 index 0000000..519dc75 --- /dev/null +++ b/src/pw_dup.c @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2000,2002 Todd C. Miller + * + * 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 TODD C. MILLER DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER 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. + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +#include +#include + +#if !defined(OpenBSD) || OpenBSD < 200105 + +#include +#include +#include +#include + +struct passwd * +pw_dup(const struct passwd *pw) { + char *cp; + size_t nsize=0, psize=0, gsize=0, dsize=0, ssize=0, total=0; + struct passwd *newpw; + + /* Allocate in one big chunk for easy freeing */ + total = sizeof(struct passwd); + if (pw->pw_name) { + nsize = strlen(pw->pw_name) + 1; + total += nsize; + } + if (pw->pw_passwd) { + psize = strlen(pw->pw_passwd) + 1; + total += psize; + } +#ifdef LOGIN_CAP + if (pw->pw_class) { + csize = strlen(pw->pw_class) + 1; + total += csize; + } +#endif /* LOGIN_CAP */ + if (pw->pw_gecos) { + gsize = strlen(pw->pw_gecos) + 1; + total += gsize; + } + if (pw->pw_dir) { + dsize = strlen(pw->pw_dir) + 1; + total += dsize; + } + if (pw->pw_shell) { + ssize = strlen(pw->pw_shell) + 1; + total += ssize; + } + if ((cp = malloc(total)) == NULL) + return (NULL); + newpw = (struct passwd *)cp; + + /* + * Copy in passwd contents and make strings relative to space + * at the end of the buffer. + */ + (void)memcpy(newpw, pw, sizeof(struct passwd)); + cp += sizeof(struct passwd); + if (pw->pw_name) { + (void)memcpy(cp, pw->pw_name, nsize); + newpw->pw_name = cp; + cp += nsize; + } + if (pw->pw_passwd) { + (void)memcpy(cp, pw->pw_passwd, psize); + newpw->pw_passwd = cp; + cp += psize; + } +#ifdef LOGIN_CAP + if (pw->pw_class) { + (void)memcpy(cp, pw->pw_class, csize); + newpw->pw_class = cp; + cp += csize; + } +#endif /* LOGIN_CAP */ + if (pw->pw_gecos) { + (void)memcpy(cp, pw->pw_gecos, gsize); + newpw->pw_gecos = cp; + cp += gsize; + } + if (pw->pw_dir) { + (void)memcpy(cp, pw->pw_dir, dsize); + newpw->pw_dir = cp; + cp += dsize; + } + if (pw->pw_shell) { + (void)memcpy(cp, pw->pw_shell, ssize); + newpw->pw_shell = cp; + cp += ssize; + } + + return (newpw); +} + +#endif /* !OpenBSD || OpenBSD < 200105 */ diff --git a/src/security.c b/src/security.c new file mode 100644 index 0000000..f6940a5 --- /dev/null +++ b/src/security.c @@ -0,0 +1,615 @@ +/* security.c + * + * Implement Red Hat crond security context transitions + * + * Jason Vas Dias January 2006 + * + * Copyright(C) Red Hat Inc., 2006 + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +#include + +#ifdef WITH_SELINUX +# include +# include +# include +# include +# include +#endif + +#ifdef WITH_AUDIT +# include +#endif + +#ifdef WITH_PAM +static pam_handle_t *pamh = NULL; +static int pam_session_opened = 0; //global for open session + +static int +cron_conv(int num_msg, const struct pam_message **msgm, + struct pam_response **response, void *appdata_ptr) +{ + int i; + + for (i = 0; i < num_msg; i++) { + switch (msgm[i]->msg_style) { + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + if (msgm[i]->msg != NULL) { + log_it("CRON", getpid(), "pam_message", msgm[i]->msg, 0); + } + break; + default: + break; + } + } + return (0); +} + +static const struct pam_conv conv = { + cron_conv, NULL +}; + +static int cron_open_pam_session(struct passwd *pw); + +# define PAM_FAIL_CHECK if (retcode != PAM_SUCCESS) { \ + log_it(pw->pw_name, getpid(), "PAM ERROR", pam_strerror(pamh, retcode), 0); \ + if (pamh != NULL) { \ + if (pam_session_opened != 0) \ + pam_close_session(pamh, PAM_SILENT); \ + pam_end(pamh, retcode); \ + } \ +return(retcode); } +#endif + +static char **build_env(char **cronenv); + +#ifdef WITH_SELINUX +static int cron_change_selinux_range(user * u, security_context_t ucontext); +static int cron_get_job_range(user * u, security_context_t * ucontextp, + char **jobenv); +#endif + +void cron_restore_default_security_context() { +#ifdef WITH_SELINUX + setexeccon(NULL); +#endif +} + +int cron_set_job_security_context(entry * e, user * u, char ***jobenv) { + time_t minutely_time = 0; +#ifdef WITH_PAM + int ret; +#endif + + if ((e->flags & MIN_STAR) == MIN_STAR) { + /* "minute-ly" job: Every minute for given hour/dow/month/dom. + * Ensure that these jobs never run in the same minute: + */ + minutely_time = time(0); + Debug(DSCH, ("Minute-ly job. Recording time %lu\n", minutely_time)) + } + +#ifdef WITH_PAM + if ((ret = cron_start_pam(e->pwd)) != 0) { + log_it(e->pwd->pw_name, getpid(), "FAILED to authorize user with PAM", + pam_strerror(pamh, ret), 0); + return -1; + } +#endif + + *jobenv = build_env(e->envp); + +#ifdef WITH_SELINUX + /* we must get the crontab context BEFORE changing user, else + * we'll not be permitted to read the cron spool directory :-) + */ + security_context_t ucontext = 0; + + if (cron_get_job_range(u, &ucontext, *jobenv) < OK) { + log_it(e->pwd->pw_name, getpid(), "ERROR", + "failed to get SELinux context", 0); + return -1; + } + + if (cron_change_selinux_range(u, ucontext) != 0) { + log_it(e->pwd->pw_name, getpid(), "ERROR", + "failed to change SELinux context", 0); + if (ucontext) + freecon(ucontext); + return -1; + } + if (ucontext) + freecon(ucontext); +#endif +#ifdef WITH_PAM + if ((ret = cron_open_pam_session(e->pwd)) != 0) { + log_it(e->pwd->pw_name, getpid(), + "FAILED to open PAM security session", pam_strerror(pamh, ret), 0); + return -1; + } +#endif + + if (cron_change_groups(e->pwd) != 0) { + return -1; + } + + time_t job_run_time = time(0L); + + if ((minutely_time > 0) && ((job_run_time / 60) != (minutely_time / 60))) { + /* if a per-minute job is delayed into the next minute + * (eg. by network authentication method timeouts), skip it. + */ + struct tm tmS, tmN; + char buf[256]; + + localtime_r(&job_run_time, &tmN); + localtime_r(&minutely_time, &tmS); + + snprintf(buf, sizeof (buf), + "Job execution of per-minute job scheduled for " + "%.2u:%.2u delayed into subsequent minute %.2u:%.2u. Skipping job run.", + tmS.tm_hour, tmS.tm_min, tmN.tm_hour, tmN.tm_min); + log_it(e->pwd->pw_name, getpid(), "INFO", buf, 0); + return -1; + } + return 0; +} + +int cron_start_pam(struct passwd *pw) { + int retcode = 0; + +#if defined(WITH_PAM) + retcode = pam_start("crond", pw->pw_name, &conv, &pamh); + PAM_FAIL_CHECK; + retcode = pam_set_item(pamh, PAM_TTY, "cron"); + PAM_FAIL_CHECK; + retcode = pam_acct_mgmt(pamh, PAM_SILENT); + PAM_FAIL_CHECK; + retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED | PAM_SILENT); + PAM_FAIL_CHECK; +#endif + + return retcode; +} + +static int cron_open_pam_session(struct passwd *pw) { + int retcode = 0; + +#if defined(WITH_PAM) + retcode = pam_open_session(pamh, PAM_SILENT); + PAM_FAIL_CHECK; + if (retcode == PAM_SUCCESS) + pam_session_opened = 1; +#endif + + return retcode; +} + +void cron_close_pam(void) { +#if defined(WITH_PAM) + if (pam_session_opened != 0) { + pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT); + pam_close_session(pamh, PAM_SILENT); + } + pam_end(pamh, PAM_SUCCESS); +#endif +} + +int cron_change_groups(struct passwd *pw) { + pid_t pid = getpid(); + + if (setgid(pw->pw_gid) != 0) { + log_it("CRON", pid, "ERROR", "setgid failed", errno); + return -1; + } + + if (initgroups(pw->pw_name, pw->pw_gid) != 0) { + log_it("CRON", pid, "ERROR", "initgroups failed", errno); + return -1; + } + +#if defined(WITH_PAM) + /* credentials may take form of supplementary groups so reinitialize + * them here */ + pam_setcred(pamh, PAM_REINITIALIZE_CRED | PAM_SILENT); +#endif + + return 0; +} + +int cron_change_user_permanently(struct passwd *pw, char *homedir) { + if (setreuid(pw->pw_uid, pw->pw_uid) != 0) { + log_it("CRON", getpid(), "ERROR", "setreuid failed", errno); + return -1; + } + + if (chdir(homedir) == -1) { + log_it("CRON", getpid(), "ERROR chdir failed", homedir, errno); + return -1; + } + + log_close(); + + return 0; +} + + +static int cron_authorize_context(security_context_t scontext, + security_context_t file_context) { +#ifdef WITH_SELINUX + struct av_decision avd; + int retval; + security_class_t tclass; + access_vector_t bit; + + tclass = string_to_security_class("file"); + if (!tclass) { + log_it("CRON", getpid(), "ERROR", "Failed to translate security class file", errno); + return 0; + } + bit = string_to_av_perm(tclass, "entrypoint"); + if (!bit) { + log_it("CRON", getpid(), "ERROR", "Failed to translate av perm entrypoint", errno); + return 0; + } + /* + * Since crontab files are not directly executed, + * crond must ensure that the crontab file has + * a context that is appropriate for the context of + * the user cron job. It performs an entrypoint + * permission check for this purpose. + */ + retval = security_compute_av(scontext, file_context, + tclass, bit, &avd); + if (retval || ((bit & avd.allowed) != bit)) + return 0; +#endif + return 1; +} + +static int cron_authorize_range(security_context_t scontext, + security_context_t ucontext) { +#ifdef WITH_SELINUX + struct av_decision avd; + int retval; + security_class_t tclass; + access_vector_t bit; + + tclass = string_to_security_class("context"); + if (!tclass) { + log_it("CRON", getpid(), "ERROR", "Failed to translate security class context", errno); + return 0; + } + bit = string_to_av_perm(tclass, "contains"); + if (!bit) { + log_it("CRON", getpid(), "ERROR", "Failed to translate av perm contains", errno); + return 0; + } + + /* + * Since crontab files are not directly executed, + * so crond must ensure that any user specified range + * falls within the seusers-specified range for that Linux user. + */ + retval = security_compute_av(scontext, ucontext, + tclass, bit, &avd); + + if (retval || ((bit & avd.allowed) != bit)) + return 0; +#endif + return 1; +} + +#if WITH_SELINUX +/* always uses u->scontext as the default process context, then changes the + level, and retuns it in ucontextp (or NULL otherwise) */ +static int +cron_get_job_range(user * u, security_context_t * ucontextp, char **jobenv) { + char *range; + + if (is_selinux_enabled() <= 0) + return 0; + if (ucontextp == 0L) + return -1; + + *ucontextp = 0L; + + if ((range = env_get("MLS_LEVEL", jobenv)) != 0L) { + context_t ccon; + if (!(ccon = context_new(u->scontext))) { + log_it(u->name, getpid(), "context_new FAILED for MLS_LEVEL", + range, 0); + context_free(ccon); + return -1; + } + + if (context_range_set(ccon, range)) { + log_it(u->name, getpid(), + "context_range_set FAILED for MLS_LEVEL", range, 0); + context_free(ccon); + return -1; + } + + if (!(*ucontextp = context_str(ccon))) { + log_it(u->name, getpid(), "context_str FAILED for MLS_LEVEL", + range, 0); + context_free(ccon); + return -1; + } + + if (!(*ucontextp = strdup(*ucontextp))) { + log_it(u->name, getpid(), "strdup FAILED for MLS_LEVEL", range, 0); + return -1; + } + context_free(ccon); + } + else if (!u->scontext) { + /* cron_change_selinux_range() deals with this */ + return 0; + } + else if (!(*ucontextp = strdup(u->scontext))) { + log_it(u->name, getpid(), "strdup FAILED for MLS_LEVEL", range, 0); + return -1; + } + + return 0; +} +#endif + +#ifdef WITH_SELINUX +static int cron_change_selinux_range(user * u, security_context_t ucontext) { + char *msg = NULL; + + if (is_selinux_enabled() <= 0) + return 0; + + if (u->scontext == 0L) { + if (security_getenforce() > 0) { + log_it(u->name, getpid(), "NULL security context for user", "", 0); + return -1; + } + else { + log_it(u->name, getpid(), + "NULL security context for user, " + "but SELinux in permissive mode, continuing", "", 0); + return 0; + } + } + + if (strcmp(u->scontext, ucontext)) { + if (!cron_authorize_range(u->scontext, ucontext)) { + if (security_getenforce() > 0) { +# ifdef WITH_AUDIT + if (asprintf(&msg, + "cron: Unauthorized MLS range acct=%s new_scontext=%s old_scontext=%s", + u->name, (char *) ucontext, u->scontext) >= 0) { + int audit_fd = audit_open(); + audit_log_user_message(audit_fd, AUDIT_USER_ROLE_CHANGE, + msg, NULL, NULL, NULL, 0); + close(audit_fd); + free(msg); + } +# endif + if (asprintf + (&msg, "Unauthorized range in %s for user range in %s", + (char *) ucontext, u->scontext) >= 0) { + log_it(u->name, getpid(), "ERROR", msg, 0); + free(msg); + } + return -1; + } + else { + if (asprintf + (&msg, + "Unauthorized range in %s for user range in %s," + " but SELinux in permissive mod, continuing", + (char *) ucontext, u->scontext) >= 0) { + log_it(u->name, getpid(), "WARNING", msg, 0); + free(msg); + } + } + } + } + + if (setexeccon(ucontext) < 0 || setkeycreatecon(ucontext) < 0) { + if (security_getenforce() > 0) { + if (asprintf + (&msg, "Could not set exec or keycreate context to %s for user", + (char *) ucontext) >= 0) { + log_it(u->name, getpid(), "ERROR", msg, 0); + free(msg); + } + return -1; + } + else { + if (asprintf + (&msg, + "Could not set exec or keycreate context to %s for user," + " but SELinux in permissive mode, continuing", + (char *) ucontext) >= 0) { + log_it(u->name, getpid(), "WARNING", msg, 0); + free(msg); + } + return 0; + } + } + return 0; +} +#endif + +int +get_security_context(const char *name, int crontab_fd, + security_context_t * rcontext, const char *tabname) { +#ifdef WITH_SELINUX + security_context_t scontext = NULL; + security_context_t file_context = NULL; + int retval = 0; + char *seuser = NULL; + char *level = NULL; + + *rcontext = NULL; + + if (is_selinux_enabled() <= 0) + return 0; + + if (name != NULL) { + if (getseuserbyname(name, &seuser, &level) < 0) { + log_it(name, getpid(), "getseuserbyname FAILED", name, 0); + return (security_getenforce() > 0); + } + } + + retval = get_default_context_with_level(name == NULL ? "system_u" : seuser, + level, NULL, &scontext); + free(seuser); + free(level); + if (retval) { + if (security_getenforce() > 0) { + log_it(name, getpid(), "No SELinux security context", tabname, 0); + return -1; + } + else { + log_it(name, getpid(), + "No security context but SELinux in permissive mode, continuing", + tabname, 0); + return 0; + } + } + + if (fgetfilecon(crontab_fd, &file_context) < OK) { + if (security_getenforce() > 0) { + log_it(name, getpid(), "getfilecon FAILED", tabname, 0); + freecon(scontext); + return -1; + } + else { + log_it(name, getpid(), + "getfilecon FAILED but SELinux in permissive mode, continuing", + tabname, 0); + *rcontext = scontext; + return 0; + } + } + + if (!cron_authorize_context(scontext, file_context)) { + char *msg=NULL; + if (asprintf(&msg, + "Unauthorized SELinux context=%s file_context=%s", (char *) scontext, file_context) >= 0) { + log_it(name, getpid(), msg, tabname, 0); + free(msg); + } else { + log_it(name, getpid(), "Unauthorized SELinux context", tabname, 0); + } + freecon(scontext); + freecon(file_context); + if (security_getenforce() > 0) { + return -1; + } + else { + log_it(name, getpid(), + "SELinux in permissive mode, continuing", + tabname, 0); + return 0; + } + } + freecon(file_context); + + *rcontext = scontext; +#endif + return 0; +} + +void free_security_context(security_context_t * scontext) { +#ifdef WITH_SELINUX + if (*scontext != NULL) { + freecon(*scontext); + *scontext = 0L; + } +#endif +} + +int crontab_security_access(void) { +#ifdef WITH_SELINUX + int selinux_check_passwd_access = -1; + if (is_selinux_enabled() > 0) { + security_context_t user_context; + if (getprevcon_raw(&user_context) == 0) { + security_class_t passwd_class; + access_vector_t crontab_bit; + struct av_decision avd; + int retval = 0; + + passwd_class = string_to_security_class("passwd"); + if (passwd_class == 0) { + fprintf(stderr, "Security class \"passwd\" is not defined in the SELinux policy.\n"); + retval = -1; + } + + if (retval == 0) { + crontab_bit = string_to_av_perm(passwd_class, "crontab"); + if (crontab_bit == 0) { + fprintf(stderr, "Security av permission \"crontab\" is not defined in the SELinux policy.\n"); + retval = -1; + } + } + + if (retval == 0) + retval = security_compute_av_raw(user_context, + user_context, passwd_class, + crontab_bit, &avd); + + if ((retval == 0) && ((crontab_bit & avd.allowed) == crontab_bit)) { + selinux_check_passwd_access = 0; + } + freecon(user_context); + } + + if (selinux_check_passwd_access != 0 && security_getenforce() == 0) + selinux_check_passwd_access = 0; + + return selinux_check_passwd_access; + } +#endif + return 0; +} + +/* Build up the job environment from the PAM environment plus the +* crontab environment +*/ +static char **build_env(char **cronenv) { +#ifdef WITH_PAM + char **jobenv = cronenv; + char **pamenv = pam_getenvlist(pamh); + char *cronvar; + int count = 0; + jobenv = env_copy(pamenv); + + /* Now add the cron environment variables. Since env_set() + * overwrites existing variables, this will let cron's + * environment settings override pam's */ + + while ((cronvar = cronenv[count++])) { + if (!(jobenv = env_set(jobenv, cronvar))) { + log_it("CRON", getpid(), + "Setting Cron environment variable failed", cronvar, 0); + return NULL; + } + } + return jobenv; +#else + return env_copy(cronenv); +#endif +} diff --git a/src/structs.h b/src/structs.h new file mode 100644 index 0000000..16cd180 --- /dev/null +++ b/src/structs.h @@ -0,0 +1,77 @@ +/* + * $Id: structs.h,v 1.7 2004/01/23 18:56:43 vixie Exp $ + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +typedef struct _entry { + struct _entry *next; + struct passwd *pwd; + char **envp; + char *cmd; + bitstr_t bit_decl(minute, MINUTE_COUNT); + bitstr_t bit_decl(hour, HOUR_COUNT); + bitstr_t bit_decl(dom, DOM_COUNT); + bitstr_t bit_decl(month, MONTH_COUNT); + bitstr_t bit_decl(dow, DOW_COUNT); + int flags; +#define MIN_STAR 0x01 +#define HR_STAR 0x02 +#define DOM_STAR 0x04 +#define DOW_STAR 0x08 +#define WHEN_REBOOT 0x10 +#define DONT_LOG 0x20 +} entry; + + /* the crontab database will be a list of the + * following structure, one element per user + * plus one for the system. + * + * These are the crontabs. + */ +#ifndef WITH_SELINUX +#define security_context_t unsigned +#endif + +typedef struct _user { + struct _user *next, *prev; /* links */ + char *name; + char *tabname; /* /etc/cron.d/ file name or NULL */ + time_t mtime; /* last modtime of crontab */ + entry *crontab; /* this person's crontab */ + security_context_t scontext; /* SELinux security context */ +} user; + +typedef struct _orphan { + struct _orphan *next; /* link */ + char *uname; + char *fname; + char *tabname; +} orphan; + +typedef struct _cron_db { + user *head, *tail; /* links */ + time_t mtime; /* last modtime on spooldir */ +#ifdef WITH_INOTIFY + int ifd; +#endif +} cron_db; + /* in the C tradition, we only create + * variables for the main program, just + * extern them elsewhere. + */ diff --git a/src/user.c b/src/user.c new file mode 100644 index 0000000..fb9d3b2 --- /dev/null +++ b/src/user.c @@ -0,0 +1,135 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* vix 26jan87 [log is in RCS file] + */ + +#include + +static const char *FileName; + +static void +log_error (const char *msg) +{ + log_it ("CRON", getpid (), msg, FileName, 0); +} + +void +free_user (user * u) { + entry *e, *ne; + + free(u->name); + free(u->tabname); + for (e = u->crontab; e != NULL; e = ne) { + ne = e->next; + free_entry(e); + } + free_security_context(&(u->scontext)); + free(u); +} + +user * +load_user (int crontab_fd, struct passwd *pw, const char *uname, + const char *fname, const char *tabname) { + char envstr[MAX_ENVSTR]; + FILE *file; + user *u; + entry *e; + int status, save_errno = errno; + char **envp = NULL, **tenvp; + + if (!(file = fdopen(crontab_fd, "r"))) { + int save_errno = errno; + log_it(uname, getpid (), "FAILED", "fdopen on crontab_fd in load_user", + save_errno); + close(crontab_fd); + return (NULL); + } + + Debug(DPARS, ("load_user()\n")) + /* file is open. build user entry, then read the crontab file. + */ + if ((u = (user *) malloc (sizeof (user))) == NULL) + goto done; + memset(u, 0, sizeof(*u)); + + if (((u->name = strdup(fname)) == NULL) + || ((u->tabname = strdup(tabname)) == NULL)) { + save_errno = errno; + free_user(u); + u = NULL; + goto done; + } + + + /* init environment. this will be copied/augmented for each entry. + */ + if ((envp = env_init()) == NULL) { + save_errno = errno; + free_user(u); + u = NULL; + goto done; + } + + if (get_security_context(pw == NULL ? NULL : uname, + crontab_fd, &u->scontext, tabname) != 0) { + free_user (u); + u = NULL; + goto done; + } + + /* load the crontab + */ + while ((status = load_env (envstr, file)) >= OK) { + switch (status) { + case ERR: + save_errno = errno; + free_user(u); + u = NULL; + goto done; + case FALSE: + FileName = tabname; + e = load_entry(file, log_error, pw, envp); + if (e) { + e->next = u->crontab; + u->crontab = e; + } + break; + case TRUE: + if ((tenvp = env_set (envp, envstr)) == NULL) { + save_errno = errno; + free_user(u); + u = NULL; + goto done; + } + envp = tenvp; + break; + } + } + +done: + if (envp) + env_free(envp); + fclose(file); + Debug(DPARS, ("...load_user() done\n")) + errno = save_errno; + return (u); +}