commit 886d9ad08ece61022493f976c10583378a3819e1 Author: Mario Fetka Date: Mon May 8 15:30:03 2017 +0200 Imported Upstream version 1.4.8 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); +}