commit 7a09ba2cf7ce0c9770c595c5a290313b53bba26e Author: Nicholas J. Kain Date: Fri Nov 12 04:02:18 2010 -0500 Initial commit. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..f4eecfb --- /dev/null +++ b/ChangeLog @@ -0,0 +1,64 @@ +20040906 + accept_conns() trivial correctness fix: EINTR return from accept should + force continue rather than break in handling loop. + + fail_on_fdne() is now file_exists() with a slightly different API. + +20040818 + Use inet_aton rather than custom function; daemon.[ch] removed. + +20040817 + ifchd no longer logs a "FATAL - select returned an error!" when it + is -KILL'ed. + +20040626 + pending_exit is now static volatile sig_atomic_t rather than + static volatile int for pedantic correctness issues. + +20040614 + Added --interface (-i) option that allows one to restrict the + interfaces that ifchd clients are allowed to modify. I + reccomend that this flag be used to further mitigate the possible + effects of a compromised client. By default, all interfaces + may be modified by clients. + Minor cleanups. + Clear corresponding namesvrs and domains on socket disconnection. + +20040613 + Factor out Linux-specific code into linux.c. + Nameservers and search domains now tracked per-connection to + prevent races where a client could force a writeout of data + provided by another client. + Interface name now cleared on connection close. + Make all headers idempotent. + Minor improvements to error messages. + +20040612 + Removed iffd[] array; this change makes the state machine action + functions (perform_*) depend on less external state. + + HOSTNAME command now supported; it is not enabled by default. + If you wish for remote daemons to be able to change the hostname + of the local machine, use the --hostname (-o) option. + +20040610 + Added MTU support. + +20040609 + Robustify so that suicide() isn't called at every possible failure. + Harmless failures will now simply print an error to the log. Risky + or severe errors still suicide(). + +20040608 + + Added support for resolv.conf, namely "nameserver" and "search" entries. + Make strlist more robust and paranoid. + +20040607 + + Improved accept() code to be much more robust; it can now properly error + recover. I hope this will fix the elusive random-exit problem that + plagues machines running the 2.6 kernel. + + Refactored dispatch_work() to be cleaner. + diff --git a/DESIGN b/DESIGN new file mode 100644 index 0000000..d7f6b7e --- /dev/null +++ b/DESIGN @@ -0,0 +1,104 @@ +Goals: + +1. Security + + a. Divide into seperate processes that each have the minimal + system access necessary to complete their task. + + b. Use a well defined IPC mechanism to facilitate cooperation + between processes. In this case, UNIX domain sockets are + used, since they allow for UNIX DAC (on Linux, at least). + + c. Write each program to be secure; don't rely on the + privilege seperations for security. + + d. Simple error handling is favored rather than complex error + handling that may possibly be caused to "recover" in an + exploitable way. + + e. Don't make stupid assumptions. Implement only the minimal + functionality necessary to perform a task. Expect brain + damaged or malicious inputs. + + f. Run inside a chroot, with minimal privileges via + capabilities or MAC. + +2. Reliability + + a. Don't try to handle severe errors. + + b. Log errors if program state is still sane. + + c. Recover from predictable problems if necessary. Make sure + that recovery behavior is well understood and defined. + + d. Complicated or unsafe recoveries should not be performed; + instead the program should promptly exit. Dead programs + don't cause exploits. + +5. Portability + + a. Portability is good, but portability may not be as wide as + a less secure program. Capabilities or MAC are not well + standardized, but remain necessary features. + + b. Aside from the previous caveat, try to be as portable as + possible. At the very least, the dhcp client daemon + should be easily portable (only broadcast and perhaps RAW + packets are necessary). + +98. Speed + + a. If we aren't required to sacrifice anything more + important, it's always good to be fast. + +99. Size + + a. If we aren't required to sacrifice anything more + important, it's always good to be frugal. + + +Specifics: + +Implementation language will be C. C is universally available, very +predictable, and well accepted. dhcp clients don't have to deal with +complicated data structures or extensively with strings, so C's +largest weaknesses should not be highly apparent. On the other hand, +dhcp clients must extensively deal with the OS in a low-level fashion. +C's strongest points will therefore be used. + +More practically, a program written in a compiled language other than +C will be poorly accepted by the average user. Since widespread use +is an eventual goal of ndhc, this point is very eloquent. + +For now, I'm taking advantage of the existing udhcpc. With minor +modifications, it will serve quite well as the dhcp client daemon. It +is then only necessary for me to write a client request translator +and a interface change daemon. The result should be a highly modular +solution. + +I'm developing on a Linux-based platform. ndhc will first be designed +to work properly on a Linux-based system. I will then make certain +that ndhc works well on a RSBAC-enabled Linux system. Afterwards, I +_may_ spend some effort porting ndhc to FreeBSD (but don't count on it). I +have no personal interest in other platforms. + +Layout: + + +ndhc daemon (root -> chroot -> drop all !(CAP_NET_BROADCAST|CAP_NET_RAW) + -> nopriv) + +* handles dhcp protocol issues +* keeps track of leases +* talks to ndhif to perform tasks that require + higher privileges than CAP_NET_BROADCAST or CAP_NET_RAW + +ifchd daemon (root -> openfd -> chroot -> drop all !CAP_NET_ADMIN -> nopriv) + +* listens for interface change requests via UNIX domain socket +* restricts valid IP ranges that will be accepted +* performs interface changes +* keeps rw fds for system files (such as /etc/resolv.conf) that must + be modified outside the chroot + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a43ea21 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, 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 + + Appendix: 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., 675 Mass Ave, Cambridge, MA 02139, 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/Makefile b/Makefile new file mode 100644 index 0000000..354eb23 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +CC = gcc -Wall -Wpointer-arith -Wstrict-prototypes +AR = ar +objects = log.o nstrl.o chroot.o pidfile.o signals.o strlist.o linux.o ifchd.o + +ifchd : $(objects) + $(CC) -lcap -o ifchd $(objects) + +ifchd.o : log.h nstrl.h chroot.h pidfile.h signals.h strlist.h linux.h + $(CC) $(CFLAGS) $(archflags) $(LDFLAGS) -c -o $@ ifchd.c + +linux.o: log.h strlist.h +chroot.o: log.h +pidfile.o: log.h +signals.o: log.h +strlist.o: +nstrl.o: +log.o : + +install: ifchd + -install -s -m 755 ifchd /usr/sbin/ifchd +tags: + -ctags -f tags *.[ch] + -cscope -b +clean: + -rm -f *.o ifchd +distclean: + -rm -f *.o ifchd tags cscope.out + diff --git a/README.ifchd b/README.ifchd new file mode 100644 index 0000000..d2c7175 --- /dev/null +++ b/README.ifchd @@ -0,0 +1,191 @@ +ifchd, copyright (c) 2004 Nicholas Kain. Licensed under GNU GPL. + +Requirements: + +Linux kernel (tested: 2.4, 2.6) + * libcap is required (available via ftp.kernel.org) + +C99-compliant C compiler (for C99 struct subobject init) + * any modern GCC should be sufficient + +Tested with glibc 2.2.x and 2.3.x. dietlibc is not compatible. I have not yet +tested uclibc. + +I may bother to port to other operating systems, but don't count on it. Other +OSes lack the functionality of a [RSBAC|SELinux]+PaX enabled kernel, so I find +them to be less useful for a highly secured system. + +INTRODUCTION +------------ + +ndhc consists of a set of daemons that cooperate in order to provide +privilege-seperated dhcp client services. Each daemon runs with the minimal +necessary privileges in order to perform its task. Currently, ndhc consists of +two daemons: the eponymous ndhc and ifchd. + +ndhc communicates with dhcp servers and handles the vagaries of the dhcp +client protocol. It runs as a non-root user inside a chroot. ndhc retains +only the minimum necessary set of privileges required to perform its duties. +These powers include the ability to bind to a low port, the ability to open a +raw socket, and the ability to communicate on broadcast channels. ndhc holds +no other powers and is restricted to a chroot that contains nothing more than a +domain socket filesystem object and (at least on Linux) a urandom device node. + +ifchd handles interface change requests. It listens on a UNIX domain socket +for such requests, and denies any client that does not match an authorized gid, +uid, or pid. ifchd runs as a non-root user inside a chroot, and retains only +the power to configure network interfaces. ifchd is designed so that it has +the ability to service multiple client requests simultaneously; a single ifchd +is sufficient for multiple ndhc clients. Only exotic setups should require +this functionality, but it does exist. + +Note that ndhc does not support the entire DHCP client protocol. Only the +minimum necessary featureset is implemented. This behavior should be familiar +to anyone who has used software that purports to be be secure. + +Many of the features that ndhc depends upon are not entirely standard and vary +between UNIX systems. It is likely that some effort will be required in order +to port ndhc to new systems. The ndhc daemon should be entirely portable aside +from its use of Linux-style POSIX capabilities. + +ifchd is necessarily less portable, since it must use system-specific ioctls in +order to configure network interfaces. Additionally, ifchd uses extensions to +the UNIX domain socket family to limit connections to user defined subsets of +possible uids, gids, and pids. These extensions are present in Linux and BSD, +although both Linux and BSD have different interfaces for the functionality. +Patches that provide support for new systems are welcome. + +USAGE +----- + +1) Compile and install ifchd and ndhc. + a) Build ifchd with "make" + b) Enter ndhc directory and build ndhc with "make" + c) Install the ifchd and ndhc executables in a normal place. I would + suggest /usr/sbin or /usr/local/sbin. + +2) Time to create the jail in which ifchd and ndhc will run. + a) Become root and create new group "ifchd". + + $ su - + # umask 077 + # groupadd ifchd + + b) Create new users "ifchd" and "dhcp". The primary group of these + users should be "ifchd". + + # useradd -d /var/lib/ndhc -g ifchd ifchd + # useradd -d /var/lib/ndhc -g ifchd dhcp + + b) Create the jail directory and set its ownership properly. + + # mkdir /var/lib/ndhc + # chown ifchd.ifchd /var/lib/ndhc + # chmod a+rx /var/lib/ndhc + + c) Create a urandom device for ndhc to use within the jail. + + # cd /var/lib/ndhc + # mkdir dev + # mknod dev/urandom c 1 9 + # chown -R root.root dev + # chmod a+rx dev + # chmod a+r dev/urandom + + d) (optional) If you wish for logging to properly work, you + will need to properly configure your logging daemon so that it + opens a domain socket in the proper location within the jail. + Since this varies per-daemon, I cannot provide a general + configuration. + +3) At this point the jail is usable; ifchd and ndhc are ready to + be used. As an example of a sample configuration, here is my + rc.dhcp: + +--START-- + +#!/bin/sh +case "$1" in + start) + ifchd -i eth0 -p /var/run/ifchd.pid -u ifchd -g ifchd -U dhcp -G ifchd \ + -c /var/lib/ndhc &> /dev/null + ndhc -b -i eth0 -u dhcp -C /var/lib/ndhc &> /dev/null + ;; + stop) + killall ndhc ifchd + ;; +esac + +--END-- + + This script works fine with my personal machines, which are set up + exactly as I have outlined above. If you have not entirely followed my + directions, the script will of course require modifications. + +4o) If you encounter problems, I suggest running both ifchd and ndhc in the + foreground, and perhaps compiling ndhc with extra debugging output + (uncomment DEBUG=1 in the Makefile). + + +BEHAVIOR NOTES +-------------- + +ifchd does not enable updates of the local hostname and resolv.conf by default. +If you wish to enable these functions, use the --resolve (-r) and --hostname +(-o) flags. See ifchd --help. + +ifchd can be set such that it only allows clients to configure particular +network interfaces. The --interface (-i) argument does the trick, and may +be used multiple times to allow multiple interfaces. + +RSBAC NOTES +----------- + +I was personally unable to get ifchd to properly function with RSBAC_NET_DEV +enabled. Browsing the rsbac source, I was unable to figure out what I was +doing incorrectly -- my RC definitions were as far as I could tell, correct. +Therefore, my directions assume that you have disabled RSBAC_NET_DEV in your +kernel configuration. + +The normal usage directions may be followed, but an additional step for rsbac +is necessary. Change to your secoff account and invoke rsbac_fd_menu on the +ifchd and ndhc executables. The AUTH capability for your ifchd and dhcp groups +must be allowed on the corresponding executables, otherwise ifchd and ndhc will +be unable to change to a non-root user and will refuse to run. + + +GRSECURITY NOTES +---------------- + +Make sure that CONFIG_GRKERNSEC_CHROOT_CAPS is disabled. Otherwise, ifchd will +lose its capabilities (in particular, the ability to reconfigure interfaces) +when it chroots. + + +PORTING NOTES +------------- + +Unportable functions are isolated to linux.c. Any attempts to port ifchd to +other platforms should isolate platform-dependent code to similarly named +compilation units (eg: for FreeBSD, freebsd.[ch]). + +There are four major functions that ifchd depends upon that are not generally +portable. First, it uses the SO_PEERCRED flag of getsockopt() to discriminate +authorized connections by uid, gid, and pid. Similar functionality exists in +at least the BSDs; however, it has a different API. Second, ifchd takes +advantage of Linux capabilities so that it does not need full root privileges. +Capabilities are supposedly a POSIX feature, but in practice, they vary greatly +from system to system. Third and fourth, ifchd configures network interfaces +and routes. Interface and route configuration is entirely non-portable, +usually requiring calls to the catch-all ioctl(), and will almost certainly +require platform-dependent code. + +Some standard C libraries include a native implementation of strlcpy() and +strlcat(). Such defines may conflict with my implementations in +nstrl.c/nstrl.h. It is up to the user whether the standard C library +implementations should be used. Note that some machines implement strlcpy() +and strlcat() with nonstandard semantics (notably Solaris). On these systems, +using the system-provided implementations may lead to security problems. Such +problems are the fault of the vendor. If you are unsure whether your system is +correct or not, I suggest using the implementation that I provide. + diff --git a/TODO b/TODO new file mode 100644 index 0000000..b927634 --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ +write documentation... +support BSD + diff --git a/chroot.c b/chroot.c new file mode 100644 index 0000000..33cc630 --- /dev/null +++ b/chroot.c @@ -0,0 +1,42 @@ +/* chroot.c - chroots ncron jobs + (C) 2003 Nicholas J. Kain + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include +#include + +#include "log.h" + +void imprison(char *path) +{ + int ret; + + if (path == NULL) + return; + + ret = chdir(path); + if (ret) { + log_line("Failed to chdir(%s). Not invoking job.", path); + exit(EXIT_FAILURE); + } + + ret = chroot(path); + if (ret) { + log_line("Failed to chroot(%s). Not invoking job.", path); + exit(EXIT_FAILURE); + } +} + diff --git a/chroot.h b/chroot.h new file mode 100644 index 0000000..c5650c1 --- /dev/null +++ b/chroot.h @@ -0,0 +1,21 @@ +/* chroot.h - include file for chroot.c + (C) 2003 Nicholas J. Kain + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef NJK_CHROOT_H_ +#define NJK_CHROOT_H_ 1 +void imprison(char *path); +#endif diff --git a/defines.h b/defines.h new file mode 100644 index 0000000..143651a --- /dev/null +++ b/defines.h @@ -0,0 +1,12 @@ +#define MAX_PATH_LENGTH 1024 +#define PID_FILE_DEFAULT "/var/run/ifchd.pid" +#define IFCHD_VERSION "0.8" + +#define COMM_SOCKET_PATH "ifchange" +#define MAX_BUF 1024 +#define MAXLINE 1024 + +#define SOCK_QUEUE 2 +#define CONN_TIMEOUT 60 +#define LINUX 1 + diff --git a/ifchd.c b/ifchd.c new file mode 100644 index 0000000..87b2f0d --- /dev/null +++ b/ifchd.c @@ -0,0 +1,916 @@ +/* ifchd.c - interface change daemon + * Time-stamp: <2004-06-14 njk> + * + * (C) 2004 Nicholas J. Kain + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define _GNU_SOURCE +#include + +#include "defines.h" +#include "log.h" +#include "chroot.h" +#include "pidfile.h" +#include "signals.h" +#include "strlist.h" +#include "ifproto.h" +#include "nstrl.h" +#include "linux.h" + +enum states { + STATE_NOTHING, + STATE_INTERFACE, + STATE_IP, + STATE_SUBNET, + STATE_TIMEZONE, + STATE_ROUTER, + STATE_TIMESVR, + STATE_DNS, + STATE_LPRSVR, + STATE_HOSTNAME, + STATE_DOMAIN, + STATE_IPTTL, + STATE_MTU, + STATE_BROADCAST, + STATE_NTPSRV, + STATE_WINS +}; + +static volatile sig_atomic_t pending_exit; + +/* Socket fd, current state, and idle time for connections. */ +static int sks[SOCK_QUEUE], state[SOCK_QUEUE], idle_time[SOCK_QUEUE]; + +/* Per-connection buffers. */ +static char ibuf[SOCK_QUEUE][MAX_BUF]; + +/* + * Per-connection pointers into the command lists. Respectively, the + * topmost item on the list, the current item, and the last item on the list. + */ +static strlist_t *head[SOCK_QUEUE], *curl[SOCK_QUEUE], *last[SOCK_QUEUE]; + +int resolv_conf_fd = -1; +/* int ntp_conf_fd = -1; */ + +/* If true, allow HOSTNAME changes from dhcp server. */ +int allow_hostname = 0; + +static uid_t peer_uid; +static gid_t peer_gid; +static pid_t peer_pid; + +/* Lists of nameservers and search domains. Unfortunately they must be + * per-connection, since otherwise seperate clients could race against + * one another to write out unpredictable data. + */ +static strlist_t *namesvrs[SOCK_QUEUE]; +static strlist_t *domains[SOCK_QUEUE]; + +static void sighandler(int sig) { + pending_exit = 1; +} + +static void fix_signals(void) { + disable_signal(SIGPIPE); + disable_signal(SIGUSR1); + disable_signal(SIGUSR2); + disable_signal(SIGTSTP); + disable_signal(SIGTTIN); + disable_signal(SIGCHLD); + disable_signal(SIGHUP); + + hook_signal(SIGINT, sighandler, 0); + hook_signal(SIGTERM, sighandler, 0); +} + +static void suicide(char *errmsg, const char *perrmsg, int status) +{ + if (errmsg) + log_line(errmsg); + if (!gflags_detach && perrmsg) + perror(perrmsg); + exit(status); +} + +static void die_nulstr(strlist_t *p) +{ + if (!p) + suicide("FATAL - NULL passed to die_nulstr\n", NULL, EXIT_FAILURE); + if (!p->str) + suicide("FATAL - NULL string in strlist\n", NULL, EXIT_FAILURE); +} + +/* Writes out each element in a strlist as an argument to a keyword in + * a file. */ +static void write_resolve_list(const char *keyword, strlist_t *list) +{ + char *buf; + strlist_t *p = list; + unsigned int len; + + if (!keyword || resolv_conf_fd == -1) + return; + + while (p) { + buf = p->str; + len = strlen(buf); + if (len) { + write(resolv_conf_fd, keyword, strlen(keyword)); + write(resolv_conf_fd, buf, strlen(buf)); + write(resolv_conf_fd, "\n", 1); + } + p = p->next; + } +} + +/* Writes a new resolv.conf based on the information we have received. */ +static void write_resolve_conf(int idx) +{ + off_t off; + + if (resolv_conf_fd == -1) + return; + if (lseek(resolv_conf_fd, 0, SEEK_SET) == -1) + return; + + write_resolve_list("nameserver ", namesvrs[idx]); + write_resolve_list("search ", domains[idx]); + off = lseek(resolv_conf_fd, 0, SEEK_CUR); + if (off == -1) { + log_line("write_resolve_conf: lseek returned error: %s\n", + strerror(errno)); + return; + } + ftruncate(resolv_conf_fd, off); + fsync(resolv_conf_fd); +} + +/* Decomposes a ' '-delimited flat character array onto a strlist, then + * calls the given function to perform work on the generated strlist. */ +static void parse_list(int idx, char *str, strlist_t **toplist, + void (*fn)(int)) +{ + char *p, n[256]; + unsigned int i; + strlist_t *newn = 0; + + if (!str || !toplist || !fn) + return; + p = str; + + while (p != '\0') { + memset(n, '\0', sizeof n); + for (i = 0; i < sizeof n - 1 && *p != '\0' && *p != ' '; ++p, ++i) + n[i] = *p; + if (*p == ' ') + ++p; + add_to_strlist(n, &newn); + } + + if (newn) { + free_strlist(*toplist); + *toplist = newn; + } else + return; + + (*fn)(idx); +} + +/* XXX: addme */ +static void perform_timezone(int idx, char *str) +{} + +/* Does anyone use this command? */ +static void perform_timesvr(int idx, char *str) +{} + +/* Add a dns server to the /etc/resolv.conf -- we already have a fd. */ +static void perform_dns(int idx, char *str) +{ + if (!str || resolv_conf_fd == -1) + return; + parse_list(idx, str, &(namesvrs[idx]), &write_resolve_conf); +} + +/* Updates for print daemons are too non-standard to be useful. */ +static void perform_lprsvr(int idx, char *str) +{} + +/* Sets machine hostname. */ +static void perform_hostname(int idx, char *str) +{ + if (!allow_hostname || !str) + return; + if (sethostname(str, strlen(str) + 1) == -1) + log_line("sethostname returned %s\n", strerror(errno)); +} + +/* update "search" in /etc/resolv.conf */ +static void perform_domain(int idx, char *str) +{ + if (!str || resolv_conf_fd == -1) + return; + parse_list(idx, str, &(domains[idx]), &write_resolve_conf); +} + +/* I don't think this can be done without a netfilter extension + * that isn't in the mainline kernels. */ +static void perform_ipttl(int idx, char *str) +{} + +/* XXX: addme */ +static void perform_ntpsrv(int idx, char *str) +{} + +/* Maybe Samba cares about this feature? I don't know. */ +static void perform_wins(int idx, char *str) +{} + +/* Wipes all state associated with a given connection. */ +static void new_sk(int idx, int val) +{ + sks[idx] = val; + memset(ibuf[idx], '\0', sizeof(ibuf[idx])); + free_strlist(head[idx]); + free_strlist(namesvrs[idx]); + free_strlist(domains[idx]); + head[idx] = NULL; + curl[idx] = NULL; + last[idx] = NULL; + namesvrs[idx] = NULL; + domains[idx] = NULL; + idle_time[idx] = time(NULL); + state[idx] = STATE_NOTHING; + clear_if_data(idx); +} + +/* Conditionally accepts a new connection and initializes data structures. */ +static void add_sk(int sk) +{ + int i; + + if (authorized_peer(sk, peer_pid, peer_uid, peer_gid)) { + for (i=0; i CONN_TIMEOUT) { + close(sks[i]); + new_sk(i, -1); + } + } +} + +/* Decomposes a ':'-delimited flat character array onto a strlist. */ +static int stream_onto_list(int i) +{ + int e, s; + + for (e = 0, s = 0; ibuf[i][e] != '\0'; e++) { + if (ibuf[i][e] == ':') { + /* Zero-length command: skip. */ + if (s == e) { + s = e + 1; + continue; + } + curl[i] = malloc(sizeof(strlist_t)); + + if (curl[i] == NULL) + suicide("FATAL - malloc failed\n", "malloc", + EXIT_FAILURE); + + if (head[i] == NULL) { + head[i] = curl[i]; + last[i] = NULL; + } + + curl[i]->next = NULL; + if (last[i] != NULL) + last[i]->next = curl[i]; + + curl[i]->str = malloc(e - s + 1); + + if (curl[i]->str == NULL) + suicide("FATAL - malloc failed\n", "malloc", + EXIT_FAILURE); + + strlcpy(curl[i]->str, ibuf[i] + s, e - s); + last[i] = curl[i]; + s = e + 1; + } + } + return s; +} + +/* State machine that runs over the command and argument list, + * executing commands. */ +static void execute_list(int i) +{ + char *p; + + for (;;) { + if (!curl[i]) + break; + die_nulstr(curl[i]); + + p = curl[i]->str; + + switch (state[i]) { + case STATE_NOTHING: + if (strncmp(p, CMD_INTERFACE, sizeof(CMD_INTERFACE)) == 0) + state[i] = STATE_INTERFACE; + if (strncmp(p, CMD_IP, sizeof(CMD_IP)) == 0) + state[i] = STATE_IP; + if (strncmp(p, CMD_SUBNET, sizeof(CMD_SUBNET)) == 0) + state[i] = STATE_SUBNET; + if (strncmp(p, CMD_TIMEZONE, sizeof(CMD_TIMEZONE)) == 0) + state[i] = STATE_TIMEZONE; + if (strncmp(p, CMD_ROUTER, sizeof(CMD_ROUTER)) == 0) + state[i] = STATE_ROUTER; + if (strncmp(p, CMD_TIMESVR, sizeof(CMD_TIMESVR)) == 0) + state[i] = STATE_TIMESVR; + if (strncmp(p, CMD_DNS, sizeof(CMD_DNS)) == 0) + state[i] = STATE_DNS; + if (strncmp(p, CMD_LPRSVR, sizeof(CMD_LPRSVR)) == 0) + state[i] = STATE_LPRSVR; + if (strncmp(p, CMD_HOSTNAME, sizeof(CMD_HOSTNAME)) == 0) + state[i] = STATE_HOSTNAME; + if (strncmp(p, CMD_DOMAIN, sizeof(CMD_DOMAIN)) == 0) + state[i] = STATE_DOMAIN; + if (strncmp(p, CMD_IPTTL, sizeof(CMD_IPTTL)) == 0) + state[i] = STATE_IPTTL; + if (strncmp(p, CMD_MTU, sizeof(CMD_MTU)) == 0) + state[i] = STATE_MTU; + if (strncmp(p, CMD_BROADCAST, sizeof(CMD_BROADCAST)) == 0) + state[i] = STATE_BROADCAST; + if (strncmp(p, CMD_NTPSRV, sizeof(CMD_NTPSRV)) == 0) + state[i] = STATE_NTPSRV; + if (strncmp(p, CMD_WINS, sizeof(CMD_WINS)) == 0) + state[i] = STATE_WINS; + free_stritem(&(curl[i])); + break; + + case STATE_INTERFACE: + perform_interface(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_IP: + perform_ip(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_SUBNET: + perform_subnet(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_TIMEZONE: + perform_timezone(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_ROUTER: + perform_router(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_TIMESVR: + perform_timesvr(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_DNS: + perform_dns(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_LPRSVR: + perform_lprsvr(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_HOSTNAME: + perform_hostname(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_DOMAIN: + perform_domain(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_IPTTL: + perform_ipttl(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_MTU: + perform_mtu(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_BROADCAST: + perform_broadcast(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_NTPSRV: + perform_ntpsrv(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + case STATE_WINS: + perform_wins(i, p); + free_stritem(&(curl[i])); + state[i] = STATE_NOTHING; + break; + + default: + log_line("warning: invalid state in dispatch_work\n"); + break; + } + } + head[i] = curl[i]; +} + +/* Opens a non-blocking listening socket with the appropriate properties. */ +static int get_listen(void) +{ + int lsock, ret; + struct sockaddr_un lsock_addr = + { + .sun_family = AF_UNIX, + .sun_path = COMM_SOCKET_PATH + }; + + lsock = socket(PF_UNIX, SOCK_STREAM, 0); + if (lsock == -1) + suicide("FATAL - failed to create socket\n", + "dispatch_work - socket", EXIT_FAILURE); + + fcntl(lsock, F_SETFL, O_NONBLOCK); + + (void) unlink(COMM_SOCKET_PATH); + ret = bind(lsock, (struct sockaddr *) &lsock_addr, sizeof(lsock_addr)); + if (ret) + suicide("FATAL - failed to bind socket\n", + "dispatch_work - bind", EXIT_FAILURE); + ret = chmod(COMM_SOCKET_PATH, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + if (ret) + suicide("FATAL - failed to chmod socket\n", + "dispatch_work - chmod", EXIT_FAILURE); + ret = listen(lsock, SOCK_QUEUE); + if (ret) + suicide("FATAL - failed to listen on socket\n", + "dispatch_work - listen", EXIT_FAILURE); + + return lsock; +} + +/* Abstracts away the details of accept()ing a socket connection. */ +static void accept_conns(int *lsock) +{ + int ret; + struct sockaddr_un sock_addr; + socklen_t sock_len = sizeof(sock_addr); + + for(;;) + { + ret = accept(*lsock, (struct sockaddr *) &sock_addr, &sock_len); + if (ret != -1) { + add_sk(ret); + return; + } + switch (errno) { + case EAGAIN: +#ifdef LINUX + case ENETDOWN: + case EPROTO: + case ENOPROTOOPT: + case EHOSTDOWN: + case ENONET: + case EHOSTUNREACH: + case EOPNOTSUPP: + case ENETUNREACH: +#endif + return; + + case EINTR: + continue; + + case EBADF: + case ENOTSOCK: + case EINVAL: + log_line("warning: accept returned %s!\n", strerror(errno)); + close(*lsock); + *lsock = get_listen(); + return; + + case ECONNABORTED: + case EMFILE: + case ENFILE: + log_line("warning: accept returned %s!\n", strerror(errno)); + return; + + default: + log_line("warning: accept returned a mysterious error: %s\n", + strerror(errno)); + return; + } + } +} + +/* Core function that handles connections, gathers input, and calls + * the state machine to do actual work. */ +static void dispatch_work(void) +{ + int lsock, ret, highfd, i, index; + fd_set rfds; + char buf[MAX_BUF]; + + /* Initialize all structures to blank state. */ + for (i=0; i highfd) + highfd = sks[i]; + } + } + if (lsock > highfd) + highfd = lsock; + + ret = select(highfd + 1, &rfds, NULL, NULL, NULL); + switch (ret) { + case 0: + close_idle_sk(); + break; + case -1: + if (pending_exit == 1) + return; + suicide("FATAL - select returned an error!\n", + "dispatch_work - select", EXIT_FAILURE); + break; + } + + if (pending_exit == 1) + return; + + /* handle pending connections */ + if (FD_ISSET(lsock, &rfds)) + accept_conns(&lsock); + + /* Read in and process data on waiting connections */ + for (i=0; i MAX_BUF - 2) { + close(sks[i]); + new_sk(i, -1); + continue; + } + + /* Append new stream input avoiding overflow. */ + strlcpy(ibuf[i] + index, buf, sizeof(ibuf[i]) - index - 1); + + /* Decompose ibuf contents onto strlist. */ + index = stream_onto_list(i); + + /* Remove everything that we've parsed into the list. */ + strlcpy(buf, ibuf[i] + index, sizeof(buf)); + strlcpy(ibuf[i], buf, sizeof(ibuf[i])); + + /* Now we have a strlist of commands and arguments. + * Decompose and execute it. */ + if (!head[i]) + continue; + curl[i] = head[i]; + execute_list(i); + } + close_idle_sk(); + } +} + +int main(int argc, char** argv) { + int c, t, uid = 0, gid = 0; + char pidfile[MAX_PATH_LENGTH] = PID_FILE_DEFAULT; + char chrootd[MAX_PATH_LENGTH] = ""; + char resolv_conf_d[MAX_PATH_LENGTH] = ""; + char *p; + struct passwd *pws; + struct group *grp; + + while (1) { + int option_index = 0; + static struct option long_options[] = { + {"detach", 0, 0, 'd'}, + {"nodetach", 0, 0, 'n'}, + {"pidfile", 1, 0, 'p'}, + {"quiet", 0, 0, 'q'}, + {"chroot", 1, 0, 'c'}, + {"resolve", 1, 0, 'r'}, + {"hostname", 0, 0, 'o'}, + {"user", 1, 0, 'u'}, + {"group", 1, 0, 'g'}, + {"cuser", 1, 0, 'U'}, + {"cgroup", 1, 0, 'G'}, + {"cpid", 1, 0, 'P'}, + {"interface", 1, 0, 'i'}, + {"help", 0, 0, 'h'}, + {"version", 0, 0, 'v'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "dnp:qc:r:ou:g:U:G:P:i:hv", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + + case 'h': + printf( +"ifchd %s, if change daemon. Licensed under GNU GPL.\n", IFCHD_VERSION); + printf( +"Copyright (C) 2004 Nicholas J. Kain\n" +"Usage: ifchd [OPTIONS]\n" +" -d, --detach detach from TTY and daemonize\n" +" -n, --nodetach stay attached to TTY\n" +" -q, --quiet don't print to std(out|err) or log\n" +" -c, --chroot path where ifchd should chroot\n" +" -r, --resolve path to resolv.conf or equiv\n" +" -o, --hostname allow dhcp to set machine hostname\n" +" -p, --pidfile pidfile path\n"); + printf( +" -u, --user user name that ifchd should run as\n" +" -g, --group group name that ifchd should run as\n" +" -U, --cuser user name of clients\n" +" -G, --cgroup group name of clients\n" +" -P, --cpid process id of client\n" +" -i, --interface ifchd clients may modify this interface\n" +" -h, --help print this help and exit\n" +" -v, --version print version information and exit\n"); + exit(EXIT_FAILURE); + break; + + case 'v': + printf( +"ifchd %s, if change daemon. Licensed under GNU GPL.\n", IFCHD_VERSION); + printf( +"Copyright (C) 2004 Nicholas J. Kain\n" +"This is free software; see the source for copying conditions. There is NO\n" +"WARRANTY; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"); + exit(EXIT_FAILURE); + break; + + case 'd': + gflags_detach = 1; + break; + + case 'n': + gflags_detach = 0; + break; + + case 'q': + gflags_quiet = 1; + break; + + case 'c': + strlcpy(chrootd, optarg, MAX_PATH_LENGTH); + break; + + case 'p': + strlcpy(pidfile, optarg, MAX_PATH_LENGTH); + break; + + case 'r': + strlcpy(resolv_conf_d, optarg, MAX_PATH_LENGTH); + break; + + case 'o': + allow_hostname = 1; + break; + + case 'u': + t = (unsigned int) strtol(optarg, &p, 10); + if (*p != '\0') { + pws = getpwnam(optarg); + if (pws) { + uid = (int)pws->pw_uid; + if (!gid) + gid = (int)pws->pw_gid; + } else suicide("FATAL - Invalid uid specified.\n", NULL, + EXIT_FAILURE); + } else + uid = t; + break; + + case 'g': + t = (unsigned int) strtol(optarg, &p, 10); + if (*p != '\0') { + grp = getgrnam(optarg); + if (grp) { + gid = (int)grp->gr_gid; + } else suicide("FATAL - Invalid gid specified.\n", NULL, + EXIT_FAILURE); + } else + gid = t; + break; + + case 'U': + t = (unsigned int) strtol(optarg, &p, 10); + if (*p != '\0') { + pws = getpwnam(optarg); + if (pws) { + peer_uid = (int)pws->pw_uid; + if (!peer_gid) + peer_gid = (int)pws->pw_gid; + } else suicide("FATAL - Invalid uid specified.\n", NULL, + EXIT_FAILURE); + } else + peer_uid = t; + break; + + case 'G': + t = (unsigned int) strtol(optarg, &p, 10); + if (*p != '\0') { + grp = getgrnam(optarg); + if (grp) { + peer_gid = (int)grp->gr_gid; + } else suicide("FATAL - Invalid gid specified.\n", NULL, + EXIT_FAILURE); + } else + peer_gid = t; + break; + + case 'P': + t = (unsigned int) strtol(optarg, &p, 10); + if (*p == '\0') + peer_pid = t; + break; + + case 'i': + add_permitted_if(optarg); + break; + } + } + + if (getuid()) + suicide("FATAL - I need root for CAP_NET_ADMIN and chroot!\n", + NULL, EXIT_FAILURE); + + if (gflags_detach) + if (daemon(0,0)) { + log_line("FATAL - detaching fork failed\n"); + exit(EXIT_FAILURE); + } + + if (!file_exists(pidfile, "w")) { + log_line("FATAL - cannot open pidfile for write!"); + exit(EXIT_FAILURE); + } + write_pid(pidfile); + + umask(077); + fix_signals(); + + /* If we are requested to update resolv.conf, preopen the fd before + * we drop root privileges, making sure that if we create + * resolv.conf, it will be world-readable. + */ + if (strncmp(resolv_conf_d, "", MAX_PATH_LENGTH)) { + umask(022); + resolv_conf_fd = open(resolv_conf_d, O_RDWR | O_CREAT); + umask(077); + if (resolv_conf_fd == -1) { + suicide("FATAL - unable to open resolv.conf\n", + "main - open", EXIT_FAILURE); + } + } + + if (!strncmp(chrootd, "", MAX_PATH_LENGTH)) + suicide("FATAL - No chroot path specified. Refusing to run.\n", + NULL, EXIT_FAILURE); + + /* Note that failure cases are handled by called fns. */ + imprison(chrootd); + drop_root(uid, gid, "cap_net_admin=ep"); + + /* Cover our tracks... */ + memset(chrootd, '\0', sizeof(chrootd)); + memset(resolv_conf_d, '\0', sizeof(resolv_conf_d)); + memset(pidfile, '\0', sizeof(pidfile)); + + dispatch_work(); + + /* Explicitly freed so memory debugger output has less static. */ + for (c=0; c + * + * (C) 2004 Nicholas J. Kain + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#define __USE_GNU 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "defines.h" +#include "log.h" +#include "strlist.h" +#include "ifproto.h" +#include "nstrl.h" + +/* Symbolic name of the interface associated with a connection. */ +static char ifnam[SOCK_QUEUE][IFNAMSIZ]; +static strlist_t *okif; + +/* Clear a specified ifnam structure. */ +void clear_if_data(int idx) +{ + memset(ifnam[idx], '\0', IFNAMSIZ); +} + +/* Clear all ifnam structures. */ +void initialize_if_data(void) +{ + int i; + for (i = 0; i < SOCK_QUEUE; i++) { + clear_if_data(i); + } +} + +/* Adds to the list of interface names ifchd clients are allowed to change. */ +void add_permitted_if(char *s) +{ + if (!s) + return; + add_to_strlist(s, &okif); +} + +/* Checks if changes are permitted to a given interface. 1 == allowed */ +static int is_permitted(char *name) +{ + strlist_t *p; + + /* If empty, permit all. */ + if (!okif) + return 1; + + if (!name || strlen(name) == 0) + return 0; + p = okif; + while (p) { + if (strcmp(name, p->str) == 0) + return 1; + p = p->next; + } + log_line("attempt to modify interface %s denied\n", name); + return 0; +} + +/* Verify that peer is authorized to connect (return 1 on success). */ +int authorized_peer(int sk, pid_t pid, uid_t uid, gid_t gid) +{ + int ret = 0; + unsigned int cl; + struct ucred cr; + + /* No credentials to verify. */ + if ( !(pid || uid || gid) ) + return 1; + + /* Verify that peer has authorized uid/gid/pid. */ + cl = sizeof(struct ucred); + if (getsockopt(sk, SOL_SOCKET, SO_PEERCRED, &cr, &cl) != -1) { + if ((pid == 0 || cr.pid == pid) || + (uid == 0 || cr.uid == uid) || + (gid == 0 || cr.gid == gid)) + ret = 1; + } else + log_line("getsockopt returned an error: %s\n", strerror(errno)); + return ret; +} + +void perform_interface(int idx, char *str) +{ + if (!str) + return; + + /* Update interface name. */ + memset(ifnam[idx], '\0', IFNAMSIZ); + strlcpy(ifnam[idx], str, IFNAMSIZ); +} + +static int set_if_flag(int idx, short flag) +{ + int fd, ret = -1; + struct ifreq ifrt; + + if (!is_permitted(ifnam[idx])) + goto out0; + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + log_line("%s: (set_if_flag) failed to open interface socket: %s\n", + ifnam[idx], strerror(errno)); + goto out0; + } + + strlcpy(ifrt.ifr_name, ifnam[idx], IFNAMSIZ); + if (ioctl(fd, SIOCGIFFLAGS, &ifrt) < 0) { + log_line("%s: unknown interface: %s\n", ifnam[idx], strerror(errno)); + goto out1; + } + strlcpy(ifrt.ifr_name, ifnam[idx], IFNAMSIZ); + ifrt.ifr_flags |= flag; + if (ioctl(fd, SIOCSIFFLAGS, &ifrt) < 0) { + log_line("%s: failed to set interface flags: %s\n", + ifnam[idx], strerror(errno)); + goto out1; + } + ret = 0; + +out1: + close(fd); +out0: + return ret; +} + +/* Sets IP address on an interface and brings it up. */ +void perform_ip(int idx, char *str) +{ + int fd; + struct in_addr ipaddr; + struct ifreq ifrt; + struct sockaddr_in sin; + + if (!str) + return; + if (!is_permitted(ifnam[idx])) + return; + if (!inet_aton(str, &ipaddr)) + return; + if (set_if_flag(idx, (IFF_UP | IFF_RUNNING))) + return; + + strlcpy(ifrt.ifr_name, ifnam[idx], IFNAMSIZ); + memset(&sin, 0, sizeof(struct sockaddr)); + sin.sin_family = AF_INET; + sin.sin_addr = ipaddr; + memcpy(&ifrt.ifr_addr, &sin, sizeof(struct sockaddr)); + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + log_line("%s: (perform_ip) failed to open interface socket: %s\n", + ifnam[idx], strerror(errno)); + return; + } + if (ioctl(fd, SIOCSIFADDR, &ifrt) < 0) + log_line("%s: failed to configure IP: %s\n", + ifnam[idx], strerror(errno)); + close(fd); +} + +/* Sets the subnet mask on an interface. */ +void perform_subnet(int idx, char *str) +{ + int fd; + struct in_addr subnet; + struct ifreq ifrt; + struct sockaddr_in sin; + + if (!str) + return; + if (!is_permitted(ifnam[idx])) + return; + if (!inet_aton(str, &subnet)) + return; + + strlcpy(ifrt.ifr_name, ifnam[idx], IFNAMSIZ); + memset(&sin, 0, sizeof(struct sockaddr)); + sin.sin_family = AF_INET; + sin.sin_addr = subnet; + memcpy(&ifrt.ifr_addr, &sin, sizeof(struct sockaddr)); + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + log_line("%s: (perform_ip) failed to open interface socket: %s\n", + ifnam[idx], strerror(errno)); + return; + } + if (ioctl(fd, SIOCSIFNETMASK, &ifrt) < 0) { + sin.sin_addr.s_addr = 0xffffffff; + if (ioctl(fd, SIOCSIFNETMASK, &ifrt) < 0) + log_line("%s: failed to configure subnet: %s\n", + ifnam[idx], strerror(errno)); + } + close(fd); +} + +void perform_router(int idx, char *str) +{ + struct rtentry rt; + struct sockaddr_in *dest; + struct sockaddr_in *gateway; + struct sockaddr_in *mask; + struct in_addr router; + int fd; + + if (!str) + return; + if (!is_permitted(ifnam[idx])) + return; + if (!inet_aton(str, &router)) + return; + + memset(&rt, 0, sizeof(struct rtentry)); + dest = (struct sockaddr_in *) &rt.rt_dst; + dest->sin_family = AF_INET; + dest->sin_addr.s_addr = 0x00000000; + gateway = (struct sockaddr_in *) &rt.rt_gateway; + gateway->sin_family = AF_INET; + gateway->sin_addr = router; + mask = (struct sockaddr_in *) &rt.rt_genmask; + mask->sin_family = AF_INET; + mask->sin_addr.s_addr = 0x00000000; + + rt.rt_flags = RTF_UP | RTF_GATEWAY; + if (mask->sin_addr.s_addr == 0xffffffff) rt.rt_flags |= RTF_HOST; + rt.rt_dev = ifnam[idx]; + rt.rt_metric = 1; + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + log_line("%s: (perform_router) failed to open interface socket: %s\n", + ifnam[idx], strerror(errno)); + return; + } + if (ioctl(fd, SIOCADDRT, &rt)) + log_line("%s: failed to set route: %s\n", ifnam[idx], strerror(errno)); + close(fd); +} + +void perform_mtu(int idx, char *str) +{ + int fd; + unsigned int mtu; + struct ifreq ifrt; + + if (!str) + return; + if (!is_permitted(ifnam[idx])) + return; + + mtu = strtol(str, NULL, 10); + ifrt.ifr_mtu = mtu; + strlcpy(ifrt.ifr_name, ifnam[idx], IFNAMSIZ); + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + log_line("%s: (perform_mtu) failed to open interface socket: %s\n", + ifnam[idx], strerror(errno)); + return; + } + if (ioctl(fd, SIOCSIFMTU, &ifrt) < 0) + log_line("%s: failed to set MTU (%d): %s\n", ifnam[idx], mtu, + strerror(errno)); + close(fd); +} + +void perform_broadcast(int idx, char *str) +{ + int fd; + struct in_addr broadcast; + struct ifreq ifrt; + struct sockaddr_in sin; + + if (!str) + return; + if (!is_permitted(ifnam[idx])) + return; + if (!inet_aton(str, &broadcast)) + return; + + strlcpy(ifrt.ifr_name, ifnam[idx], IFNAMSIZ); + memset(&sin, 0, sizeof(struct sockaddr)); + sin.sin_family = AF_INET; + sin.sin_addr = broadcast; + memcpy(&ifrt.ifr_addr, &sin, sizeof(struct sockaddr)); + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + log_line("%s: (perform_broadcast) failed to open interface socket: %s\n", ifnam[idx], strerror(errno)); + return; + } + if (ioctl(fd, SIOCSIFBRDADDR, &ifrt) < 0) + log_line("%s: failed to set broadcast: %s\n", + ifnam[idx], strerror(errno)); + close(fd); +} + +static void set_cap(uid_t uid, gid_t gid, char *captxt) +{ + cap_t caps; + + if (!captxt) { + log_line("FATAL - set_cap: captxt == NULL\n"); + exit(EXIT_FAILURE); + } + + if (prctl(PR_SET_KEEPCAPS, 1)) { + log_line("FATAL - set_cap: prctl() failed\n"); + exit(EXIT_FAILURE); + } + + if (setgroups(0, NULL) == -1) { + log_line("FATAL - set_cap: setgroups() failed\n"); + exit(EXIT_FAILURE); + } + + if (setegid(gid) == -1 || seteuid(uid) == -1) { + log_line("FATAL - set_cap: seteuid() failed\n"); + exit(EXIT_FAILURE); + } + + caps = cap_from_text(captxt); + if (!caps) { + log_line("FATAL - set_cap: cap_from_text() failed\n"); + exit(EXIT_FAILURE); + } + + if (cap_set_proc(caps) == -1) { + log_line("FATAL - set_cap: cap_set_proc() failed\n"); + exit(EXIT_FAILURE); + } + + cap_free(caps); +} + +void drop_root(uid_t uid, gid_t gid, char *captxt) +{ + if (!captxt) { + log_line("FATAL - drop_root: captxt == NULL\n"); + exit(EXIT_FAILURE); + } + + if (uid == 0 || gid == 0) { + log_line("FATAL - drop_root: attempt to drop root to root?\n"); + exit(EXIT_FAILURE); + } + + set_cap(uid, gid, captxt); + + if (setregid(gid, gid) == -1 || setreuid(uid, uid) == -1) { + log_line("FATAL - drop_root: failed to drop root!\n"); + exit(EXIT_FAILURE); + } +} + diff --git a/linux.h b/linux.h new file mode 100644 index 0000000..0f3e217 --- /dev/null +++ b/linux.h @@ -0,0 +1,36 @@ +/* linux.h - ifchd Linux-specific functions include + * Time-stamp: <2004-06-13 njk> + * + * (C) 2004 Nicholas J. Kain + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef NJK_IFCHD_LINUX_H_ +#define NJK_IFCHD_LINUX_H_ 1 +void clear_if_data(int idx); +void initialize_if_data(void); +void add_permitted_if(char *s); +int authorized_peer(int sk, pid_t pid, uid_t uid, gid_t gid); +void perform_interface(int idx, char *str); +void perform_ip(int idx, char *str); +void perform_subnet(int idx, char *str); +void perform_router(int idx, char *str); +void perform_mtu(int idx, char *str); +void perform_broadcast(int idx, char *str); +void drop_root(uid_t uid, gid_t gid, char *captxt); +#endif + diff --git a/log.c b/log.c new file mode 100644 index 0000000..8688124 --- /dev/null +++ b/log.c @@ -0,0 +1,46 @@ +/* log.c - simple logging support for ncron + (C) 2003 Nicholas J. Kain + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include +#include +#include +#include +#include "defines.h" + +/* global logging flags */ +int gflags_quiet = 0; +int gflags_detach = 1; + +void log_line(char *format, ...) { + va_list argp; + + if (format == NULL || gflags_quiet) + return; + + if (gflags_detach) { + openlog("ifchd", LOG_PID, LOG_DAEMON); + va_start(argp, format); + vsyslog(LOG_ERR | LOG_DAEMON, format, argp); + va_end(argp); + closelog(); + } else { + va_start(argp, format); + vfprintf(stderr, format, argp); + va_end(argp); + } + closelog(); +} diff --git a/log.h b/log.h new file mode 100644 index 0000000..1620f0d --- /dev/null +++ b/log.h @@ -0,0 +1,26 @@ +/* log.h - simple logging support for ncron + (C) 2003 Nicholas J. Kain + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef NJK_LOG_H_ +#define NJK_LOG_H_ 1 + +extern int gflags_quiet; +extern int gflags_detach; + +void log_line(char* format, ...); +#endif + diff --git a/ndhc/AUTHORS.udhcp b/ndhc/AUTHORS.udhcp new file mode 100644 index 0000000..89a6de4 --- /dev/null +++ b/ndhc/AUTHORS.udhcp @@ -0,0 +1,13 @@ +udhcp server/client package +----------------------- + +Russ Dill +Matthew Ramsay +Chris Trew + +Other Credits: +-------------- +Moreton Bay (http://www.moretonbay.com/) +Lineo (http://opensource.lineo.com) + + diff --git a/ndhc/COPYING b/ndhc/COPYING new file mode 100644 index 0000000..a43ea21 --- /dev/null +++ b/ndhc/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, 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 + + Appendix: 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., 675 Mass Ave, Cambridge, MA 02139, 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/ndhc/MODIFICATIONS b/ndhc/MODIFICATIONS new file mode 100644 index 0000000..90b2d54 --- /dev/null +++ b/ndhc/MODIFICATIONS @@ -0,0 +1,43 @@ +All modifications are to udhcp 0.9.8 +All bugs should be blamed on Nicholas Kain, not the udhcp folks! + +Great thanks go to the authors of udhcp (primarily Russ Dill), from which +ndhc is adapted. + +CHANGES +------- + +C99 compiler required + +removed dhcp server support (use mainline udhcp) +removed sample scripts +added chroot support +added capability support +added setuid/setgid support +run_script now takes int argument (from enum) +script.c heavily modified to call ifchd +stripped command line option for custom script +bounds check all string functions +removed pidfile support: backgrounding model + chroot preclude it +manpage updated +removed busybox compat wrapper +convert all strncpy -> strlcpy +u_int*_t -> uint*_t (C99) +add_simple_option no longer does unspeakable things to the C language +character signedness issues fixed +casting bug [dhcpc.c:549 sizeof(signal) should be sizeof sig] + - won't bite except on an architecture where sizeof fnptr != sizeof int +LOG and DEBUG are abolished - functions are used instead, see log.c/log.h +C99 structure initializers used exclusively (C99) +Linux kernel headers no longer used under any circumstances. + - this is likely to break prehistoric systems running /dev/urandom + +in order to seed /dev/urandom with some data (mac address) unique to your +system. If reading /dev/urandom fails, ndhc will fall back to its old +behavior of seeding with time(0). + + +signals accepted by ndhc +------------------------- + +ndhc also responds to SIGUSR1 and SIGUSR2. SIGUSR1 will force a renew state, +and SIGUSR2 will force a release of the current lease, and cause ndhc to +go into an inactive state (until it is killed, or receives a SIGUSR1). You do +not need to sleep between sending signals, as signals received are processed +sequencially in the order they are received. + diff --git a/ndhc/TODO b/ndhc/TODO new file mode 100644 index 0000000..6ddb266 --- /dev/null +++ b/ndhc/TODO @@ -0,0 +1,7 @@ +TODO +---- ++ using time(0) breaks if the system clock changes, find a portable solution ++ make failure of reading functions revert to previous value, not the default ++ sanity code for option[OPT_LEN] ++ fix aliasing (ie: eth0:0) + diff --git a/ndhc/clientpacket.c b/ndhc/clientpacket.c new file mode 100644 index 0000000..8b036db --- /dev/null +++ b/ndhc/clientpacket.c @@ -0,0 +1,256 @@ +/* clientpacket.c + * + * Packet generation and dispatching functions for the DHCP client. + * + * Russ Dill July 2001 + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dhcpd.h" +#include "packet.h" +#include "options.h" +#include "dhcpc.h" +#include "log.h" + +/* Create a random xid */ +unsigned long random_xid(void) +{ + static int initialized; + if (!initialized) { + int fd; + unsigned long seed; + + fd = open("/dev/urandom", O_RDONLY); + if (fd == -1 || read(fd, &seed, sizeof(seed)) < 0) { + log_line(LOG_WARNING, "Could not load seed from /dev/urandom: %s\n", + strerror(errno)); + seed = time(0); + } + if (fd != -1) + close(fd); + srand(seed); + initialized++; + } + return rand(); +} + + +/* initialize a packet with the proper defaults */ +static void init_packet(struct dhcpMessage *packet, char type) +{ + struct vendor { + char vendor; + char length; + char str[sizeof("ndhc "VERSION)]; + } vendor_id = { DHCP_VENDOR, sizeof("ndhc "VERSION) - 1, "ndhc "VERSION}; + + init_header(packet, type); + memcpy(packet->chaddr, client_config.arp, 6); + add_option_string(packet->options, client_config.clientid); + if (client_config.hostname) + add_option_string(packet->options, client_config.hostname); + add_option_string(packet->options, (unsigned char *)&vendor_id); +} + + +/* Add a paramater request list for stubborn DHCP servers. Pull the data + * from the struct in options.c. Don't do bounds checking here because it + * goes towards the head of the packet. */ +static void add_requests(struct dhcpMessage *packet) +{ + int end = end_option(packet->options); + int i, len = 0; + + packet->options[end + OPT_CODE] = DHCP_PARAM_REQ; + for (i = 0; options[i].code; i++) + if (options[i].flags & OPTION_REQ) + packet->options[end + OPT_DATA + len++] = options[i].code; + packet->options[end + OPT_LEN] = len; + packet->options[end + OPT_DATA + len] = DHCP_END; + +} + + +/* Broadcast a DHCP discover packet to the network, with an optionally + * requested IP */ +int send_discover(unsigned long xid, unsigned long requested) +{ + struct dhcpMessage packet; + + init_packet(&packet, DHCPDISCOVER); + packet.xid = xid; + if (requested) + add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); + + add_requests(&packet); + log_line(LOG_DEBUG, "Sending discover...\n"); + return raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, + SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); +} + + +/* Broadcasts a DHCP request message */ +int send_selecting(unsigned long xid, unsigned long server, + unsigned long requested) +{ + struct dhcpMessage packet; + struct in_addr addr; + + init_packet(&packet, DHCPREQUEST); + packet.xid = xid; + + add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); + add_simple_option(packet.options, DHCP_SERVER_ID, server); + + add_requests(&packet); + addr.s_addr = requested; + log_line(LOG_DEBUG, "Sending select for %s...\n", inet_ntoa(addr)); + return raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, + SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); +} + + +/* Unicasts or broadcasts a DHCP renew message */ +int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr) +{ + struct dhcpMessage packet; + int ret = 0; + + init_packet(&packet, DHCPREQUEST); + packet.xid = xid; + packet.ciaddr = ciaddr; + + add_requests(&packet); + log_line(LOG_DEBUG, "Sending renew...\n"); + if (server) + ret = kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT); + else + ret = raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, + SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); + return ret; +} + + +/* Unicasts a DHCP release message */ +int send_release(unsigned long server, unsigned long ciaddr) +{ + struct dhcpMessage packet; + + init_packet(&packet, DHCPRELEASE); + packet.xid = random_xid(); + packet.ciaddr = ciaddr; + + add_simple_option(packet.options, DHCP_REQUESTED_IP, ciaddr); + add_simple_option(packet.options, DHCP_SERVER_ID, server); + + log_line(LOG_DEBUG, "Sending release...\n"); + return kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT); +} + + +/* return -1 on errors that are fatal for the socket, + * -2 for those that aren't */ +int get_raw_packet(struct dhcpMessage *payload, int fd) +{ + int bytes; + struct udp_dhcp_packet packet; + uint32_t source, dest; + uint16_t check; + + memset(&packet, 0, sizeof(struct udp_dhcp_packet)); + bytes = read(fd, &packet, sizeof(struct udp_dhcp_packet)); + if (bytes < 0) { + debug(LOG_INFO, "couldn't read on raw listening socket -- ignoring\n"); + usleep(500000); /* possible down interface, looping condition */ + return -1; + } + + if (bytes < (int) (sizeof(struct iphdr) + sizeof(struct udphdr))) { + debug(LOG_INFO, "message too short, ignoring\n"); + return -2; + } + + if (bytes < ntohs(packet.ip.tot_len)) { + debug(LOG_INFO, "Truncated packet\n"); + return -2; + } + + /* ignore any extra garbage bytes */ + bytes = ntohs(packet.ip.tot_len); + + /* Make sure its the right packet for us, and that it passes + * sanity checks */ + if (packet.ip.protocol != IPPROTO_UDP + || packet.ip.version != IPVERSION + || packet.ip.ihl != sizeof(packet.ip) >> 2 + || packet.udp.dest != htons(CLIENT_PORT) + || bytes > (int)sizeof(struct udp_dhcp_packet) + || ntohs(packet.udp.len) != (short)(bytes - sizeof(packet.ip))) { + debug(LOG_INFO, "unrelated/bogus packet\n"); + return -2; + } + + /* check IP checksum */ + check = packet.ip.check; + packet.ip.check = 0; + if (check != checksum(&(packet.ip), sizeof(packet.ip))) { + debug(LOG_INFO, "bad IP header checksum, ignoring\n"); + return -1; + } + + /* verify the UDP checksum by replacing the header with a psuedo header */ + source = packet.ip.saddr; + dest = packet.ip.daddr; + check = packet.udp.check; + packet.udp.check = 0; + memset(&packet.ip, 0, sizeof(packet.ip)); + + packet.ip.protocol = IPPROTO_UDP; + packet.ip.saddr = source; + packet.ip.daddr = dest; + packet.ip.tot_len = packet.udp.len; /* cheat on the psuedo-header */ + if (check && check != checksum(&packet, bytes)) { + debug(LOG_ERR, "packet with bad UDP checksum received, ignoring\n"); + return -2; + } + + memcpy(payload, &(packet.data), + bytes - (sizeof(packet.ip) + sizeof(packet.udp))); + + if (ntohl(payload->cookie) != DHCP_MAGIC) { + log_line(LOG_ERR, "received bogus message (bad magic) -- ignoring\n"); + return -2; + } + debug(LOG_INFO, "oooooh!!! got some!\n"); + return bytes - (sizeof(packet.ip) + sizeof(packet.udp)); + +} + diff --git a/ndhc/clientpacket.h b/ndhc/clientpacket.h new file mode 100644 index 0000000..2a6facb --- /dev/null +++ b/ndhc/clientpacket.h @@ -0,0 +1,12 @@ +#ifndef _CLIENTPACKET_H +#define _CLIENTPACKET_H + +unsigned long random_xid(void); +int send_discover(unsigned long xid, unsigned long requested); +int send_selecting(unsigned long xid, unsigned long server, unsigned long requested); +int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr); +int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr); +int send_release(unsigned long server, unsigned long ciaddr); +int get_raw_packet(struct dhcpMessage *payload, int fd); + +#endif diff --git a/ndhc/dhcpc.c b/ndhc/dhcpc.c new file mode 100644 index 0000000..a0b20d0 --- /dev/null +++ b/ndhc/dhcpc.c @@ -0,0 +1,594 @@ +/* dhcpc.c + * + * ndhc DHCP client + * + * Russ Dill July 2001 + * Nicholas Kain 2004 + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dhcpd.h" +#include "dhcpc.h" +#include "options.h" +#include "clientpacket.h" +#include "packet.h" +#include "script.h" +#include "socket.h" +#include "log.h" +#include "rootcap.h" +#include "nstrl.h" + +static unsigned long requested_ip, server_addr, timeout; +static unsigned long lease, t1, t2, xid, start; +static int state, packet_num, fd, listen_mode; +static sig_atomic_t pending_exit, pending_renew, pending_release; + +enum { + LISTEN_NONE, + LISTEN_KERNEL, + LISTEN_RAW +}; + +struct client_config_t client_config = { + /* Default options. */ + .abort_if_no_lease = 0, + .foreground = 0, + .quit_after_lease = 0, + .background_if_no_lease = 0, + .interface = "eth0", + .script = "none", + .clientid = NULL, + .hostname = NULL, + .ifindex = 0, + .arp = "\0", +}; + +static void show_usage(void) +{ + printf( +"Usage: ndhc [OPTIONS]\n\n" +" -c, --clientid=CLIENTID Client identifier\n" +" -H, --hostname=HOSTNAME Client hostname\n" +" -h Alias for -H\n" +" -f, --foreground Do not fork after getting lease\n" +" -b, --background Fork to background if lease cannot be\n" +" immediately negotiated.\n" +" -i, --interface=INTERFACE Interface to use (default: eth0)\n" +" -n, --now Exit with failure if lease cannot be\n" +" immediately negotiated.\n" +" -q, --quit Quit after obtaining lease\n" +" -r, --request=IP IP address to request (default: none)\n" +" -u, --user Change privileges to this user\n" +" -C, --chroot Directory to which udhcp should chroot\n" +" -v, --version Display version\n" + ); + exit(EXIT_SUCCESS); +} + +/* just a little helper */ +static void change_mode(int new_mode) +{ + debug(LOG_INFO, "entering %s listen mode", + new_mode ? (new_mode == 1 ? "kernel" : "raw") : "none"); + close(fd); + fd = -1; + listen_mode = new_mode; +} + +/* perform a renew */ +static void perform_renew(void) +{ + log_line(LOG_INFO, "Performing a DHCP renew...\n"); + switch (state) { + case BOUND: + change_mode(LISTEN_KERNEL); + case RENEWING: + case REBINDING: + state = RENEW_REQUESTED; + break; + case RENEW_REQUESTED: /* impatient are we? fine, square 1 */ + run_script(NULL, SCRIPT_DECONFIG); + case REQUESTING: + case RELEASED: + change_mode(LISTEN_RAW); + state = INIT_SELECTING; + break; + case INIT_SELECTING: + break; + } + + /* start things over */ + packet_num = 0; + + /* Kill any timeouts because the user wants this to hurry along */ + timeout = 0; +} + + +/* perform a release */ +static void perform_release(void) +{ + char buf[16]; + struct in_addr temp_addr; + + buf[16] = '\0'; + + /* send release packet */ + if (state == BOUND || state == RENEWING || state == REBINDING) { + temp_addr.s_addr = server_addr; + snprintf(buf, sizeof buf, "%s", inet_ntoa(temp_addr)); + temp_addr.s_addr = requested_ip; + log_line(LOG_INFO, "Unicasting a release of %s to %s.\n", + inet_ntoa(temp_addr), buf); + send_release(server_addr, requested_ip); /* unicast */ + run_script(NULL, SCRIPT_DECONFIG); + } + log_line(LOG_INFO, "Entering released state.\n"); + + change_mode(LISTEN_NONE); + state = RELEASED; + timeout = 0x7fffffff; +} + +static void signal_handler(int sig) +{ + switch (sig) { + case SIGUSR1: + pending_renew = 1; + break; + case SIGUSR2: + pending_release = 1; + break; + case SIGTERM: + pending_exit = 1; + break; + } +} + +static void background(void) +{ + if (daemon(0, 0) == -1) { + perror("fork"); + exit(EXIT_SUCCESS); + } + client_config.foreground = 1; /* Do not fork again. */ +} + +static void handle_timeout(void) +{ + time_t now = time(0); + + /* timeout dropped to zero */ + switch (state) { + case INIT_SELECTING: + if (packet_num < 3) { + if (packet_num == 0) + xid = random_xid(); + + /* send discover packet */ + send_discover(xid, requested_ip); /* broadcast */ + + timeout = now + ((packet_num == 2) ? 4 : 2); + packet_num++; + } else { + if (client_config.background_if_no_lease) { + log_line(LOG_INFO, "No lease, going to background.\n"); + background(); + } else if (client_config.abort_if_no_lease) { + log_line(LOG_INFO, "No lease, failing.\n"); + exit(EXIT_FAILURE); + } + /* wait to try again */ + packet_num = 0; + timeout = now + 60; + } + break; + case RENEW_REQUESTED: + case REQUESTING: + if (packet_num < 3) { + /* send request packet */ + if (state == RENEW_REQUESTED) + /* unicast */ + send_renew(xid, server_addr, requested_ip); + else + /* broadcast */ + send_selecting(xid, server_addr, requested_ip); + timeout = now + ((packet_num == 2) ? 10 : 2); + packet_num++; + } else { + /* timed out, go back to init state */ + if (state == RENEW_REQUESTED) + run_script(NULL, SCRIPT_DECONFIG); + state = INIT_SELECTING; + timeout = now; + packet_num = 0; + change_mode(LISTEN_RAW); + } + break; + case BOUND: + /* Lease is starting to run out, time to enter renewing state */ + state = RENEWING; + change_mode(LISTEN_KERNEL); + debug(LOG_INFO, "Entering renew state.\n"); + /* fall right through */ + case RENEWING: + /* Either set a new T1, or enter REBINDING state */ + if ((t2 - t1) <= (lease / 14400 + 1)) { + /* timed out, enter rebinding state */ + state = REBINDING; + timeout = now + (t2 - t1); + debug(LOG_INFO, "Entering rebinding state.\n"); + } else { + /* send a request packet */ + send_renew(xid, server_addr, requested_ip); /* unicast */ + + t1 = ((t2 - t1) >> 1) + t1; + timeout = t1 + start; + } + break; + case REBINDING: + /* Either set a new T2, or enter INIT state */ + if ((lease - t2) <= (lease / 14400 + 1)) { + /* timed out, enter init state */ + state = INIT_SELECTING; + log_line(LOG_INFO, "Lease lost, entering init state.\n"); + run_script(NULL, SCRIPT_DECONFIG); + timeout = now; + packet_num = 0; + change_mode(LISTEN_RAW); + } else { + /* send a request packet */ + send_renew(xid, 0, requested_ip); /* broadcast */ + + t2 = ((lease - t2) >> 1) + t2; + timeout = t2 + start; + } + break; + case RELEASED: + /* yah, I know, *you* say it would never happen */ + timeout = 0x7fffffff; + break; + } +} + +static void handle_packet(void) +{ + unsigned char *temp = NULL, *message = NULL; + int len; + time_t now = time(0); + struct in_addr temp_addr; + struct dhcpMessage packet; + + debug(LOG_INFO, "got a packet\n"); + + if (listen_mode == LISTEN_KERNEL) + len = get_packet(&packet, fd); + else + len = get_raw_packet(&packet, fd); + + if (len == -1 && errno != EINTR) { + debug(LOG_INFO, "error on read, %s, reopening socket.\n", + strerror(errno)); + change_mode(listen_mode); /* just close and reopen */ + } + + if (len < 0) + return; + + if (packet.xid != xid) { + debug(LOG_INFO, "Ignoring XID %lx (our xid is %lx).\n", + (unsigned long) packet.xid, xid); + return; + } + + if ((message = get_option(&packet, DHCP_MESSAGE_TYPE)) == NULL) { + debug(LOG_ERR, "couldnt get option from packet -- ignoring\n"); + return; + } + + switch (state) { + case INIT_SELECTING: + /* Must be a DHCPOFFER to one of our xid's */ + if (*message == DHCPOFFER) { + if ((temp = get_option(&packet, DHCP_SERVER_ID))) { + memcpy(&server_addr, temp, 4); + xid = packet.xid; + requested_ip = packet.yiaddr; + + /* enter requesting state */ + state = REQUESTING; + timeout = now; + packet_num = 0; + } else { + debug(LOG_ERR, "No server ID in message\n"); + } + } + break; + case RENEW_REQUESTED: + case REQUESTING: + case RENEWING: + case REBINDING: + if (*message == DHCPACK) { + if (!(temp = get_option(&packet, DHCP_LEASE_TIME))) { + log_line(LOG_ERR, + "No lease time received, assuming 1h.\n"); + lease = 60 * 60; + } else { + memcpy(&lease, temp, 4); + lease = ntohl(lease); + } + + /* enter bound state */ + t1 = lease >> 1; + + /* little fixed point for n * .875 */ + t2 = (lease * 0x7) >> 3; + temp_addr.s_addr = packet.yiaddr; + log_line(LOG_INFO, + "Lease of %s obtained, lease time %ld.\n", + inet_ntoa(temp_addr), lease); + start = now; + timeout = t1 + start; + requested_ip = packet.yiaddr; + run_script(&packet, + ((state == RENEWING || state == REBINDING) + ? SCRIPT_RENEW : SCRIPT_BOUND)); + + state = BOUND; + change_mode(LISTEN_NONE); + if (client_config.quit_after_lease) + exit(EXIT_SUCCESS); + if (!client_config.foreground) + background(); + + } else if (*message == DHCPNAK) { + /* return to init state */ + log_line(LOG_INFO, "Received DHCP NAK.\n"); + run_script(&packet, SCRIPT_NAK); + if (state != REQUESTING) + run_script(NULL, SCRIPT_DECONFIG); + state = INIT_SELECTING; + timeout = now; + requested_ip = 0; + packet_num = 0; + change_mode(LISTEN_RAW); + sleep(3); /* avoid excessive network traffic */ + } + break; + case BOUND: + case RELEASED: + default: + break; + } +} + +static int do_work(void) +{ + struct timeval tv; + fd_set rfds; + for (;;) { + + /* Handle signals asynchronously. */ + if (pending_renew) + perform_renew(); + if (pending_release) + perform_release(); + if (pending_exit) { + log_line(LOG_INFO, "Received SIGTERM. Exiting gracefully.\n"); + exit(EXIT_SUCCESS); + } + + tv.tv_sec = timeout - time(0); + tv.tv_usec = 0; + + if (listen_mode != LISTEN_NONE && fd < 0) { + if (listen_mode == LISTEN_KERNEL) + fd = listen_socket(INADDR_ANY, CLIENT_PORT, + client_config.interface); + else + fd = raw_socket(client_config.ifindex); + + if (fd < 0) { + log_line(LOG_ERR, "FATAL: couldn't listen on socket: %s.\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + } + + if (tv.tv_sec <= 0) { + handle_timeout(); + continue; + } + + FD_ZERO(&rfds); + if (fd >= 0) + FD_SET(fd, &rfds); + debug(LOG_INFO, "Waiting on select...\n"); + if (select(fd + 1, &rfds, NULL, NULL, &tv) == -1) { + switch (errno) { + case EBADF: + fd = -1; + default: + debug(LOG_ERR, "Error: \"%s\" on select!\n", + strerror(errno)); + case EINTR: /* Signal received, go back to top. */ + continue; + } + } + debug(LOG_INFO, "select suceeded\n"); + + if (listen_mode != LISTEN_NONE && FD_ISSET(fd, &rfds)) + handle_packet(); + } +} + +int main(int argc, char **argv) +{ + char *chroot_dir = NULL; + int c, len; + struct passwd *pwd; + uid_t uid = 0; + gid_t gid = 0; + static struct option arg_options[] = { + {"clientid", required_argument, 0, 'c'}, + {"foreground", no_argument, 0, 'f'}, + {"background", no_argument, 0, 'b'}, + {"hostname", required_argument, 0, 'H'}, + {"hostname", required_argument, 0, 'h'}, + {"interface", required_argument, 0, 'i'}, + {"now", no_argument, 0, 'n'}, + {"quit", no_argument, 0, 'q'}, + {"request", required_argument, 0, 'r'}, + {"version", no_argument, 0, 'v'}, + {"user", required_argument, 0, 'u'}, + {"chroot", required_argument, 0, 'C'}, + {"help", no_argument, 0, '?'}, + {0, 0, 0, 0} + }; + + /* get options */ + while (1) { + int option_index = 0; + c = getopt_long(argc, argv, "c:fbH:h:i:np:qr:u:C:v", arg_options, + &option_index); + if (c == -1) break; + + switch (c) { + case 'c': + len = strlen(optarg) > 255 ? 255 : strlen(optarg); + if (client_config.clientid) + free(client_config.clientid); + client_config.clientid = malloc(len + 2); + client_config.clientid[OPT_CODE] = DHCP_CLIENT_ID; + client_config.clientid[OPT_LEN] = len; + client_config.clientid[OPT_DATA] = '\0'; + strlcpy((char *)client_config.clientid + OPT_DATA, optarg, len); + break; + case 'f': + client_config.foreground = 1; + break; + case 'b': + client_config.background_if_no_lease = 1; + break; + case 'h': + case 'H': + len = strlen(optarg) > 255 ? 255 : strlen(optarg); + if (client_config.hostname) + free(client_config.hostname); + client_config.hostname = malloc(len + 2); + client_config.hostname[OPT_CODE] = DHCP_HOST_NAME; + client_config.hostname[OPT_LEN] = len; + strlcpy((char*)client_config.hostname + 2, optarg, len); + break; + case 'i': + client_config.interface = optarg; + break; + case 'n': + client_config.abort_if_no_lease = 1; + break; + case 'q': + client_config.quit_after_lease = 1; + break; + case 'r': + requested_ip = inet_addr(optarg); + break; + case 'u': + pwd = getpwnam(optarg); + if (pwd) { + uid = (int)pwd->pw_uid; + gid = (int)pwd->pw_gid; + } else { + printf("Bad username provided.\n"); + exit(EXIT_FAILURE); + } + break; + case 'C': + len = strlen(optarg) > 255 ? 255 : strlen(optarg); + chroot_dir = malloc(len + 2); + memset(chroot_dir, '\0', len + 2); + strlcpy(chroot_dir, optarg, len); + break; + case 'v': + printf("ndhc, version %s\n\n", VERSION); + exit(EXIT_SUCCESS); + break; + default: + show_usage(); + } + } + + log_line(LOG_INFO, "ndhc client (v%s) started.\n", VERSION); + + if (read_interface(client_config.interface, &client_config.ifindex, + NULL, client_config.arp) < 0) + exit(EXIT_FAILURE); + + if (!client_config.clientid) { + client_config.clientid = malloc(6 + 3); + client_config.clientid[OPT_CODE] = DHCP_CLIENT_ID; + client_config.clientid[OPT_LEN] = 7; + client_config.clientid[OPT_DATA] = 1; + memcpy(client_config.clientid + 3, client_config.arp, 6); + } + + /* setup signal handlers */ + signal(SIGUSR1, signal_handler); + signal(SIGUSR2, signal_handler); + signal(SIGTERM, signal_handler); + + if (chdir(chroot_dir)) { + printf("Failed to chdir(%s)!\n", chroot_dir); + exit(EXIT_FAILURE); + } + + if (chroot(chroot_dir)) { + printf("Failed to chroot(%s)!\n", chroot_dir); + exit(EXIT_FAILURE); + } + + drop_root(uid, gid, + "cap_net_bind_service,cap_net_broadcast,cap_net_raw=ep"); + + state = INIT_SELECTING; + run_script(NULL, SCRIPT_DECONFIG); + change_mode(LISTEN_RAW); + + do_work(); + + return EXIT_SUCCESS; +} + diff --git a/ndhc/dhcpc.h b/ndhc/dhcpc.h new file mode 100644 index 0000000..1a7e81f --- /dev/null +++ b/ndhc/dhcpc.h @@ -0,0 +1,31 @@ +#ifndef _DHCPC_H +#define _DHCPC_H + +enum { + INIT_SELECTING, + REQUESTING, + BOUND, + RENEWING, + REBINDING, + INIT_REBOOT, + RENEW_REQUESTED, + RELEASED +}; + +struct client_config_t { + char foreground; /* Do not fork */ + char quit_after_lease; /* Quit after obtaining lease */ + char abort_if_no_lease; /* Abort if no lease */ + char background_if_no_lease; /* Fork to background if no lease */ + char *interface; /* The name of the interface to use */ + char *script; /* User script to run at dhcp events */ + unsigned char *clientid; /* Optional client id to use */ + unsigned char *hostname; /* Optional hostname to use */ + int ifindex; /* Index number of the interface to use */ + unsigned char arp[6]; /* Our arp address */ +}; + +extern struct client_config_t client_config; + +#endif + diff --git a/ndhc/dhcpd.h b/ndhc/dhcpd.h new file mode 100644 index 0000000..f9d3606 --- /dev/null +++ b/ndhc/dhcpd.h @@ -0,0 +1,132 @@ +/* dhcpd.h */ +#ifndef _DHCPD_H +#define _DHCPD_H + +#include +#include + +#include "leases.h" + +/************************************/ +/* Defaults _you_ may want to tweak */ +/************************************/ + +/* the period of time the client is allowed to use that address */ +#define LEASE_TIME (60*60*24*10) /* 10 days of seconds */ + +/*****************************************************************/ +/* Do not modify below here unless you know what you are doing!! */ +/*****************************************************************/ + +/* DHCP protocol -- see RFC 2131 */ +#define SERVER_PORT 67 +#define CLIENT_PORT 68 + +#define DHCP_MAGIC 0x63825363 + +/* DHCP option codes (partial list) */ +#define DHCP_PADDING 0x00 +#define DHCP_SUBNET 0x01 +#define DHCP_TIME_OFFSET 0x02 +#define DHCP_ROUTER 0x03 +#define DHCP_TIME_SERVER 0x04 +#define DHCP_NAME_SERVER 0x05 +#define DHCP_DNS_SERVER 0x06 +#define DHCP_LOG_SERVER 0x07 +#define DHCP_COOKIE_SERVER 0x08 +#define DHCP_LPR_SERVER 0x09 +#define DHCP_HOST_NAME 0x0c +#define DHCP_BOOT_SIZE 0x0d +#define DHCP_DOMAIN_NAME 0x0f +#define DHCP_SWAP_SERVER 0x10 +#define DHCP_ROOT_PATH 0x11 +#define DHCP_IP_TTL 0x17 +#define DHCP_MTU 0x1a +#define DHCP_BROADCAST 0x1c +#define DHCP_NTP_SERVER 0x2a +#define DHCP_WINS_SERVER 0x2c +#define DHCP_REQUESTED_IP 0x32 +#define DHCP_LEASE_TIME 0x33 +#define DHCP_OPTION_OVER 0x34 +#define DHCP_MESSAGE_TYPE 0x35 +#define DHCP_SERVER_ID 0x36 +#define DHCP_PARAM_REQ 0x37 +#define DHCP_MESSAGE 0x38 +#define DHCP_MAX_SIZE 0x39 +#define DHCP_T1 0x3a +#define DHCP_T2 0x3b +#define DHCP_VENDOR 0x3c +#define DHCP_CLIENT_ID 0x3d +#define DHCP_END 0xFF + +enum { + BOOTREQUEST = 1, + BOOTREPLY = 2 +}; + +#define ETH_10MB 1 +#define ETH_10MB_LEN 6 + +enum { + DHCPDISCOVER = 1, + DHCPOFFER = 2, + DHCPREQUEST = 3, + DHCPDECLINE = 4, + DHCPACK = 5, + DHCPNAK = 6, + DHCPRELEASE = 7, + DHCPINFORM = 8 +}; + +#define BROADCAST_FLAG 0x8000 + +enum { + OPTION_FIELD = 0, + FILE_FIELD = 1, + SNAME_FIELD = 2 +}; + +#define MAC_BCAST_ADDR (unsigned char *) "\xff\xff\xff\xff\xff\xff" + +enum { + OPT_CODE = 0, + OPT_LEN = 1, + OPT_DATA = 2 +}; + +struct option_set { + unsigned char *data; + struct option_set *next; +}; + +struct server_config_t { + uint32_t server; /* Our IP, in network order */ + uint32_t start; /* Start address of leases, network order */ + uint32_t end; /* End of leases, network order */ + struct option_set *options; /* List of DHCP options loaded from the config file */ + char *interface; /* The name of the interface to use */ + int ifindex; /* Index number of the interface to use */ + unsigned char arp[6]; /* Our arp address */ + unsigned long lease; /* lease time in seconds (host order) */ + unsigned long max_leases; /* maximum number of leases (including reserved address) */ + char remaining; /* should the lease file be interpreted as lease time remaining, or + * as the time the lease expires */ + unsigned long auto_time; /* how long should udhcpd wait before writing a config file. + * if this is zero, it will only write one on SIGUSR1 */ + unsigned long decline_time; /* how long an address is reserved if a client returns a + * decline message */ + unsigned long conflict_time; /* how long an arp conflict offender is leased for */ + unsigned long offer_time; /* how long an offered address is reserved */ + unsigned long min_lease; /* minimum lease a client can request*/ + char *lease_file; + char *notify_file; /* What to run whenever leases are written */ + uint32_t siaddr; /* next server bootp option */ + char *sname; /* bootp server name */ + char *boot_file; /* bootp boot file option */ +}; + +extern struct server_config_t server_config; +extern struct dhcpOfferedAddr *leases; + + +#endif diff --git a/ndhc/leases.h b/ndhc/leases.h new file mode 100644 index 0000000..aa0a233 --- /dev/null +++ b/ndhc/leases.h @@ -0,0 +1,24 @@ +/* leases.h */ +#ifndef _LEASES_H +#define _LEASES_H + + +struct dhcpOfferedAddr { + uint8_t chaddr[16]; + uint32_t yiaddr; /* network order */ + uint32_t expires; /* host order */ +}; + +extern unsigned char blank_chaddr[]; + +void clear_lease(uint8_t *chaddr, uint32_t yiaddr); +struct dhcpOfferedAddr *add_lease(uint8_t *chaddr, uint32_t yiaddr, unsigned long lease); +int lease_expired(struct dhcpOfferedAddr *lease); +struct dhcpOfferedAddr *oldest_expired_lease(void); +struct dhcpOfferedAddr *find_lease_by_chaddr(uint8_t *chaddr); +struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr); +uint32_t find_address(int check_expired); +int check_ip(uint32_t addr); + + +#endif diff --git a/ndhc/log.c b/ndhc/log.c new file mode 100644 index 0000000..9dd3d45 --- /dev/null +++ b/ndhc/log.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include + +void log_line(int level, char *format, ...) { + va_list argp; + + if (format == NULL) return; + + va_start(argp, format); + vfprintf(stderr, format, argp); + va_end(argp); + openlog("ndhc", 0, 0); + va_start(argp, format); + vsyslog(level, format, argp); + va_end(argp); + closelog(); +} + diff --git a/ndhc/log.h b/ndhc/log.h new file mode 100644 index 0000000..8b66412 --- /dev/null +++ b/ndhc/log.h @@ -0,0 +1,11 @@ +#ifndef H_LOG_H__ +#define H_LOG_H__ +#include +void log_line(int level, char *format, ...); +#ifdef DEBUG +#define debug log_line +#else +#define debug(...) +#endif +#endif + diff --git a/ndhc/ndhc.8 b/ndhc/ndhc.8 new file mode 100644 index 0000000..18dceaf --- /dev/null +++ b/ndhc/ndhc.8 @@ -0,0 +1,56 @@ +.TH NDHC 8 2004-02-25 Linux "Linux Administrator's Manual" +.SH NAME +ndhc \- secure DHCP client +.SH SYNOPSIS +.B ndhc +.RI [ OPTION ]... +.SH DESCRIPTION +The ndhc client negotiates a lease with the DHCP server and +informs ifchd of the change when it is obtained or lost. +.SH OPTIONS +.TP +.BI \-c\ CLIENTID ,\ \-\-clientid= CLIENTID +Send the client identifier +.IR CLIENTID . +.TP +.BR -f ,\ \-\-foreground +Do not fork after obtaining a lease. +.TP +.BI \-H\ HOSTNAME ,\ \-\-hostname= HOSTNAME +Send the client hostname +.IR HOSTNAME . +.TP +.BI \-h\ HOSTNAME +Alias for -H +.IR HOSTNAME . +.TP +.BI \-i\ INTERFACE ,\ \-\-interface= INTERFACE +Configure +.IR INTERFACE . +.TP +.BR -n ,\ \-\-now +Exit with failure if a lease cannot be obtained. +.TP +.BR -q ,\ \-\-quit +Exit after obtaining a lease. +.TP +.BI \-r\ ADDRESS ,\ \-\-request= ADDRESS +Request IP address +.IR ADDRESS . +.TP +.BR -v ,\ \-\-version +Display version. +.SH NOTES +.B ndhc +responds to the following signals: +.TP +.B SIGUSR1 +This signal causes +.B ndhc +to renew the current lease or, if it does not have one, obtain a +new lease. +.TP +.B SIGUSR2 +This signal caused +.B ndhc +to release the current lease. diff --git a/ndhc/nstrl.c b/ndhc/nstrl.c new file mode 100644 index 0000000..0b35fd2 --- /dev/null +++ b/ndhc/nstrl.c @@ -0,0 +1,48 @@ +/* nstrl.c - strlcpy/strlcat implementation + Time-stamp: <2003-05-28 02:35:13 njk> + + (C) 2003 Nicholas Jay Kain + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include + +#ifndef HAVE_STRLCPY + +size_t strlcpy (char *dest, char *src, size_t size) +{ + register char *d = dest, *s = src; + + for (; *s != '\0' && size > 0; size--, d++, s++) + *d = *s; + + *d = '\0'; + return (d - dest) + (s - src); +} + +size_t strlcat (char *dest, char *src, size_t size) +{ + register char *d = dest, *s = src; + + for (; size > 0 && *d != '\0'; size--, d++); + + for (; *s != '\0' && size > 0; size--, d++, s++) + *d = *s; + + *d = '\0'; + return (d - dest) + (s - src); +} + +#endif diff --git a/ndhc/nstrl.h b/ndhc/nstrl.h new file mode 100644 index 0000000..da2f8fc --- /dev/null +++ b/ndhc/nstrl.h @@ -0,0 +1,25 @@ +/* nstrl.h - header file for strlcpy/strlcat implementation + Time-stamp: <2003-05-28 02:34:47 njk> + + (C) 2003 Nicholas Jay Kain + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef NJK_HAVE_STRL_ +size_t strlcpy (char *dest, char *src, size_t size); +size_t strlcat (char *dest, char *src, size_t size); +#endif + +#define NJK_HAVE_STRL_ 1 diff --git a/ndhc/options.c b/ndhc/options.c new file mode 100644 index 0000000..7280b00 --- /dev/null +++ b/ndhc/options.c @@ -0,0 +1,228 @@ +/* + * options.c -- DHCP server option packet tools + * Rewrite by Russ Dill July 2001 + * Fixes and hardening: Nicholas Kain + */ + +#include +#include +#include + +#include "log.h" +#include "dhcpd.h" +#include "options.h" +#include "leases.h" + + +/* supported options are easily added here */ +struct dhcp_option options[] = { + /* name[10] flags code */ + {"subnet", OPTION_IP | OPTION_REQ, 0x01}, + {"timezone", OPTION_S32, 0x02}, + {"router", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x03}, + {"timesvr", OPTION_IP | OPTION_LIST, 0x04}, + {"namesvr", OPTION_IP | OPTION_LIST, 0x05}, + {"dns", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x06}, + {"logsvr", OPTION_IP | OPTION_LIST, 0x07}, + {"cookiesvr", OPTION_IP | OPTION_LIST, 0x08}, + {"lprsvr", OPTION_IP | OPTION_LIST, 0x09}, + {"hostname", OPTION_STRING | OPTION_REQ, 0x0c}, + {"bootsize", OPTION_U16, 0x0d}, + {"domain", OPTION_STRING | OPTION_REQ, 0x0f}, + {"swapsvr", OPTION_IP, 0x10}, + {"rootpath", OPTION_STRING, 0x11}, + {"ipttl", OPTION_U8, 0x17}, + {"mtu", OPTION_U16, 0x1a}, + {"broadcast", OPTION_IP | OPTION_REQ, 0x1c}, + {"ntpsrv", OPTION_IP | OPTION_LIST, 0x2a}, + {"wins", OPTION_IP | OPTION_LIST, 0x2c}, + {"requestip", OPTION_IP, 0x32}, + {"lease", OPTION_U32, 0x33}, + {"dhcptype", OPTION_U8, 0x35}, + {"serverid", OPTION_IP, 0x36}, + {"message", OPTION_STRING, 0x38}, + {"tftp", OPTION_STRING, 0x42}, + {"bootfile", OPTION_STRING, 0x43}, + {"", 0x00, 0x00} +}; + +/* Lengths of the different option types */ +int option_lengths[] = { + [OPTION_IP] = 4, + [OPTION_IP_PAIR] = 8, + [OPTION_BOOLEAN] = 1, + [OPTION_STRING] = 1, + [OPTION_U8] = 1, + [OPTION_U16] = 2, + [OPTION_S16] = 2, + [OPTION_U32] = 4, + [OPTION_S32] = 4 +}; + + +/* get an option with bounds checking (warning, not aligned). */ +unsigned char *get_option(struct dhcpMessage *packet, int code) +{ + int i = 0, length = 308; + unsigned char *optionptr; + int over = 0, done = 0, curr = OPTION_FIELD; + + optionptr = packet->options; + while (!done) { + if (i >= length) { + log_line(LOG_WARNING, "bogus packet, option fields too long.\n"); + return NULL; + } + if (optionptr[i + OPT_CODE] == code) { + if (i + 1 + optionptr[i + OPT_LEN] >= length) { + log_line(LOG_WARNING, "bogus packet, option fields too long.\n"); + return NULL; + } + return optionptr + i + 2; + } + switch (optionptr[i + OPT_CODE]) { + case DHCP_PADDING: + i++; + break; + case DHCP_OPTION_OVER: + if (i + 1 + optionptr[i + OPT_LEN] >= length) { + log_line(LOG_WARNING, + "bogus packet, option fields too long.\n"); + return NULL; + } + over = optionptr[i + 3]; + i += optionptr[OPT_LEN] + 2; + break; + case DHCP_END: + if (curr == OPTION_FIELD && over & FILE_FIELD) { + optionptr = packet->file; + i = 0; + length = 128; + curr = FILE_FIELD; + } else if (curr == FILE_FIELD && over & SNAME_FIELD) { + optionptr = packet->sname; + i = 0; + length = 64; + curr = SNAME_FIELD; + } else done = 1; + break; + default: + i += optionptr[OPT_LEN + i] + 2; + } + } + return NULL; +} + + +/* return the position of the 'end' option */ +int end_option(unsigned char *optionptr) +{ + int i = 0; + + while (i < 308 && optionptr[i] != DHCP_END) { + if (optionptr[i] == DHCP_PADDING) + ++i; + else + i += optionptr[i + OPT_LEN] + 2; + } + return (i < 308 ? i : 308); +} + + +/* add an option string to the options (an option string contains an option + * code, length, then data) */ +int add_option_string(unsigned char *optionptr, unsigned char *string) +{ + int end = end_option(optionptr); + + /* end position + string length + option code/length + end option */ + if (end + string[OPT_LEN] + 2 + 1 >= 308) { + log_line(LOG_ERR, "Option 0x%02x did not fit into the packet!\n", + string[OPT_CODE]); + return 0; + } + debug(LOG_INFO, "adding option 0x%02x\n", string[OPT_CODE]); + memcpy(optionptr + end, string, string[OPT_LEN] + 2); + optionptr[end + string[OPT_LEN] + 2] = DHCP_END; + return string[OPT_LEN] + 2; +} + +int add_simple_option(unsigned char *optionptr, unsigned char code, + uint32_t data) +{ + int i, length = 0; + unsigned char option[2 + 4]; + + for (i = 0; options[i].code; i++) + if (options[i].code == code) { + length = option_lengths[options[i].flags & TYPE_MASK]; + } + + option[OPT_CODE] = code; + option[OPT_LEN] = (unsigned char)length; + + if (!length) { + debug(LOG_ERR, "Could not add option 0x%02x\n", code); + return 0; + } else if (length == 1) { + uint8_t t = (uint8_t)data; + memcpy(option + 2, &t, 1); + } else if (length == 2) { + uint16_t t = (uint16_t)data; + memcpy(option + 2, &t, 2); + } else if (length == 4) { + uint32_t t = (uint32_t)data; + memcpy(option + 2, &t, 4); + } + return add_option_string(optionptr, option); +} + +/* find option 'code' in opt_list */ +struct option_set *find_option(struct option_set *opt_list, char code) +{ + while (opt_list && opt_list->data[OPT_CODE] < code) + opt_list = opt_list->next; + + if (opt_list && opt_list->data[OPT_CODE] == code) return opt_list; + else return NULL; +} + + +/* add an option to the opt_list */ +void attach_option(struct option_set **opt_list, struct dhcp_option *option, + char *buffer, int length) +{ + struct option_set *existing, *new, **curr; + + /* add it to an existing option */ + if ((existing = find_option(*opt_list, option->code))) { + debug(LOG_INFO, "Attaching option %s to existing member of list\n", + option->name); + if (option->flags & OPTION_LIST) { + if (existing->data[OPT_LEN] + length <= 255) { + existing->data = realloc(existing->data, + existing->data[OPT_LEN] + length + 2); + memcpy(existing->data + existing->data[OPT_LEN] + 2, buffer, + length); + existing->data[OPT_LEN] += length; + } /* else, ignore the data; we could put this in a second option + in the future */ + } /* else, ignore the new data */ + } else { + debug(LOG_INFO, "Attaching option %s to list\n", option->name); + + /* make a new option */ + new = malloc(sizeof(struct option_set)); + new->data = malloc(length + 2); + new->data[OPT_CODE] = option->code; + new->data[OPT_LEN] = length; + memcpy(new->data + 2, buffer, length); + + curr = opt_list; + while (*curr && (*curr)->data[OPT_CODE] < option->code) + curr = &(*curr)->next; + + new->next = *curr; + *curr = new; + } +} diff --git a/ndhc/options.h b/ndhc/options.h new file mode 100644 index 0000000..9b5b1ac --- /dev/null +++ b/ndhc/options.h @@ -0,0 +1,40 @@ +/* options.h */ +#ifndef _OPTIONS_H +#define _OPTIONS_H + +#include "packet.h" + +#define TYPE_MASK 0x0F + +enum { + OPTION_IP=1, + OPTION_IP_PAIR, + OPTION_STRING, + OPTION_BOOLEAN, + OPTION_U8, + OPTION_U16, + OPTION_S16, + OPTION_U32, + OPTION_S32 +}; + +#define OPTION_REQ 0x10 /* have the client request this option */ +#define OPTION_LIST 0x20 /* There can be a list of 1 or more of these */ + +struct dhcp_option { + char name[10]; + char flags; + unsigned char code; +}; + +extern struct dhcp_option options[]; +extern int option_lengths[]; + +unsigned char *get_option(struct dhcpMessage *packet, int code); +int end_option(unsigned char *optionptr); +int add_option_string(unsigned char *optionptr, unsigned char *string); +int add_simple_option(unsigned char *optionptr, unsigned char code, uint32_t data); +struct option_set *find_option(struct option_set *opt_list, char code); +void attach_option(struct option_set **opt_list, struct dhcp_option *option, char *buffer, int length); + +#endif diff --git a/ndhc/packet.c b/ndhc/packet.c new file mode 100644 index 0000000..10c65e8 --- /dev/null +++ b/ndhc/packet.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "packet.h" +#include "log.h" +#include "dhcpd.h" +#include "options.h" + + +void init_header(struct dhcpMessage *packet, char type) +{ + memset(packet, 0, sizeof(struct dhcpMessage)); + switch (type) { + case DHCPDISCOVER: + case DHCPREQUEST: + case DHCPRELEASE: + case DHCPINFORM: + packet->op = BOOTREQUEST; + break; + case DHCPOFFER: + case DHCPACK: + case DHCPNAK: + packet->op = BOOTREPLY; + } + packet->htype = ETH_10MB; + packet->hlen = ETH_10MB_LEN; + packet->cookie = htonl(DHCP_MAGIC); + packet->options[0] = DHCP_END; + add_simple_option(packet->options, DHCP_MESSAGE_TYPE, type); +} + + +/* read a packet from socket fd, return -1 on read error, -2 on packet error */ +int get_packet(struct dhcpMessage *packet, int fd) +{ + int bytes; + int i; + const char broken_vendors[][8] = { + "MSFT 98", + "" + }; + unsigned char *vendor; + + memset(packet, 0, sizeof(struct dhcpMessage)); + bytes = read(fd, packet, sizeof(struct dhcpMessage)); + if (bytes < 0) { + debug(LOG_INFO, "couldn't read on listening socket, ignoring\n"); + return -1; + } + + if (ntohl(packet->cookie) != DHCP_MAGIC) { + log_line(LOG_ERR, "received bogus message, ignoring.\n"); + return -2; + } + debug(LOG_INFO, "Received a packet\n"); + + if (packet->op == BOOTREQUEST + && (vendor = get_option(packet, DHCP_VENDOR))) + { + for (i = 0; broken_vendors[i][0]; i++) { + if (vendor[OPT_LEN - 2] == (unsigned char)strlen(broken_vendors[i]) + && !strncmp((char *)vendor, broken_vendors[i], + vendor[OPT_LEN - 2])) + { + debug(LOG_INFO, "broken client (%s), forcing broadcast\n", + broken_vendors[i]); + packet->flags |= htons(BROADCAST_FLAG); + } + } + } + return bytes; +} + +uint16_t checksum(void *addr, int count) +{ + /* Compute Internet Checksum for "count" bytes + * beginning at location "addr". + */ + register int32_t sum = 0; + uint16_t *source = (uint16_t *)addr; + + while (count > 1) { + sum += *source++; + count -= 2; + } + + /* Add left-over byte, if any */ + if (count > 0) { + /* Make sure that the left-over byte is added correctly both + * with little and big endian hosts */ + uint16_t tmp = 0; + *(unsigned char *) (&tmp) = * (unsigned char *) source; + sum += tmp; + } + /* Fold 32-bit sum to 16 bits */ + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} + + +/* Constuct a ip/udp header for a packet, and specify the source and dest + * hardware address */ +int raw_packet(struct dhcpMessage *payload, uint32_t source_ip, + int source_port, uint32_t dest_ip, int dest_port, + unsigned char *dest_arp, int ifindex) +{ + int fd, result = -1; + struct sockaddr_ll dest; + struct udp_dhcp_packet packet; + + if ((fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { + debug(LOG_ERR, "socket call failed: %s\n", strerror(errno)); + goto out; + } + + memset(&dest, 0, sizeof(dest)); + memset(&packet, 0, sizeof(packet)); + + dest.sll_family = AF_PACKET; + dest.sll_protocol = htons(ETH_P_IP); + dest.sll_ifindex = ifindex; + dest.sll_halen = 6; + memcpy(dest.sll_addr, dest_arp, 6); + if (bind(fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_ll)) < 0) { + debug(LOG_ERR, "bind call failed: %s\n", strerror(errno)); + goto out_fd; + } + + packet.ip.protocol = IPPROTO_UDP; + packet.ip.saddr = source_ip; + packet.ip.daddr = dest_ip; + packet.udp.source = htons(source_port); + packet.udp.dest = htons(dest_port); + /* cheat on the psuedo-header */ + packet.udp.len = htons(sizeof(packet.udp) + sizeof(struct dhcpMessage)); + packet.ip.tot_len = packet.udp.len; + memcpy(&(packet.data), payload, sizeof(struct dhcpMessage)); + packet.udp.check = checksum(&packet, sizeof(struct udp_dhcp_packet)); + + packet.ip.tot_len = htons(sizeof(struct udp_dhcp_packet)); + packet.ip.ihl = sizeof(packet.ip) >> 2; + packet.ip.version = IPVERSION; + packet.ip.ttl = IPDEFTTL; + packet.ip.check = checksum(&(packet.ip), sizeof(packet.ip)); + + result = sendto(fd, &packet, sizeof(struct udp_dhcp_packet), 0, + (struct sockaddr *)&dest, sizeof dest); + if (result <= 0) { + debug(LOG_ERR, "write on socket failed: %s\n", + strerror(errno)); + } +out_fd: + close(fd); +out: + return result; +} + + +/* Let the kernel do all the work for packet generation */ +int kernel_packet(struct dhcpMessage *payload, uint32_t source_ip, + int source_port, uint32_t dest_ip, int dest_port) +{ + int n = 1, fd, result = -1; + struct sockaddr_in client; + + if ((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) + goto out; + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof(n)) == -1) + goto out_fd; + + memset(&client, 0, sizeof(client)); + client.sin_family = AF_INET; + client.sin_port = htons(source_port); + client.sin_addr.s_addr = source_ip; + + if (bind(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1) + goto out_fd; + + memset(&client, 0, sizeof(client)); + client.sin_family = AF_INET; + client.sin_port = htons(dest_port); + client.sin_addr.s_addr = dest_ip; + + if (connect(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1) + goto out_fd; + + result = write(fd, payload, sizeof(struct dhcpMessage)); +out_fd: + close(fd); +out: + return result; +} + diff --git a/ndhc/packet.h b/ndhc/packet.h new file mode 100644 index 0000000..45e500f --- /dev/null +++ b/ndhc/packet.h @@ -0,0 +1,41 @@ +#ifndef _PACKET_H +#define _PACKET_H + +#include +#include + +struct dhcpMessage { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr; + uint32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + uint32_t cookie; + uint8_t options[308]; /* 312 - cookie */ +}; + +struct udp_dhcp_packet { + struct iphdr ip; + struct udphdr udp; + struct dhcpMessage data; +}; + +void init_header(struct dhcpMessage *packet, char type); +int get_packet(struct dhcpMessage *packet, int fd); +uint16_t checksum(void *addr, int count); +int raw_packet(struct dhcpMessage *payload, uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port, unsigned char *dest_arp, int ifindex); +int kernel_packet(struct dhcpMessage *payload, uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port); + + +#endif diff --git a/ndhc/rootcap.c b/ndhc/rootcap.c new file mode 100644 index 0000000..fe555eb --- /dev/null +++ b/ndhc/rootcap.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include + +#include "log.h" + +static void set_cap(uid_t uid, gid_t gid, char *captxt) +{ + cap_t caps; + + if (!captxt) { + log_line(LOG_ERR, "FATAL - set_cap: captxt == NULL\n"); + exit(EXIT_FAILURE); + } + + if (prctl(PR_SET_KEEPCAPS, 1)) { + log_line(LOG_ERR, "FATAL - set_cap: prctl() failed\n"); + exit(EXIT_FAILURE); + } + + if (setgroups(0, NULL) == -1) { + log_line(LOG_ERR, "FATAL - set_cap: setgroups() failed\n"); + exit(EXIT_FAILURE); + } + + if (setegid(gid) == -1 || seteuid(uid) == -1) { + log_line(LOG_ERR, "FATAL - set_cap: seteuid() failed\n"); + exit(EXIT_FAILURE); + } + + caps = cap_from_text(captxt); + if (!caps) { + log_line(LOG_ERR, "FATAL - set_cap: cap_from_text() failed\n"); + exit(EXIT_FAILURE); + } + + if (cap_set_proc(caps) == -1) { + log_line(LOG_ERR, "FATAL - set_cap: cap_set_proc() failed\n"); + exit(EXIT_FAILURE); + } + + cap_free(caps); +} + +void drop_root(uid_t uid, gid_t gid, char *captxt) +{ + if (!captxt) { + log_line(LOG_ERR, "FATAL - drop_root: captxt == NULL\n"); + exit(EXIT_FAILURE); + } + + if (uid == 0 || gid == 0) { + log_line(LOG_ERR, "FATAL - drop_root: attempt to drop root to root?\n"); + exit(EXIT_FAILURE); + } + + set_cap(uid, gid, captxt); + + if (setregid(gid, gid) == -1 || setreuid(uid, uid) == -1) { + log_line(LOG_ERR, "FATAL - drop_root: failed to drop root!\n"); + exit(EXIT_FAILURE); + } +} + diff --git a/ndhc/rootcap.h b/ndhc/rootcap.h new file mode 100644 index 0000000..cc59284 --- /dev/null +++ b/ndhc/rootcap.h @@ -0,0 +1,2 @@ +void drop_root(uid_t uid, gid_t gid, char *captxt); + diff --git a/ndhc/script.c b/ndhc/script.c new file mode 100644 index 0000000..996a7b2 --- /dev/null +++ b/ndhc/script.c @@ -0,0 +1,258 @@ +/* script.c + * + * Functions to call the interface change daemon + * + * Russ Dill July 2001 + * Nicholas Kain 2004 + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "options.h" +#include "dhcpd.h" +#include "dhcpc.h" +#include "packet.h" +#include "options.h" +#include "log.h" +#include "script.h" + +static int snprintip(char *dest, size_t size, unsigned char *ip) { + if (!dest) return -1; + return snprintf(dest, size, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); +} + +static int sprintip(char *dest, size_t size, char *pre, unsigned char *ip) { + if (!dest) return -1; + return snprintf(dest, size, "%s%d.%d.%d.%d", pre, ip[0], ip[1], ip[2], ip[3]); +} + +/* Fill dest with the text of option 'option'. */ +static void fill_options(char *dest, unsigned char *option, struct dhcp_option *type_p, unsigned int maxlen) +{ + int type, optlen; + uint16_t val_u16; + int16_t val_s16; + uint32_t val_u32; + int32_t val_s32; + int len = option[OPT_LEN - 2]; + char *odest; + + odest = dest; + + dest += snprintf(dest, maxlen, "%s=", type_p->name); + + type = type_p->flags & TYPE_MASK; + optlen = option_lengths[type]; + for(;;) { + switch (type) { + case OPTION_IP_PAIR: + dest += sprintip(dest, maxlen - (dest - odest), "", option); + *(dest++) = '/'; + option += 4; + optlen = 4; + case OPTION_IP: /* Works regardless of host byte order. */ + dest += sprintip(dest, maxlen - (dest - odest), "", option); + break; + case OPTION_BOOLEAN: + dest += snprintf(dest, maxlen - (dest - odest), *option ? "yes " : "no "); + break; + case OPTION_U8: + dest += snprintf(dest, maxlen - (dest - odest), "%u ", *option); + break; + case OPTION_U16: + memcpy(&val_u16, option, 2); + dest += snprintf(dest, maxlen - (dest - odest), "%u ", ntohs(val_u16)); + break; + case OPTION_S16: + memcpy(&val_s16, option, 2); + dest += snprintf(dest, maxlen - (dest - odest), "%d ", ntohs(val_s16)); + break; + case OPTION_U32: + memcpy(&val_u32, option, 4); + dest += snprintf(dest, maxlen - (dest - odest), "%lu ", (unsigned long) ntohl(val_u32)); + break; + case OPTION_S32: + memcpy(&val_s32, option, 4); + dest += snprintf(dest, maxlen - (dest - odest), "%ld ", (long) ntohl(val_s32)); + break; + case OPTION_STRING: + if ( (maxlen - (dest - odest)) < (unsigned)len) return; + memcpy(dest, option, len); + dest[len] = '\0'; + return; /* Short circuit this case */ + } + option += optlen; + len -= optlen; + if (len <= 0) break; + } +} + +static int open_ifch(void) { + int sockfd, ret; + struct sockaddr_un address = + { + .sun_family = AF_UNIX, + .sun_path = "ifchange" + }; + + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + ret = connect(sockfd, (struct sockaddr *)&address, sizeof(address)); + + if (ret == -1) { + log_line(LOG_ERR, "unable to connect to ifchd!\n"); + exit(EXIT_FAILURE); + } + + return sockfd; +} + +static void sockwrite(int fd, const void *buf, size_t count) +{ + int ret; + +sockwrite_again: + ret = write(fd, buf, count); + if (ret == -1) { + if (errno == EAGAIN) + goto sockwrite_again; + log_line(LOG_ERR, "error while writing to unix socket!\n"); + exit(EXIT_FAILURE); + } + if (ret < 0) ret = 0; + if ((unsigned int)ret < strlen(buf)) { + log_line(LOG_ERR, "incomplete write!\n"); + } + debug(LOG_INFO, "writing: %s\n", (char *)buf); +} + +static void deconfig_if(void) +{ + int sockfd; + char buf[256]; + + memset(buf, '\0', sizeof buf); + + sockfd = open_ifch(); + + snprintf(buf, sizeof buf, "interface:%s:", + client_config.interface); + sockwrite(sockfd, buf, strlen(buf)); + + snprintf(buf, sizeof buf, "ip:0.0.0.0:"); + sockwrite(sockfd, buf, strlen(buf)); + + close(sockfd); + exit(EXIT_SUCCESS); +} + +static void translate_option(int sockfd, struct dhcpMessage *packet, int opt) { + char buf[256], buf2[256]; + unsigned char *p; + int i; + + if (!packet) return; + + memset(buf, '\0', sizeof(buf)); + memset(buf2, '\0', sizeof(buf2)); + + p = get_option(packet, options[opt].code); + fill_options(buf2, p, &options[opt], sizeof(buf2) - 1); + snprintf(buf, sizeof buf, "%s:", buf2); + for (i=0; i<256; i++) { + if (buf[i] == '\0') break; + if (buf[i] == '=') { + buf[i] = ':'; + break; + } + } + sockwrite(sockfd, buf, strlen(buf)); +} + +static void bound_if(struct dhcpMessage *packet) +{ + int sockfd; + char buf[256], buf2[256]; + char ip[32]; + + if (!packet) return; + + memset(buf, '\0', sizeof(buf)); + memset(ip, '\0', sizeof(ip)); + memset(buf2, '\0', sizeof(buf2)); + + sockfd = open_ifch(); + + snprintf(buf, sizeof buf, "interface:%s:", client_config.interface); + sockwrite(sockfd, buf, strlen(buf)); + + snprintip(ip, sizeof ip, (unsigned char *) &packet->yiaddr); + snprintf(buf, sizeof buf, "ip:%s:", ip); + sockwrite(sockfd, buf, strlen(buf)); + + translate_option(sockfd, packet, 0); + translate_option(sockfd, packet, 2); + translate_option(sockfd, packet, 5); + translate_option(sockfd, packet, 9); + translate_option(sockfd, packet, 11); + translate_option(sockfd, packet, 15); + translate_option(sockfd, packet, 16); + translate_option(sockfd, packet, 17); + + close(sockfd); + exit(EXIT_SUCCESS); +} + +void run_script(struct dhcpMessage *packet, int mode) +{ + int pid; + + pid = fork(); + if (pid) { + waitpid(pid, NULL, 0); + return; + } else if (pid == 0) { + switch (mode) { + case SCRIPT_DECONFIG: + deconfig_if(); + break; + case SCRIPT_BOUND: + bound_if(packet); + break; + case SCRIPT_RENEW: + bound_if(packet); + break; + case SCRIPT_NAK: + deconfig_if(); + break; + default: + break; + } + log_line(LOG_ERR, "invalid script mode: %d\n", mode); + exit(EXIT_FAILURE); + } +} + diff --git a/ndhc/script.h b/ndhc/script.h new file mode 100644 index 0000000..7bbaa77 --- /dev/null +++ b/ndhc/script.h @@ -0,0 +1,13 @@ +#ifndef _SCRIPT_H +#define _SCRIPT_H + +enum { + SCRIPT_DECONFIG = 0, + SCRIPT_BOUND = 1, + SCRIPT_RENEW = 2, + SCRIPT_NAK = 4 +}; + +void run_script(struct dhcpMessage *packet, int mode); + +#endif diff --git a/ndhc/socket.c b/ndhc/socket.c new file mode 100644 index 0000000..0b9e256 --- /dev/null +++ b/ndhc/socket.c @@ -0,0 +1,155 @@ +/* + * socket.c -- DHCP server client/server socket creation + * + * udhcp client/server + * Copyright (C) 1999 Matthew Ramsay + * Chris Trew + * + * Rewrite by Russ Dill July 2001 + * + * Cleanup and fixes, Nicholas Kain 2004 + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "nstrl.h" + +int read_interface(char *interface, int *ifindex, uint32_t *addr, + unsigned char *arp) +{ + int fd, ret = -1; + struct ifreq ifr; + struct sockaddr_in *our_ip; + + memset(&ifr, 0, sizeof(struct ifreq)); + if((fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1) { + log_line(LOG_ERR, "socket failed!: %s\n", strerror(errno)); + goto out; + } + + ifr.ifr_addr.sa_family = AF_INET; + strlcpy(ifr.ifr_name, interface, IFNAMSIZ); + + if (addr) { + if (ioctl(fd, SIOCGIFADDR, &ifr)) { + log_line(LOG_ERR, "Couldn't get IP for %s.\n", strerror(errno)); + goto out_fd; + } + our_ip = (struct sockaddr_in *) &ifr.ifr_addr; + *addr = our_ip->sin_addr.s_addr; + debug(LOG_INFO, "%s (our ip) = %s\n", ifr.ifr_name, + inet_ntoa(our_ip->sin_addr)); + } + + if (ioctl(fd, SIOCGIFINDEX, &ifr)) { + log_line(LOG_ERR, "SIOCGIFINDEX failed!: %s\n", strerror(errno)); + goto out_fd; + } + + debug(LOG_INFO, "adapter index %d\n", ifr.ifr_ifindex); + *ifindex = ifr.ifr_ifindex; + + if (ioctl(fd, SIOCGIFHWADDR, &ifr)) { + log_line(LOG_ERR, "Couldn't get MAC for %s\n", strerror(errno)); + goto out_fd; + } + + memcpy(arp, ifr.ifr_hwaddr.sa_data, 6); + debug(LOG_INFO, "adapter hardware address %02x:%02x:%02x:%02x:%02x:%02x\n", + arp[0], arp[1], arp[2], arp[3], arp[4], arp[5]); + ret = 0; +out_fd: + close(fd); +out: + return ret; +} + +int listen_socket(unsigned int ip, int port, char *inf) +{ + struct ifreq interface; + int fd; + struct sockaddr_in addr; + int n = 1; + + debug(LOG_INFO, "Opening listen socket on 0x%08x:%d %s\n", ip, port, inf); + if ((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { + debug(LOG_ERR, "socket call failed: %s\n", strerror(errno)); + goto out; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = ip; + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&n, sizeof n) == -1) + goto out_fd; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char *)&n, sizeof n) == -1) + goto out_fd; + + strlcpy(interface.ifr_ifrn.ifrn_name, inf, IFNAMSIZ); + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, + (char *)&interface, sizeof interface) < 0) + goto out_fd; + + if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1) + goto out_fd; + + return fd; +out_fd: + close(fd); +out: + return -1; +} + +int raw_socket(int ifindex) +{ + int fd; + struct sockaddr_ll sock; + + debug(LOG_INFO, "Opening raw socket on ifindex %d\n", ifindex); + if ((fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { + debug(LOG_ERR, "socket call failed: %s\n", strerror(errno)); + goto out; + } + + sock.sll_family = AF_PACKET; + sock.sll_protocol = htons(ETH_P_IP); + sock.sll_ifindex = ifindex; + if (bind(fd, (struct sockaddr *) &sock, sizeof(sock)) < 0) { + debug(LOG_ERR, "bind call failed: %s\n", strerror(errno)); + goto out_fd; + } + + return fd; +out_fd: + close(fd); +out: + return -1; +} + diff --git a/ndhc/socket.h b/ndhc/socket.h new file mode 100644 index 0000000..fec361a --- /dev/null +++ b/ndhc/socket.h @@ -0,0 +1,9 @@ +/* socket.h */ +#ifndef _SOCKET_H +#define _SOCKET_H + +int read_interface(char *interface, int *ifindex, uint32_t *addr, unsigned char *arp); +int listen_socket(unsigned int ip, int port, char *inf); +int raw_socket(int ifindex); + +#endif diff --git a/nstrl.c b/nstrl.c new file mode 100644 index 0000000..2361da5 --- /dev/null +++ b/nstrl.c @@ -0,0 +1,46 @@ +/* nstrl.c - strlcpy/strlcat implementation + (C) 2003 Nicholas J. Kain + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include + +#ifndef HAVE_STRLCPY + +size_t strlcpy (char *dest, char *src, size_t size) +{ + register char *d = dest, *s = src; + + for (; *s != '\0' && size > 0; size--, d++, s++) + *d = *s; + + *d = '\0'; + return (d - dest) + (s - src); +} + +size_t strlcat (char *dest, char *src, size_t size) +{ + register char *d = dest, *s = src; + + for (; size > 0 && *d != '\0'; size--, d++); + + for (; *s != '\0' && size > 0; size--, d++, s++) + *d = *s; + + *d = '\0'; + return (d - dest) + (s - src); +} + +#endif diff --git a/nstrl.h b/nstrl.h new file mode 100644 index 0000000..5ce49a8 --- /dev/null +++ b/nstrl.h @@ -0,0 +1,23 @@ +/* nstrl.h - header file for strlcpy/strlcat implementation + (C) 2003 Nicholas J. Kain + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef NJK_HAVE_STRL_ +#define NJK_HAVE_STRL_ 1 +size_t strlcpy (char *dest, char *src, size_t size); +size_t strlcat (char *dest, char *src, size_t size); +#endif + diff --git a/pidfile.c b/pidfile.c new file mode 100644 index 0000000..d1fd370 --- /dev/null +++ b/pidfile.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include + +#include "defines.h" +#include "log.h" + +void write_pid(char *file) { + FILE *f; + char buf[MAXLINE]; + + if (!file) + return; + + f = fopen(file, "w"); + if (f == NULL) { + log_line("FATAL - failed to open pid file \"%s\"!\n", file); + exit(EXIT_FAILURE); + } + + snprintf(buf, sizeof buf, "%i", (unsigned int)getpid()); + fwrite(buf, sizeof (char), strlen(buf), f); + + if (fclose(f) != 0) { + log_line("FATAL - failed to close pid file \"%s\"!\n", file); + exit(EXIT_FAILURE); + } +} + +/* Return 0 on success, -1 on failure. */ +int file_exists(char *file, char *mode) { + FILE *f; + + if (file == NULL || mode == NULL) + return -1; + + f = fopen(file, mode); + if (f == NULL) + return -1; + fclose(f); + return 0; +} + diff --git a/pidfile.h b/pidfile.h new file mode 100644 index 0000000..100f151 --- /dev/null +++ b/pidfile.h @@ -0,0 +1,6 @@ +#ifndef NJK_PIDFILE_H_ +#define NJK_PIDFILE_H_ 1 +void write_pid(char *file); +int file_exists(char *file, char *mode); +#endif + diff --git a/signals.c b/signals.c new file mode 100644 index 0000000..54bda05 --- /dev/null +++ b/signals.c @@ -0,0 +1,46 @@ +/* signals.c - abstracts signal handling + (C) 2004 Nicholas J. Kain + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include +#include +#include +#include "log.h" + +void hook_signal(int signum, void (*fn)(int), int flags) { + struct sigaction new_action; + + new_action.sa_handler = fn; + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = flags; + + if (sigaction(signum, &new_action, NULL)) { + log_line("FATAL - failed to hook signal %i\n", signum); + exit(EXIT_FAILURE); + } +} + +void disable_signal(int signum) { + struct sigaction new_action; + + new_action.sa_handler = SIG_IGN; + sigemptyset(&new_action.sa_mask); + + if (sigaction(signum, &new_action, NULL)) { + log_line("FATAL - failed to ignore signal %i\n", signum); + exit(EXIT_FAILURE); + } +} diff --git a/signals.h b/signals.h new file mode 100644 index 0000000..93cc10e --- /dev/null +++ b/signals.h @@ -0,0 +1,6 @@ +#ifndef NJK_SIGNALS_H_ +#define NJK_SIGNALS_H_ 1 +void hook_signal(int signum, void (*fn)(int), int flags); +void disable_signal(int signum); +#endif + diff --git a/strlist.c b/strlist.c new file mode 100644 index 0000000..17cb834 --- /dev/null +++ b/strlist.c @@ -0,0 +1,71 @@ +#include +#include +#include + +#include "nstrl.h" +#include "strlist.h" + +void add_to_strlist(char *name, strlist_t **list) +{ + strlist_t *item, *t; + char *s; + unsigned int len; + + if (!list || !name) + return; + + len = strlen(name); + if (!len) + return; + s = malloc(len + 1); + if (!s) + return; + strlcpy(s, name, len + 1); + + item = malloc(sizeof (strlist_t)); + if (!item) + goto out0; + item->str = s; + item->next = NULL; + + if (!*list) { + *list = item; + return; + } + for (t = *list; t->next; t = t->next) + if (!t->next) { + t->next = item; + return; + } + + free(item); /* should be impossible, but hey */ +out0: + free(s); + return; +} + +void free_strlist(strlist_t *head) +{ + strlist_t *p = head, *q = NULL; + + while (p != NULL) { + free(p->str); + q = p; + p = q->next; + free(q); + } +} + +void free_stritem(strlist_t **p) +{ + strlist_t *q; + + if (!p || !*p) + return; + + q = (*p)->next; + free((*p)->str); + free(*p); + *p = q; +} + diff --git a/strlist.h b/strlist.h new file mode 100644 index 0000000..e639202 --- /dev/null +++ b/strlist.h @@ -0,0 +1,14 @@ +#ifndef NJK_STRLIST_H_ +#define NJK_STRLIST_H_ 1 + +typedef struct +{ + char *str; + void *next; +} strlist_t; + +void add_to_strlist(char *name, strlist_t **list); +void free_strlist(strlist_t *head); +void free_stritem(strlist_t **p); + +#endif