diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8a7c431 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[scripts/*] +indent_size = 4 + +[{**.c,**.h}] +indent_type = space +indent_size = 2 diff --git a/INSTALL b/INSTALL index b20627a..096e6f5 100644 --- a/INSTALL +++ b/INSTALL @@ -1,6 +1,9 @@ -pam_shield by Walter de Jong and +pam_shield + +Copyright (C) 2007-2024 +Walter de Jong Jonathan Niehof -Copyright 2007-2012 +Jeffrey Clark pam_shield COMES WITH NO WARRANTY. synctool IS FREE SOFTWARE. pam_shield is distributed under terms described in the GNU General Public diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0daa041 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,336 @@ +GNU General Public License +========================== + +_Version 2, June 1991_ +_Copyright © 1989, 1991 Free Software Foundation, Inc.,_ +_51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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. + +### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +**0.** This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The “Program”, below, +refers to any such program or work, and a “work based on the Program” +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term “modification”.) Each licensee is addressed as “you”. + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + +**1.** You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + +**2.** You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +* **a)** You must cause the modified files to carry prominent notices +stating that you changed the files and the date of any change. +* **b)** You must cause any work that you distribute or publish, that in +whole or in part contains or is derived from the Program or any +part thereof, to be licensed as a whole at no charge to all third +parties under the terms of this License. +* **c)** If the modified program normally reads commands interactively +when run, you must cause it, when started running for such +interactive use in the most ordinary way, to print or display an +announcement including an appropriate copyright notice and a +notice that there is no warranty (or else, saying that you provide +a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this +License. (Exception: if the Program itself is interactive but +does not normally print such an announcement, your work based on +the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +**3.** You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + +* **a)** Accompany it with the complete corresponding machine-readable +source code, which must be distributed under the terms of Sections +1 and 2 above on a medium customarily used for software interchange; or, +* **b)** Accompany it with a written offer, valid for at least three +years, to give any third party, for a charge no more than your +cost of physically performing source distribution, a complete +machine-readable copy of the corresponding source code, to be +distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, +* **c)** Accompany it with the information you received as to the offer +to distribute corresponding source code. (This alternative is +allowed only for noncommercial distribution and only if you +received the program in object code or executable form with such +an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +**4.** You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + +**5.** You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +**6.** Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +**7.** If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +**8.** If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + +**9.** The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and “any +later version”, you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + +**10.** If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + +### NO WARRANTY + +**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + +**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the “copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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) year 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 Lesser General +Public License instead of this License. diff --git a/README b/README index 8794710..86a551a 100644 --- a/README +++ b/README @@ -1,5 +1,9 @@ -pam_shield by Walter de Jong and Jonathan Niehof -, copyright 2007-2012. +pam_shield + +Copyright (C) 2007-2024 +Walter de Jong +Jonathan Niehof +Jeffrey Clark pam_shield COMES WITH NO WARRANTY. pam_shield IS FREE SOFTWARE. pam_shield is distributed under terms described in the GNU General Public diff --git a/pam_shield.c b/pam_shield.c index eab5e7f..9065b85 100644 --- a/pam_shield.c +++ b/pam_shield.c @@ -1,9 +1,10 @@ /* - pam_shield.c + pam_shield.c - pam_shield 0.9.7 - Copyright (C) 2007-2012 Walter de Jong - and Jonathan Niehof + Copyright (C) 2007-2024 + Walter de Jong + Jonathan Niehof + Jeffrey Clark 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 @@ -20,23 +21,21 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* - pam_shield is a PAM module that uses route/iptables to lock out script kiddies - that probe your machine for open logins and/or easy guessable passwords. + pam_shield is a PAM module that uses route/iptables to lock out script + kiddies that probe your machine for open logins and/or easy guessable + passwords. - You can run this module with + You can run this module with - auth optional pam_shield.so + auth optional pam_shield.so - But just make sure it's not the only auth module you run..! - This module does not do any authentication, it just monitors access. - - - (btw, if you don't like my indentation, try setting tabsize to 4) + But just make sure it's not the only auth module you run..! + This module does not do any authentication, it just monitors access. */ #include "pam_shield.h" -#define PAM_SM_AUTH 1 +#define PAM_SM_AUTH 1 #include @@ -45,323 +44,350 @@ #pragma GCC visibility push(hidden) void logmsg(int level, const char *fmt, ...) { -va_list varargs; + va_list varargs; - if (level == LOG_DEBUG && !(options & OPT_DEBUG)) - return; + if (level == LOG_DEBUG && !(options & OPT_DEBUG)) + return; #ifdef LOG_AUTHPRIV - openlog("PAM-shield", LOG_PID, LOG_AUTHPRIV); + openlog("PAM-shield", LOG_PID, LOG_AUTHPRIV); #else - openlog("PAM-shield", LOG_PID, LOG_AUTH); + openlog("PAM-shield", LOG_PID, LOG_AUTH); #endif - va_start(varargs, fmt); - vsyslog(level, fmt, varargs); - va_end(varargs); + va_start(varargs, fmt); + vsyslog(level, fmt, varargs); + va_end(varargs); - closelog(); + closelog(); } -/* - Mind that argv[0] is an argument, not the name of the module -*/ +/* Mind that argv[0] is an argument, not the name of the module */ static void get_options(int argc, char **argv) { -int i; - - for(i = 0; i < argc; i++) { - if (!strcmp(argv[i], "debug")) { - options |= OPT_DEBUG; - logmsg(LOG_DEBUG, "logging debug info"); - continue; - } - if (!strcmp(argv[i], "use_first_pass")) /* Thorsten Kukuk sez all modules should accept this argument */ - continue; - - if (!strncmp(argv[i], "conf=", 5)) { - conffile = argv[i] + 5; - continue; - } - logmsg(LOG_ERR, "unknown argument '%s', ignored", argv[i]); - } + int i; + + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "debug")) { + options |= OPT_DEBUG; + logmsg(LOG_DEBUG, "logging debug info"); + continue; + } + if (!strcmp(argv[i], "use_first_pass")) /* Thorsten Kukuk sez all modules + should accept this argument */ + continue; + + if (!strncmp(argv[i], "conf=", 5)) { + conffile = argv[i] + 5; + continue; + } + logmsg(LOG_ERR, "unknown argument '%s', ignored", argv[i]); + } } static _pam_shield_db_rec_t *new_db_record(int window_size) { -_pam_shield_db_rec_t *record; -int size; - - if (window_size <= 0) { - window_size = 1; - size = sizeof(_pam_shield_db_rec_t); - } else - size = sizeof(_pam_shield_db_rec_t) + (window_size-1) * sizeof(time_t); - - if ((record = (_pam_shield_db_rec_t *)malloc(size)) == NULL) { - logmsg(LOG_CRIT, "new_db_record(): out of memory allocating %d bytes", size); - return NULL; - } - memset(record, 0, size); - record->max_entries = window_size; - return record; + _pam_shield_db_rec_t *record; + int size; + + if (window_size <= 0) { + window_size = 1; + size = sizeof(_pam_shield_db_rec_t); + } else + size = sizeof(_pam_shield_db_rec_t) + (window_size - 1) * sizeof(time_t); + + if ((record = (_pam_shield_db_rec_t *)malloc(size)) == NULL) { + logmsg(LOG_CRIT, "new_db_record(): out of memory allocating %d bytes", + size); + return NULL; + } + memset(record, 0, size); + record->max_entries = window_size; + return record; } static void destroy_db_record(_pam_shield_db_rec_t *record) { - if (record != NULL) - free(record); + if (record != NULL) + free(record); } /* - get remote IPs for the rhost + get remote IPs for the rhost - the return value must be freed with freeaddrinfo() + the return value must be freed with freeaddrinfo() */ static struct addrinfo *get_addr_info(char *rhost) { -struct addrinfo hints, *res; -int err; - - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - - if ((err = getaddrinfo(rhost, NULL, &hints, &res)) != 0) { - logmsg(LOG_ERR, "%s: %s\n", rhost, gai_strerror(err)); - return NULL; - } - return res; + struct addrinfo hints, *res; + int err; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if ((err = getaddrinfo(rhost, NULL, &hints, &res)) != 0) { + logmsg(LOG_ERR, "%s: %s\n", rhost, gai_strerror(err)); + return NULL; + } + return res; } #pragma GCC visibility pop /* - the authenticate function always returns PAM_IGNORE, because this - module does not really authenticate -*/ -PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { -char *user, *rhost; -struct passwd *pwd; -unsigned int retry_count; -int suspicious_dns; - - if (init_module()) - return PAM_IGNORE; - - logmsg(LOG_DEBUG, "this is version " PAM_SHIELD_VERSION); - -/* - read_config() may fail (due to syntax errors, etc.), try to make the best of it - by continuing anyway -*/ - read_config(); - get_options(argc, (char **)argv); - -/* get the username */ - if (pam_get_item(pamh, PAM_USER, (const void **)(void *)&user) != PAM_SUCCESS) - user = NULL; - - if (user != NULL && !*user) - user = NULL; - - logmsg(LOG_DEBUG, "user %s", (user == NULL) ? "(unknown)" : user); - -/* if not blocking all and the user is known, let go */ - if (!(options & OPT_BLOCK_ALL) && user != NULL && (pwd = getpwnam(user)) != NULL) { - logmsg(LOG_DEBUG, "ignoring known user %s", user); - deinit_module(); - return PAM_IGNORE; - } - -/* get the remotehost address */ - if (pam_get_item(pamh, PAM_RHOST, (const void **)(void *)&rhost) != PAM_SUCCESS) - rhost = NULL; - - if (rhost != NULL && !*rhost) - rhost = NULL; - - logmsg(LOG_DEBUG, "remotehost %s", (rhost == NULL) ? "(unknown)" : rhost); - -/* - if rhost is NULL, pam_shield is probably being used for a local service here - Because pam_shield only makes sense in a networked environment, bail out now -*/ - if (rhost == NULL) { - deinit_module(); - return PAM_IGNORE; - } - -/* - if rhost is completely numeric, then it has no DNS entry -*/ - suspicious_dns = 0; - if (strspn(rhost, "0123456789.") == strlen(rhost) - || strspn(rhost, "0123456789:abcdefABCDEF") == strlen(rhost)) { - if (options & OPT_MISSING_DNS) - logmsg(LOG_DEBUG, "missing DNS entry for %s (allowed)", rhost); - else { - logmsg(LOG_DEBUG, "missing DNS entry for %s (denied)", rhost); - suspicious_dns = 1; - } - } else { -/* - see if this rhost is whitelisted + the authenticate function always returns PAM_IGNORE, because this + module does not really authenticate */ - if (match_name_list(rhost)) { - deinit_module(); - return PAM_IGNORE; - } - } - do { - struct addrinfo *addr_info, *addr_p; - unsigned char addr_family; - char ipbuf[INET6_ADDRSTRLEN], *saddr; - _pam_shield_db_rec_t *record; - datum key, data; - int whitelisted; - - if ((addr_info = get_addr_info(rhost)) == NULL) { /* missing reverse DNS entry */ - if (options & OPT_MISSING_REVERSE) - logmsg(LOG_DEBUG, "missing reverse DNS entry for %s (allowed)", rhost); - else { - logmsg(LOG_DEBUG, "missing reverse DNS entry for %s (denied)", rhost); - suspicious_dns = 1; - } - } -/* for every address that this host is known for, check for whitelist entry */ - for(addr_p = addr_info; addr_p != NULL; addr_p = addr_p->ai_next) { - whitelisted = 0; - switch(addr_p->ai_family) { - case PF_INET: - saddr = (char *)&((struct sockaddr_in *)(addr_p->ai_addr))->sin_addr.s_addr; - - if (match_ipv4_list((unsigned char *)saddr)) { - logmsg(LOG_DEBUG, "remoteip %s (whitelisted)", inet_ntop(AF_INET, (char *)&((struct sockaddr_in *)(addr_p->ai_addr))->sin_addr, ipbuf, sizeof(ipbuf))); - whitelisted = 1; - } else - logmsg(LOG_DEBUG, "remoteip %s", inet_ntop(AF_INET, (char *)&((struct sockaddr_in *)(addr_p->ai_addr))->sin_addr, ipbuf, sizeof(ipbuf))); - break; - - case PF_INET6: - saddr = (char *)&((struct sockaddr_in6 *)(addr_p->ai_addr))->sin6_addr.s6_addr; - - if (match_ipv6_list((unsigned char *)saddr)) { - logmsg(LOG_DEBUG, "remoteip %s (whitelisted)", inet_ntop(AF_INET6, (char *)&((struct sockaddr_in6 *)(addr_p->ai_addr))->sin6_addr, ipbuf, sizeof(ipbuf))); - whitelisted = 1; - } else - logmsg(LOG_DEBUG, "remoteip %s", inet_ntop(AF_INET6, (char *)&((struct sockaddr_in6 *)(addr_p->ai_addr))->sin6_addr, ipbuf, sizeof(ipbuf))); - break; - - default: - logmsg(LOG_DEBUG, "remoteip unknown (not IP)"); - - freeaddrinfo(addr_info); - deinit_module(); - return (suspicious_dns) ? PAM_AUTH_ERR : PAM_IGNORE; - } -/* host is whitelisted by an allow line in the config file, so exit */ - if (whitelisted) { - freeaddrinfo(addr_info); - deinit_module(); - return PAM_IGNORE; - } - } -/* open the database */ - retry_count=0; - while ((dbf = gdbm_open(dbfile, 512, GDBM_WRCREAT, (mode_t)0600, fatal_func)) == NULL) { - if (gdbm_errno != GDBM_CANT_BE_WRITER || retry_count>500) { - logmsg(LOG_ERR, "failed to open gdbm file '%s' : %s", dbfile, - gdbm_strerror(gdbm_errno)); - freeaddrinfo(addr_info); - deinit_module(); - return (suspicious_dns) ? PAM_AUTH_ERR : PAM_IGNORE; - } - logmsg(LOG_DEBUG,"waiting to open db, try %d",retry_count); - usleep(1000); - retry_count++; - } -/* for every address that this host is known for, check the database */ - for(addr_p = addr_info; addr_p != NULL; addr_p = addr_p->ai_next) { - whitelisted = 0; - switch(addr_p->ai_family) { - case PF_INET: - addr_family = PAM_SHIELD_ADDR_IPV4; - key.dptr = saddr = (char *)&((struct sockaddr_in *)(addr_p->ai_addr))->sin_addr.s_addr; - key.dsize = sizeof(struct in_addr); - break; - - case PF_INET6: - addr_family = PAM_SHIELD_ADDR_IPV6; - key.dptr = saddr = (char *)&((struct sockaddr_in6 *)(addr_p->ai_addr))->sin6_addr.s6_addr; - key.dsize = sizeof(struct in6_addr); - break; - - default: - addr_family = -1; - key.dptr = saddr = NULL; - key.dsize = 0; - } - if (key.dptr == NULL) - continue; - - data = gdbm_fetch(dbf, key); /* get db record */ - if (data.dptr != NULL) { - record = (_pam_shield_db_rec_t *)data.dptr; -/* - Although this code does some expiration, it only does so for "this ip"; - it is still necessary to run an external database cleanup process every - now and then (eg, from cron.daily) -*/ - expire_record(record); - - if (record->count >= record->max_entries) { /* shift, so we always log the most recent time */ - memmove(record->timestamps, &record->timestamps[1], (record->max_entries-1)*sizeof(time_t)); - record->count--; - } - record->timestamps[record->count++] = this_time; - - logmsg(LOG_DEBUG, "%u times from %s", record->count, rhost); -/* - too many in the interval, so trigger - - trigger "add" is subject to a race, so try to be smart about it - and do not add the same block within 20 seconds -*/ - if (record->count >= max_conns && this_time - record->trigger_active > 20 - && !run_trigger("add", record)) - record->trigger_active = this_time; - } else { - if ((record = new_db_record(max_conns)) != NULL) { - record->addr_family = addr_family; - memcpy(record->ip.any, saddr, key.dsize); - record->timestamps[record->count++] = this_time; - - logmsg(LOG_DEBUG, "putting new record in db"); - - if (max_conns <= 1) { /* (maybe) stupid, but possible */ - record->trigger_active = this_time; - run_trigger("add", record); - } - } - } - if (record != NULL) { - data.dptr = (char *)record; - data.dsize = sizeof(_pam_shield_db_rec_t) + (record->max_entries-1)*sizeof(time_t); - -/* key.dptr and key.dsize are still set to saddr and addr_size */ - - if (gdbm_store(dbf, key, data, GDBM_REPLACE)) - logmsg(LOG_ERR, "failed to write db record"); - } - destroy_db_record(record); - } - freeaddrinfo(addr_info); - gdbm_close(dbf); - } while(0); - - deinit_module(); - return (suspicious_dns) ? PAM_AUTH_ERR : PAM_IGNORE; +PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, + const char **argv) { + char *user, *rhost; + struct passwd *pwd; + unsigned int retry_count; + int suspicious_dns; + + if (init_module()) + return PAM_IGNORE; + + logmsg(LOG_DEBUG, "this is version " PAM_SHIELD_VERSION); + + /* + read_config() may fail (due to syntax errors, etc.), try to make the + best of it by continuing anyway + */ + read_config(); + get_options(argc, (char **)argv); + + /* get the username */ + if (pam_get_item(pamh, PAM_USER, (const void **)(void *)&user) != PAM_SUCCESS) + user = NULL; + + if (user != NULL && !*user) + user = NULL; + + logmsg(LOG_DEBUG, "user %s", (user == NULL) ? "(unknown)" : user); + + /* if not blocking all and the user is known, let go */ + if (!(options & OPT_BLOCK_ALL) && user != NULL && + (pwd = getpwnam(user)) != NULL) { + logmsg(LOG_DEBUG, "ignoring known user %s", user); + deinit_module(); + return PAM_IGNORE; + } + + /* get the remotehost address */ + if (pam_get_item(pamh, PAM_RHOST, (const void **)(void *)&rhost) != + PAM_SUCCESS) + rhost = NULL; + + if (rhost != NULL && !*rhost) + rhost = NULL; + + logmsg(LOG_DEBUG, "remotehost %s", (rhost == NULL) ? "(unknown)" : rhost); + + /* + if rhost is NULL, pam_shield is probably being used for a local + service here Because pam_shield only makes sense in a networked + environment, bail out now + */ + if (rhost == NULL) { + deinit_module(); + return PAM_IGNORE; + } + + /* if rhost is completely numeric, then it has no DNS entry */ + suspicious_dns = 0; + if (strspn(rhost, "0123456789.") == strlen(rhost) || + strspn(rhost, "0123456789:abcdefABCDEF") == strlen(rhost)) { + if (options & OPT_MISSING_DNS) + logmsg(LOG_DEBUG, "missing DNS entry for %s (allowed)", rhost); + else { + logmsg(LOG_DEBUG, "missing DNS entry for %s (denied)", rhost); + suspicious_dns = 1; + } + } else { + /* see if this rhost is whitelisted */ + if (match_name_list(rhost)) { + deinit_module(); + return PAM_IGNORE; + } + } + do { + struct addrinfo *addr_info, *addr_p; + unsigned char addr_family; + char ipbuf[INET6_ADDRSTRLEN], *saddr; + _pam_shield_db_rec_t *record; + datum key, data; + int whitelisted; + + if ((addr_info = get_addr_info(rhost)) == + NULL) { /* missing reverse DNS entry */ + if (options & OPT_MISSING_REVERSE) + logmsg(LOG_DEBUG, "missing reverse DNS entry for %s (allowed)", rhost); + else { + logmsg(LOG_DEBUG, "missing reverse DNS entry for %s (denied)", rhost); + suspicious_dns = 1; + } + } + /* for each address that host is known for, check for whitelist entry */ + for (addr_p = addr_info; addr_p != NULL; addr_p = addr_p->ai_next) { + whitelisted = 0; + switch (addr_p->ai_family) { + case PF_INET: + saddr = + (char *)&((struct sockaddr_in *)(addr_p->ai_addr))->sin_addr.s_addr; + + if (match_ipv4_list((unsigned char *)saddr)) { + logmsg( + LOG_DEBUG, "remoteip %s (whitelisted)", + inet_ntop( + AF_INET, + (char *)&((struct sockaddr_in *)(addr_p->ai_addr))->sin_addr, + ipbuf, sizeof(ipbuf))); + whitelisted = 1; + } else + logmsg( + LOG_DEBUG, "remoteip %s", + inet_ntop( + AF_INET, + (char *)&((struct sockaddr_in *)(addr_p->ai_addr))->sin_addr, + ipbuf, sizeof(ipbuf))); + break; + + case PF_INET6: + saddr = (char *)&((struct sockaddr_in6 *)(addr_p->ai_addr)) + ->sin6_addr.s6_addr; + + if (match_ipv6_list((unsigned char *)saddr)) { + logmsg(LOG_DEBUG, "remoteip %s (whitelisted)", + inet_ntop(AF_INET6, + (char *)&((struct sockaddr_in6 *)(addr_p->ai_addr)) + ->sin6_addr, + ipbuf, sizeof(ipbuf))); + whitelisted = 1; + } else + logmsg(LOG_DEBUG, "remoteip %s", + inet_ntop(AF_INET6, + (char *)&((struct sockaddr_in6 *)(addr_p->ai_addr)) + ->sin6_addr, + ipbuf, sizeof(ipbuf))); + break; + + default: + logmsg(LOG_DEBUG, "remoteip unknown (not IP)"); + + freeaddrinfo(addr_info); + deinit_module(); + return (suspicious_dns) ? PAM_AUTH_ERR : PAM_IGNORE; + } + /* host is whitelisted by an allow line in the config file, so exit */ + if (whitelisted) { + freeaddrinfo(addr_info); + deinit_module(); + return PAM_IGNORE; + } + } + /* open the database */ + retry_count = 0; + while ((dbf = gdbm_open(dbfile, 512, GDBM_WRCREAT, (mode_t)0600, + fatal_func)) == NULL) { + if (gdbm_errno != GDBM_CANT_BE_WRITER || retry_count > 500) { + logmsg(LOG_ERR, "failed to open gdbm file '%s' : %s", dbfile, + gdbm_strerror(gdbm_errno)); + freeaddrinfo(addr_info); + deinit_module(); + return (suspicious_dns) ? PAM_AUTH_ERR : PAM_IGNORE; + } + logmsg(LOG_DEBUG, "waiting to open db, try %d", retry_count); + usleep(1000); + retry_count++; + } + /* for every address that this host is known for, check the database */ + for (addr_p = addr_info; addr_p != NULL; addr_p = addr_p->ai_next) { + whitelisted = 0; + switch (addr_p->ai_family) { + case PF_INET: + addr_family = PAM_SHIELD_ADDR_IPV4; + key.dptr = saddr = + (char *)&((struct sockaddr_in *)(addr_p->ai_addr))->sin_addr.s_addr; + key.dsize = sizeof(struct in_addr); + break; + + case PF_INET6: + addr_family = PAM_SHIELD_ADDR_IPV6; + key.dptr = saddr = (char *)&((struct sockaddr_in6 *)(addr_p->ai_addr)) + ->sin6_addr.s6_addr; + key.dsize = sizeof(struct in6_addr); + break; + + default: + addr_family = -1; + key.dptr = saddr = NULL; + key.dsize = 0; + } + if (key.dptr == NULL) + continue; + + data = gdbm_fetch(dbf, key); /* get db record */ + if (data.dptr != NULL) { + record = (_pam_shield_db_rec_t *)data.dptr; + /* + Although this code does some expiration, it only does so for + "this ip"; it is still necessary to run an external database cleanup + process every now and then (eg, from cron.daily) + */ + expire_record(record); + + if (record->count >= record->max_entries) { /* shift, so we always log + the most recent time */ + memmove(record->timestamps, &record->timestamps[1], + (record->max_entries - 1) * sizeof(time_t)); + record->count--; + } + record->timestamps[record->count++] = this_time; + + logmsg(LOG_DEBUG, "%u times from %s", record->count, rhost); + /* + too many in the interval, so trigger + + trigger "add" is subject to a race, so try to be smart about it + and do not add the same block within 20 seconds + */ + if (record->count >= max_conns && + this_time - record->trigger_active > 20 && + !run_trigger("add", record)) + record->trigger_active = this_time; + } else { + if ((record = new_db_record(max_conns)) != NULL) { + record->addr_family = addr_family; + memcpy(record->ip.any, saddr, key.dsize); + record->timestamps[record->count++] = this_time; + + logmsg(LOG_DEBUG, "putting new record in db"); + + if (max_conns <= 1) { /* (maybe) stupid, but possible */ + record->trigger_active = this_time; + run_trigger("add", record); + } + } + } + if (record != NULL) { + data.dptr = (char *)record; + data.dsize = sizeof(_pam_shield_db_rec_t) + + (record->max_entries - 1) * sizeof(time_t); + + /* key.dptr and key.dsize are still set to saddr and addr_size */ + + if (gdbm_store(dbf, key, data, GDBM_REPLACE)) + logmsg(LOG_ERR, "failed to write db record"); + } + destroy_db_record(record); + } + freeaddrinfo(addr_info); + gdbm_close(dbf); + } while (0); + + deinit_module(); + return (suspicious_dns) ? PAM_AUTH_ERR : PAM_IGNORE; } -PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { - return PAM_SUCCESS; +PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, + const char **argv) { + return PAM_SUCCESS; } - -/* EOB */ diff --git a/pam_shield.h b/pam_shield.h index 0a74303..ec106dd 100644 --- a/pam_shield.h +++ b/pam_shield.h @@ -1,61 +1,78 @@ /* - pam_shield.h + pam_shield.h + + Copyright (C) 2007-2024 + Walter de Jong + Jonathan Niehof + Jeffrey Clark + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef PAM_SHIELD -#define PAM_SHIELD 1 +#define PAM_SHIELD 1 #include -#define PAM_SHIELD_ADDR_IPV4 0 -#define PAM_SHIELD_ADDR_IPV6 1 +#define PAM_SHIELD_ADDR_IPV4 0 +#define PAM_SHIELD_ADDR_IPV6 1 typedef struct { - unsigned char addr_family; /* PAM_SHIELD_ADDR_IPV4|PAM_SHIELD_ADDR_IPV6 */ - union { - struct in_addr in; /* IPv4 number */ - struct in6_addr in6; /* IPv6 number */ - char any[1]; /* access to any */ - } ip; - - unsigned int max_entries; /* number of timestamps */ - unsigned int count; /* number of auth requests done */ - time_t trigger_active; /* time the trigger was triggered (needed for expiration) */ - time_t timestamps[1]; /* sliding window of timestamps */ -} _pam_shield_db_rec_t; + unsigned char addr_family; /* PAM_SHIELD_ADDR_IPV4|PAM_SHIELD_ADDR_IPV6 */ + union { + struct in_addr in; /* IPv4 number */ + struct in6_addr in6; /* IPv6 number */ + char any[1]; /* access to any */ + } ip; + unsigned int max_entries; /* number of timestamps */ + unsigned int count; /* number of auth requests done */ + time_t trigger_active; /* time the trigger was triggered (needed for + expiration) */ + time_t timestamps[1]; /* sliding window of timestamps */ +} _pam_shield_db_rec_t; /* - the IP list is used for making in-memory whitelists - (they are not in the database, but in the config file) + the IP list is used for making in-memory whitelists + (they are not in the database, but in the config file) */ typedef struct ip_list_tag ip_list; struct ip_list_tag { - union { - struct in_addr in; - struct in6_addr in6; - unsigned char any[1]; - } ip; - - union { - struct in_addr in; - struct in6_addr in6; - unsigned char any[1]; - } mask; - - ip_list *prev, *next; + union { + struct in_addr in; + struct in6_addr in6; + unsigned char any[1]; + } ip; + + union { + struct in_addr in; + struct in6_addr in6; + unsigned char any[1]; + } mask; + + ip_list *prev, *next; }; /* whitelisted hosntnames and network names */ typedef struct name_list_tag name_list; struct name_list_tag { - name_list *prev, *next; + name_list *prev, *next; - char name[1]; + char name[1]; }; -#endif /* PAM_SHIELD */ - -/* EOB */ +#endif /* PAM_SHIELD */ diff --git a/pam_shield_lib.c b/pam_shield_lib.c index 37014f5..fe63954 100644 --- a/pam_shield_lib.c +++ b/pam_shield_lib.c @@ -1,9 +1,10 @@ /* - pam_shield_lib.c + pam_shield_lib.c - pam_shield 0.9.7 - Copyright (C) 2007-2012 Walter de Jong - and Jonathan Niehof + Copyright (C) 2007-2024 + Walter de Jong + Jonathan Niehof + Jeffrey Clark 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 @@ -20,24 +21,24 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include #include -#include -#include -#include -#include -#include -#include -#include +#include #include +#include #include -#include -#include -#include -#include -#include +#include +#include +#include #include "pam_shield_lib.h" @@ -62,717 +63,732 @@ long retention = DEFAULT_RETENTION; time_t this_time; - ip_list *new_ip_list(void) { -ip_list *ip; + ip_list *ip; - if ((ip = (ip_list *)malloc(sizeof(ip_list))) == NULL) - return NULL; + if ((ip = (ip_list *)malloc(sizeof(ip_list))) == NULL) + return NULL; - memset(ip, 0, sizeof(ip_list)); - return ip; + memset(ip, 0, sizeof(ip_list)); + return ip; } void destroy_ip_list(ip_list *list) { -ip_list *p; + ip_list *p; - while(list != NULL) { - p = list; - list = list->next; - free(p); - } + while (list != NULL) { + p = list; + list = list->next; + free(p); + } } void add_ip_list(ip_list **root, ip_list *ip) { - if (root == NULL || ip == NULL) - return; - - if (options & OPT_DEBUG) { - char addr[INET6_ADDRSTRLEN], mask[INET6_ADDRSTRLEN]; - - if (*root == allow_ipv4_list) /* (butt ugly check, just to get nice debug output) */ - logmsg(LOG_DEBUG, "allowing from %s/%s", inet_ntop(AF_INET, &ip->ip.in, addr, sizeof(addr)), - inet_ntop(AF_INET, &ip->mask.in, mask, sizeof(mask))); - else - logmsg(LOG_DEBUG, "allowing from %s/%s", inet_ntop(AF_INET6, &ip->ip.in6, addr, sizeof(addr)), - inet_ntop(AF_INET6, &ip->mask.in6, mask, sizeof(mask))); - } - ip->prev = ip->next = NULL; - - if (*root == NULL) { - *root = ip; - return; - } -/* prepend it */ - (*root)->prev = ip; - ip->next = *root; - *root = ip; + if (root == NULL || ip == NULL) + return; + + if (options & OPT_DEBUG) { + char addr[INET6_ADDRSTRLEN], mask[INET6_ADDRSTRLEN]; + + if (*root == + allow_ipv4_list) /* (butt ugly check, just to get nice debug output) */ + logmsg(LOG_DEBUG, "allowing from %s/%s", + inet_ntop(AF_INET, &ip->ip.in, addr, sizeof(addr)), + inet_ntop(AF_INET, &ip->mask.in, mask, sizeof(mask))); + else + logmsg(LOG_DEBUG, "allowing from %s/%s", + inet_ntop(AF_INET6, &ip->ip.in6, addr, sizeof(addr)), + inet_ntop(AF_INET6, &ip->mask.in6, mask, sizeof(mask))); + } + ip->prev = ip->next = NULL; + + if (*root == NULL) { + *root = ip; + return; + } + /* prepend it */ + (*root)->prev = ip; + ip->next = *root; + *root = ip; } /* - try to match an IP number against the allow list - returns 1 if it matches + try to match an IP number against the allow list + returns 1 if it matches */ int match_ipv4_list(unsigned char *saddr) { -ip_list *ip; -int i, match; - - for(ip = allow_ipv4_list; ip != NULL; ip = ip->next) { - match = 1; - for(i = 0; i < sizeof(ip->ip.in.s_addr); i++) { - if ((ip->ip.any[i] & ip->mask.any[i]) != (saddr[i] & ip->mask.any[i])) { - match = 0; - break; - } - } - if (match) { - char addr1[INET_ADDRSTRLEN], addr2[INET_ADDRSTRLEN], mask[INET_ADDRSTRLEN]; - - logmsg(LOG_DEBUG, "whitelist match: %s %s/%s", inet_ntop(AF_INET, saddr, addr1, sizeof(addr1)), - inet_ntop(AF_INET, &ip->ip.in, addr2, sizeof(addr2)), - inet_ntop(AF_INET, &ip->mask.in, mask, sizeof(mask))); - return 1; - } - } - return 0; + ip_list *ip; + int i, match; + + for (ip = allow_ipv4_list; ip != NULL; ip = ip->next) { + match = 1; + for (i = 0; i < sizeof(ip->ip.in.s_addr); i++) { + if ((ip->ip.any[i] & ip->mask.any[i]) != (saddr[i] & ip->mask.any[i])) { + match = 0; + break; + } + } + if (match) { + char addr1[INET_ADDRSTRLEN], addr2[INET_ADDRSTRLEN], + mask[INET_ADDRSTRLEN]; + + logmsg(LOG_DEBUG, "whitelist match: %s %s/%s", + inet_ntop(AF_INET, saddr, addr1, sizeof(addr1)), + inet_ntop(AF_INET, &ip->ip.in, addr2, sizeof(addr2)), + inet_ntop(AF_INET, &ip->mask.in, mask, sizeof(mask))); + return 1; + } + } + return 0; } int match_ipv6_list(unsigned char *saddr) { -ip_list *ip; -int i, match; - - for(ip = allow_ipv6_list; ip != NULL; ip = ip->next) { - match = 1; - for(i = 0; i < sizeof(ip->ip.in6.s6_addr); i++) { - if ((ip->ip.any[i] & ip->mask.any[i]) != (saddr[i] & ip->mask.any[i])) { - match = 0; - break; - } - } - if (match) { - char addr1[INET6_ADDRSTRLEN], addr2[INET6_ADDRSTRLEN], mask[INET6_ADDRSTRLEN]; - - logmsg(LOG_DEBUG, "whitelist match: %s %s/%s", inet_ntop(AF_INET6, saddr, addr1, sizeof(addr1)), - inet_ntop(AF_INET6, &ip->ip.in6, addr2, sizeof(addr2)), - inet_ntop(AF_INET6, &ip->mask.in6, mask, sizeof(mask))); - return 1; - } - } - return 0; + ip_list *ip; + int i, match; + + for (ip = allow_ipv6_list; ip != NULL; ip = ip->next) { + match = 1; + for (i = 0; i < sizeof(ip->ip.in6.s6_addr); i++) { + if ((ip->ip.any[i] & ip->mask.any[i]) != (saddr[i] & ip->mask.any[i])) { + match = 0; + break; + } + } + if (match) { + char addr1[INET6_ADDRSTRLEN], addr2[INET6_ADDRSTRLEN], + mask[INET6_ADDRSTRLEN]; + + logmsg(LOG_DEBUG, "whitelist match: %s %s/%s", + inet_ntop(AF_INET6, saddr, addr1, sizeof(addr1)), + inet_ntop(AF_INET6, &ip->ip.in6, addr2, sizeof(addr2)), + inet_ntop(AF_INET6, &ip->mask.in6, mask, sizeof(mask))); + return 1; + } + } + return 0; } -/* - name_lists are hostnames and/or network names -*/ +/* name_lists are hostnames and/or network names */ name_list *new_name_list(char *name) { -name_list *n; + name_list *n; - if (name == NULL || !*name) - return NULL; + if (name == NULL || !*name) + return NULL; - if ((n = (name_list *)malloc(sizeof(name_list) + strlen(name))) == NULL) - return NULL; + if ((n = (name_list *)malloc(sizeof(name_list) + strlen(name))) == NULL) + return NULL; - memset(n, 0, sizeof(name_list)); - strcpy(n->name, name); - return n; + memset(n, 0, sizeof(name_list)); + strcpy(n->name, name); + return n; } void destroy_name_list(name_list *list) { -name_list *p; + name_list *p; - while(list != NULL) { - p = list; - list = list->next; - free(p); - } + while (list != NULL) { + p = list; + list = list->next; + free(p); + } } void add_name_list(name_list **root, name_list *n) { - if (root == NULL || n == NULL) - return; + if (root == NULL || n == NULL) + return; - logmsg(LOG_DEBUG, "allowing from %s", n->name); + logmsg(LOG_DEBUG, "allowing from %s", n->name); - n->prev = n->next = NULL; + n->prev = n->next = NULL; - if (*root == NULL) { - *root = n; - return; - } -/* prepend it */ - (*root)->prev = n; - n->next = *root; - *root = n; + if (*root == NULL) { + *root = n; + return; + } + /* prepend it */ + (*root)->prev = n; + n->next = *root; + *root = n; } - /* - see if 'name' matches our whitelist - return 1 if it does + see if 'name' matches our whitelist + return 1 if it does */ int match_name_list(char *name) { -name_list *n; - - if (name == NULL || !*name) - return 0; - - for(n = allow_names; n != NULL; n = n->next) { - if (n->name[0] == '.') { - if ((strlen(name) > strlen(n->name)) && !strcasecmp(n->name, name + strlen(name) - strlen(n->name))) { - logmsg(LOG_DEBUG, "whitelist match: host %s in domain %s", name, n->name); - return 1; - } - } else { - if (!strcasecmp(n->name, name)) { - logmsg(LOG_DEBUG, "whitelist match: host %s", name); - return 1; - } - } - } - return 0; + name_list *n; + + if (name == NULL || !*name) + return 0; + + for (n = allow_names; n != NULL; n = n->next) { + if (n->name[0] == '.') { + if ((strlen(name) > strlen(n->name)) && + !strcasecmp(n->name, name + strlen(name) - strlen(n->name))) { + logmsg(LOG_DEBUG, "whitelist match: host %s in domain %s", name, + n->name); + return 1; + } + } else { + if (!strcasecmp(n->name, name)) { + logmsg(LOG_DEBUG, "whitelist match: host %s", name); + return 1; + } + } + } + return 0; } - -/* - initialize variables -*/ +/* initialize variables */ int init_module(void) { - this_time = time(NULL); + this_time = time(NULL); - conffile = strdup(DEFAULT_CONFFILE); - dbfile = strdup(DEFAULT_DBFILE); - trigger_cmd = strdup(DEFAULT_TRIGGER_CMD); + conffile = strdup(DEFAULT_CONFFILE); + dbfile = strdup(DEFAULT_DBFILE); + trigger_cmd = strdup(DEFAULT_TRIGGER_CMD); - if (conffile == NULL || dbfile == NULL || trigger_cmd == NULL) { - logmsg(LOG_CRIT, "out of memory"); - return -1; - } - return 0; + if (conffile == NULL || dbfile == NULL || trigger_cmd == NULL) { + logmsg(LOG_CRIT, "out of memory"); + return -1; + } + return 0; } void deinit_module(void) { - if (conffile != NULL) { - free(conffile); - conffile = NULL; - } - if (dbfile != NULL) { - free(dbfile); - dbfile = NULL; - } - if (trigger_cmd != NULL) { - free(trigger_cmd); - trigger_cmd = NULL; - } - destroy_ip_list(allow_ipv4_list); - allow_ipv4_list = NULL; - - destroy_ip_list(allow_ipv6_list); - allow_ipv6_list = NULL; - - destroy_name_list(allow_names); - allow_names = NULL; + if (conffile != NULL) { + free(conffile); + conffile = NULL; + } + if (dbfile != NULL) { + free(dbfile); + dbfile = NULL; + } + if (trigger_cmd != NULL) { + free(trigger_cmd); + trigger_cmd = NULL; + } + destroy_ip_list(allow_ipv4_list); + allow_ipv4_list = NULL; + + destroy_ip_list(allow_ipv6_list); + allow_ipv6_list = NULL; + + destroy_name_list(allow_names); + allow_names = NULL; } -/* - strip leading and trailing whitespace from a string -*/ +/* strip leading and trailing whitespace from a string */ void strip(char *str) { -char *p; -int i; + char *p; + int i; - if (str == NULL || !*str) - return; + if (str == NULL || !*str) + return; - p = str; + p = str; - if (*p == ' ' || *p == '\t') { - while(*p && (*p == ' ' || *p == '\t')) - p++; + if (*p == ' ' || *p == '\t') { + while (*p && (*p == ' ' || *p == '\t')) + p++; - memmove(str, p, strlen(p)+1); - } - i = strlen(str)-1; - while(i >= 0 && (str[i] == ' ' || str[i] == '\t' || str[i] == '\r' || str[i] == '\n')) - str[i--] = 0; + memmove(str, p, strlen(p) + 1); + } + i = strlen(str) - 1; + while (i >= 0 && + (str[i] == ' ' || str[i] == '\t' || str[i] == '\r' || str[i] == '\n')) + str[i--] = 0; } - /* - multipliers: - 1s second - 1m minute - 1h hour - 1d day - 1w week - 1M month - 1y year - - default is 1 - returns 0 on error + multipliers: + 1s second + 1m minute + 1h hour + 1d day + 1w week + 1M month + 1y year + + default is 1 + returns 0 on error */ long get_multiplier(char *str) { - if (str == NULL || !*str) - return 1L; + if (str == NULL || !*str) + return 1L; - if (str[1]) /* we expect only a single character here */ - return 0L; + if (str[1]) /* we expect only a single character here */ + return 0L; - switch(*str) { - case 's': - return 1L; + switch (*str) { + case 's': + return 1L; - case 'm': - return 60L; + case 'm': + return 60L; - case 'h': - return 3600L; + case 'h': + return 3600L; - case 'd': - return (3600L * 24L); + case 'd': + return (3600L * 24L); - case 'w': - return (7L * 3600L * 24L); + case 'w': + return (7L * 3600L * 24L); - case 'M': - return (30L * 3600L * 24L); + case 'M': + return (30L * 3600L * 24L); - case 'y': - case 'Y': - return (365L * 3600L * 24L); - } - return 0L; + case 'y': + case 'Y': + return (365L * 3600L * 24L); + } + return 0L; } /* - generate bitmask from '/24' notation + generate bitmask from '/24' notation - mask is struct in_addr.saddr, size is the size of the array - (4 for IPv4, 16 for IPv6) + mask is struct in_addr.saddr, size is the size of the array + (4 for IPv4, 16 for IPv6) */ void ip_bitmask(int bits, unsigned char *mask, int size) { -int i, num, rest; + int i, num, rest; - if (mask == NULL) - return; + if (mask == NULL) + return; - memset(mask, 0, size); + memset(mask, 0, size); - if (bits < 0) - bits = 0; + if (bits < 0) + bits = 0; - if (bits > size*8) - bits = size*8; + if (bits > size * 8) + bits = size * 8; - num = bits / 8; - rest = bits % 8; + num = bits / 8; + rest = bits % 8; - for(i = 0; i < num; i++) - mask[i] = 0xff; + for (i = 0; i < num; i++) + mask[i] = 0xff; - if (rest) - mask[i++] = ~(0xff >> rest); + if (rest) + mask[i++] = ~(0xff >> rest); - while(i < size) - mask[i++] = 0; + while (i < size) + mask[i++] = 0; } /* - allow network/netmask, for both IPv4 and IPv6 - netmask can be in canonical or decimal notation + allow network/netmask, for both IPv4 and IPv6 + netmask can be in canonical or decimal notation */ int allow_ip(char *ipnum, int line_no) { -char *netmask; -ip_list *ip; -name_list *name; -int bits; - - if (ipnum == NULL || !*ipnum) { - logmsg(LOG_ALERT, "%s:%d: missing argument to 'allow'", conffile, line_no); - return -1; - } - if ((netmask = strchr(ipnum, '/')) != NULL) { - *netmask = 0; - netmask++; - if (!*netmask) { - logmsg(LOG_ALERT, "%s:%d: missing netmask, assuming it is a host", conffile, line_no); - netmask = NULL; - } - } - if ((ip = new_ip_list()) == NULL) { - logmsg(LOG_ALERT, "%s:%d: out of memory adding 'allow' line", conffile, line_no); - return -1; - } -/* try network address as IPv4 */ - if (inet_pton(AF_INET, ipnum, &ip->ip.in) > 0) { - if (netmask == NULL) { /* no netmask given, treat as host */ - memset(&ip->mask.in.s_addr, 0xff, sizeof(ip->mask.in.s_addr)); - add_ip_list(&allow_ipv4_list, ip); - return 0; - } -/* netmask in '/24' like notation? */ - if (strspn(netmask, "0123456789") == strlen(netmask)) { - bits = atoi(netmask); - if (bits <= 0 || bits > 32) { - logmsg(LOG_ALERT, "%s:%d: syntax error in netmask", conffile, line_no); - destroy_ip_list(ip); - return -1; - } - ip_bitmask(bits, (unsigned char *)&ip->mask.in.s_addr, sizeof(ip->mask.in.s_addr)); - - add_ip_list(&allow_ipv4_list, ip); - return 0; - } -/* netmask in canonical notation? */ - if (inet_pton(AF_INET, netmask, &ip->mask.in) > 0) { - add_ip_list(&allow_ipv4_list, ip); - return 0; - } - logmsg(LOG_ALERT, "%s:%d: syntax error in netmask", conffile, line_no); - destroy_ip_list(ip); - return -1; - } -/* try network address as IPv6 */ - if (inet_pton(AF_INET6, ipnum, &ip->ip.in6) > 0) { - if (netmask == NULL) { /* no netmask given, treat as host */ - memset(ip->mask.in6.s6_addr, 0xff, sizeof(ip->mask.in6.s6_addr)); - add_ip_list(&allow_ipv6_list, ip); - return 0; - } -/* netmask in '/24' like notation? */ - if (strspn(netmask, "0123456789") == strlen(netmask)) { - bits = atoi(netmask); - if (bits <= 0 || bits > 32) { - logmsg(LOG_ALERT, "%s:%d: syntax error in netmask", conffile, line_no); - destroy_ip_list(ip); - return -1; - } - ip_bitmask(bits, (unsigned char *)ip->mask.in6.s6_addr, sizeof(ip->mask.in6.s6_addr)); - - add_ip_list(&allow_ipv6_list, ip); - return 0; - } -/* netmask in canonical notation? */ - if (inet_pton(AF_INET6, netmask, &ip->mask.in6) > 0) { - add_ip_list(&allow_ipv6_list, ip); - return 0; - } - logmsg(LOG_ALERT, "%s:%d: syntax error in netmask", conffile, line_no); - destroy_ip_list(ip); - return -1; - } -/* - when we get here it's either a syntax error or a hostname or a network name - with names, you can not specify a netmask -*/ - destroy_ip_list(ip); - ip = NULL; - - if (netmask != NULL) { - logmsg(LOG_ALERT, "%s:%d: syntax error in internet address", conffile, line_no); - return -1; - } - if ((name = new_name_list(ipnum)) == NULL) { - logmsg(LOG_ALERT, "%s:%d: out of memory while adding 'allow' line", conffile, line_no); - return -1; - } - add_name_list(&allow_names, name); - return 0; + char *netmask; + ip_list *ip; + name_list *name; + int bits; + + if (ipnum == NULL || !*ipnum) { + logmsg(LOG_ALERT, "%s:%d: missing argument to 'allow'", conffile, line_no); + return -1; + } + if ((netmask = strchr(ipnum, '/')) != NULL) { + *netmask = 0; + netmask++; + if (!*netmask) { + logmsg(LOG_ALERT, "%s:%d: missing netmask, assuming it is a host", + conffile, line_no); + netmask = NULL; + } + } + if ((ip = new_ip_list()) == NULL) { + logmsg(LOG_ALERT, "%s:%d: out of memory adding 'allow' line", conffile, + line_no); + return -1; + } + /* try network address as IPv4 */ + if (inet_pton(AF_INET, ipnum, &ip->ip.in) > 0) { + if (netmask == NULL) { /* no netmask given, treat as host */ + memset(&ip->mask.in.s_addr, 0xff, sizeof(ip->mask.in.s_addr)); + add_ip_list(&allow_ipv4_list, ip); + return 0; + } + /* netmask in '/24' like notation? */ + if (strspn(netmask, "0123456789") == strlen(netmask)) { + bits = atoi(netmask); + if (bits <= 0 || bits > 32) { + logmsg(LOG_ALERT, "%s:%d: syntax error in netmask", conffile, line_no); + destroy_ip_list(ip); + return -1; + } + ip_bitmask(bits, (unsigned char *)&ip->mask.in.s_addr, + sizeof(ip->mask.in.s_addr)); + + add_ip_list(&allow_ipv4_list, ip); + return 0; + } + /* netmask in canonical notation? */ + if (inet_pton(AF_INET, netmask, &ip->mask.in) > 0) { + add_ip_list(&allow_ipv4_list, ip); + return 0; + } + logmsg(LOG_ALERT, "%s:%d: syntax error in netmask", conffile, line_no); + destroy_ip_list(ip); + return -1; + } + /* try network address as IPv6 */ + if (inet_pton(AF_INET6, ipnum, &ip->ip.in6) > 0) { + if (netmask == NULL) { /* no netmask given, treat as host */ + memset(ip->mask.in6.s6_addr, 0xff, sizeof(ip->mask.in6.s6_addr)); + add_ip_list(&allow_ipv6_list, ip); + return 0; + } + /* netmask in '/24' like notation? */ + if (strspn(netmask, "0123456789") == strlen(netmask)) { + bits = atoi(netmask); + if (bits <= 0 || bits > 32) { + logmsg(LOG_ALERT, "%s:%d: syntax error in netmask", conffile, line_no); + destroy_ip_list(ip); + return -1; + } + ip_bitmask(bits, (unsigned char *)ip->mask.in6.s6_addr, + sizeof(ip->mask.in6.s6_addr)); + + add_ip_list(&allow_ipv6_list, ip); + return 0; + } + /* netmask in canonical notation? */ + if (inet_pton(AF_INET6, netmask, &ip->mask.in6) > 0) { + add_ip_list(&allow_ipv6_list, ip); + return 0; + } + logmsg(LOG_ALERT, "%s:%d: syntax error in netmask", conffile, line_no); + destroy_ip_list(ip); + return -1; + } + /* + when we get here it's either a syntax error or a hostname or a network + name with names, you can not specify a netmask + */ + destroy_ip_list(ip); + ip = NULL; + + if (netmask != NULL) { + logmsg(LOG_ALERT, "%s:%d: syntax error in internet address", conffile, + line_no); + return -1; + } + if ((name = new_name_list(ipnum)) == NULL) { + logmsg(LOG_ALERT, "%s:%d: out of memory while adding 'allow' line", + conffile, line_no); + return -1; + } + add_name_list(&allow_names, name); + return 0; } -/* - read configuration file -*/ +/* read configuration file */ int read_config(void) { -FILE *f; -struct stat statbuf; -char buf[MAX_LINE], *p, *endp; -int line_no, err; -long multiplier; - - logmsg(LOG_DEBUG, "reading config file '%s'", conffile); - - if ((f = fopen(conffile, "r")) == NULL) { - logmsg(LOG_ALERT, "failed to read config file '%s'", conffile); - return -1; - } - line_no = 0; - err = 0; - - while(fgets(buf, MAX_LINE, f) != NULL) { - line_no++; - - strip(buf); - if (!*buf || buf[0] == '#') - continue; - -/* keyword value */ - - p = buf; - while(*p && *p != ' ' && *p != '\t') - p++; - - if (!*p) { - logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); - err--; - continue; - } - *p = 0; - p++; - - strip(buf); - if (!*buf) { - logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); - err--; - continue; - } - strip(p); - if (!*p) { - logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); - err--; - continue; - } - -/* buf is the key, p is the value */ - - if (!strcmp(buf, "debug")) { - if (!strcmp(p, "on") || !strcmp(p, "yes")) { - options |= OPT_DEBUG; - logmsg(LOG_DEBUG, "logging debug info"); - continue; - } - if (!strcmp(p, "off") || !strcmp(p, "no")) { - logmsg(LOG_DEBUG, "ignoring config option 'debug %s' (overruled by PAM command line argument 'debug')", p); - continue; - } - logmsg(LOG_ALERT, "%s:%d: unknown argument '%s' to 'debug'", conffile, line_no, p); - continue; - } - if (!strcmp(buf, "block")) { - if (!strcmp(p, "all-users")) { - options |= OPT_BLOCK_ALL; - continue; - } - if (!strcmp(p, "unknown-users")) { - options &= ~OPT_BLOCK_ALL; - continue; - } - logmsg(LOG_ALERT, "%s:%d: unknown argument '%s' to 'block'", conffile, line_no, p); - err--; - continue; - } - if (!strcmp(buf, "allow_missing_dns")) { - if (!strcasecmp(p, "yes") || !strcasecmp(p, "allow") || !strcasecmp(p, "on")) { - options |= OPT_MISSING_DNS; - continue; - } - if (!strcasecmp(p, "no") || !strcasecmp(p, "deny") || !strcasecmp(p, "off")) { - options &= ~OPT_MISSING_DNS; - continue; - } - logmsg(LOG_ALERT, "%s:%d: unknown argument '%s' to 'allow_missing_dns'", conffile, line_no, p); - err--; - continue; - } - if (!strcmp(buf, "allow_missing_reverse")) { - if (!strcasecmp(p, "yes") || !strcasecmp(p, "allow") || !strcasecmp(p, "on")) { - options |= OPT_MISSING_REVERSE; - continue; - } - if (!strcasecmp(p, "no") || !strcasecmp(p, "deny") || !strcasecmp(p, "off")) { - options &= ~OPT_MISSING_REVERSE; - continue; - } - logmsg(LOG_ALERT, "%s:%d: unknown argument '%s' to 'allow_missing_reverse'", conffile, line_no, p); - err--; - continue; - } - if (!strcmp(buf, "allow")) { - if (allow_ip(p, line_no)) - err--; - continue; - } - if (!strcmp(buf, "db")) { - free(dbfile); - if ((dbfile = strdup(p)) == NULL) { - logmsg(LOG_CRIT, "out of memory"); - err--; - } - continue; - } - if (!strcmp(buf, "trigger_cmd")) { - free(trigger_cmd); - if ((trigger_cmd = strdup(p)) == NULL) { - logmsg(LOG_CRIT, "out of memory"); - err--; - } - if (stat(trigger_cmd, &statbuf) == -1) { - logmsg(LOG_ALERT, "%s:%d: command '%s' not found", conffile, line_no, trigger_cmd); - err--; - } - continue; - } - if (!strcmp(buf, "max_conns")) { - max_conns = (int)strtol(p, &endp, 10); - if (*endp) { - logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); - err--; - max_conns = DEFAULT_MAX_CONNS; - } - continue; - } - if (!strcmp(buf, "interval")) { - interval = (int)strtol(p, &endp, 10); - if (!(multiplier = get_multiplier(endp))) { - logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); - err--; - interval = DEFAULT_INTERVAL; - } else - interval *= multiplier; - continue; - } - if (!strcmp(buf, "retention")) { - retention = (int)strtol(p, &endp, 10); - if (!(multiplier = get_multiplier(endp))) { - logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); - err--; - retention = DEFAULT_RETENTION; - } else - retention *= multiplier; - continue; - } - logmsg(LOG_ALERT, "%s:%d: unknown keyword '%s'", conffile, line_no, buf); - err--; - } - fclose(f); - - logmsg(LOG_DEBUG, "done reading config file, %d errors", -err); - - return err; + FILE *f; + struct stat statbuf; + char buf[MAX_LINE], *p, *endp; + int line_no, err; + long multiplier; + + logmsg(LOG_DEBUG, "reading config file '%s'", conffile); + + if ((f = fopen(conffile, "r")) == NULL) { + logmsg(LOG_ALERT, "failed to read config file '%s'", conffile); + return -1; + } + line_no = 0; + err = 0; + + while (fgets(buf, MAX_LINE, f) != NULL) { + line_no++; + + strip(buf); + if (!*buf || buf[0] == '#') + continue; + + /* keyword value */ + + p = buf; + while (*p && *p != ' ' && *p != '\t') + p++; + + if (!*p) { + logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); + err--; + continue; + } + *p = 0; + p++; + + strip(buf); + if (!*buf) { + logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); + err--; + continue; + } + strip(p); + if (!*p) { + logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); + err--; + continue; + } + + /* buf is the key, p is the value */ + + if (!strcmp(buf, "debug")) { + if (!strcmp(p, "on") || !strcmp(p, "yes")) { + options |= OPT_DEBUG; + logmsg(LOG_DEBUG, "logging debug info"); + continue; + } + if (!strcmp(p, "off") || !strcmp(p, "no")) { + logmsg(LOG_DEBUG, + "ignoring config option 'debug %s' (overruled by PAM command " + "line argument 'debug')", + p); + continue; + } + logmsg(LOG_ALERT, "%s:%d: unknown argument '%s' to 'debug'", conffile, + line_no, p); + continue; + } + if (!strcmp(buf, "block")) { + if (!strcmp(p, "all-users")) { + options |= OPT_BLOCK_ALL; + continue; + } + if (!strcmp(p, "unknown-users")) { + options &= ~OPT_BLOCK_ALL; + continue; + } + logmsg(LOG_ALERT, "%s:%d: unknown argument '%s' to 'block'", conffile, + line_no, p); + err--; + continue; + } + if (!strcmp(buf, "allow_missing_dns")) { + if (!strcasecmp(p, "yes") || !strcasecmp(p, "allow") || + !strcasecmp(p, "on")) { + options |= OPT_MISSING_DNS; + continue; + } + if (!strcasecmp(p, "no") || !strcasecmp(p, "deny") || + !strcasecmp(p, "off")) { + options &= ~OPT_MISSING_DNS; + continue; + } + logmsg(LOG_ALERT, "%s:%d: unknown argument '%s' to 'allow_missing_dns'", + conffile, line_no, p); + err--; + continue; + } + if (!strcmp(buf, "allow_missing_reverse")) { + if (!strcasecmp(p, "yes") || !strcasecmp(p, "allow") || + !strcasecmp(p, "on")) { + options |= OPT_MISSING_REVERSE; + continue; + } + if (!strcasecmp(p, "no") || !strcasecmp(p, "deny") || + !strcasecmp(p, "off")) { + options &= ~OPT_MISSING_REVERSE; + continue; + } + logmsg(LOG_ALERT, + "%s:%d: unknown argument '%s' to 'allow_missing_reverse'", + conffile, line_no, p); + err--; + continue; + } + if (!strcmp(buf, "allow")) { + if (allow_ip(p, line_no)) + err--; + continue; + } + if (!strcmp(buf, "db")) { + free(dbfile); + if ((dbfile = strdup(p)) == NULL) { + logmsg(LOG_CRIT, "out of memory"); + err--; + } + continue; + } + if (!strcmp(buf, "trigger_cmd")) { + free(trigger_cmd); + if ((trigger_cmd = strdup(p)) == NULL) { + logmsg(LOG_CRIT, "out of memory"); + err--; + } + if (stat(trigger_cmd, &statbuf) == -1) { + logmsg(LOG_ALERT, "%s:%d: command '%s' not found", conffile, line_no, + trigger_cmd); + err--; + } + continue; + } + if (!strcmp(buf, "max_conns")) { + max_conns = (int)strtol(p, &endp, 10); + if (*endp) { + logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); + err--; + max_conns = DEFAULT_MAX_CONNS; + } + continue; + } + if (!strcmp(buf, "interval")) { + interval = (int)strtol(p, &endp, 10); + if (!(multiplier = get_multiplier(endp))) { + logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); + err--; + interval = DEFAULT_INTERVAL; + } else + interval *= multiplier; + continue; + } + if (!strcmp(buf, "retention")) { + retention = (int)strtol(p, &endp, 10); + if (!(multiplier = get_multiplier(endp))) { + logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); + err--; + retention = DEFAULT_RETENTION; + } else + retention *= multiplier; + continue; + } + logmsg(LOG_ALERT, "%s:%d: unknown keyword '%s'", conffile, line_no, buf); + err--; + } + fclose(f); + + logmsg(LOG_DEBUG, "done reading config file, %d errors", -err); + + return err; } /* - print the IP number of a db_record - return NULL on error, or buf on success + print the IP number of a db_record + return NULL on error, or buf on success */ const char *print_ip(_pam_shield_db_rec_t *record, char *buf, int buflen) { - if (buf == NULL || buflen <= 1) - return NULL; - - buflen--; - if (!buflen) { - *buf = 0; - return buf; - } - if (record == NULL) { - strncpy(buf, "(null)", buflen); - buf[buflen] = 0; - return buf; - } - switch(record->addr_family) { - case PAM_SHIELD_ADDR_IPV4: - return inet_ntop(AF_INET, &record->ip.in, buf, buflen); - - case PAM_SHIELD_ADDR_IPV6: - return inet_ntop(AF_INET6, &record->ip.in6, buf, buflen); - } - return NULL; + if (buf == NULL || buflen <= 1) + return NULL; + + buflen--; + if (!buflen) { + *buf = 0; + return buf; + } + if (record == NULL) { + strncpy(buf, "(null)", buflen); + buf[buflen] = 0; + return buf; + } + switch (record->addr_family) { + case PAM_SHIELD_ADDR_IPV4: + return inet_ntop(AF_INET, &record->ip.in, buf, buflen); + + case PAM_SHIELD_ADDR_IPV6: + return inet_ntop(AF_INET6, &record->ip.in6, buf, buflen); + } + return NULL; } -/* - run external command -*/ +/* run external command */ int run_trigger(char *cmd, _pam_shield_db_rec_t *record) { -char ipbuf[INET6_ADDRSTRLEN]; -pid_t pid; + char ipbuf[INET6_ADDRSTRLEN]; + pid_t pid; - if (cmd == NULL || record == NULL) - return -1; + if (cmd == NULL || record == NULL) + return -1; - if (print_ip(record, ipbuf, sizeof(ipbuf)) == NULL) - return -1; + if (print_ip(record, ipbuf, sizeof(ipbuf)) == NULL) + return -1; - logmsg(LOG_DEBUG, "running command '%s %s'", cmd, ipbuf); + logmsg(LOG_DEBUG, "running command '%s %s'", cmd, ipbuf); - if (options & OPT_DRYRUN) - return 0; + if (options & OPT_DRYRUN) + return 0; - pid = fork(); - if (pid == (pid_t)-1) { - logmsg(LOG_CRIT, "can not fork, failed to run trigger"); - return -1; - } - if (!pid) { - char *argv[4]; + pid = fork(); + if (pid == (pid_t)-1) { + logmsg(LOG_CRIT, "can not fork, failed to run trigger"); + return -1; + } + if (!pid) { + char *argv[4]; - argv[0] = trigger_cmd; - argv[1] = cmd; - argv[2] = ipbuf; - argv[3] = NULL; + argv[0] = trigger_cmd; + argv[1] = cmd; + argv[2] = ipbuf; + argv[3] = NULL; - execvp(argv[0], argv); + execvp(argv[0], argv); - logmsg(LOG_CRIT, "failed to execute command '%s %s %s'", trigger_cmd, cmd, ipbuf); - exit(-1); - } else { - pid_t err; - int status; + logmsg(LOG_CRIT, "failed to execute command '%s %s %s'", trigger_cmd, cmd, + ipbuf); + exit(-1); + } else { + pid_t err; + int status; - while((err = waitpid(pid, &status, 0)) > 0); + while ((err = waitpid(pid, &status, 0)) > 0) + ; - if (WEXITSTATUS(status) != 0) - return -1; - } - return 0; + if (WEXITSTATUS(status) != 0) + return -1; + } + return 0; } int expire_record(_pam_shield_db_rec_t *record) { -int updated; -char ipbuf[INET6_ADDRSTRLEN]; - - if (record == NULL) - return 0; - - updated = 0; -/* - expire entries that are no longer in this interval (sliding window) -*/ - while(record->count > 0 && difftime(this_time, record->timestamps[0]) >= (double)interval) { - memmove(record->timestamps, &record->timestamps[1], (record->max_entries-1)*sizeof(time_t)); - record->count--; - updated++; - } - if (record->trigger_active) { - if (difftime(this_time, record->trigger_active) >= (double)retention) { -/* - expire old trigger, but only do this if the sliding window is clean -*/ - if (!record->count) { - logmsg(LOG_DEBUG, "expiring old trigger for %s", print_ip(record, ipbuf, sizeof(ipbuf))); - record->trigger_active = (time_t)0L; - run_trigger("del", record); - updated++; - } - } else { - if (options & OPT_SYNC) { - run_trigger("sync", record); - } - } - } - return updated; + int updated; + char ipbuf[INET6_ADDRSTRLEN]; + + if (record == NULL) + return 0; + + updated = 0; + /* expire entries that are no longer in this interval (sliding window) */ + while (record->count > 0 && + difftime(this_time, record->timestamps[0]) >= (double)interval) { + memmove(record->timestamps, &record->timestamps[1], + (record->max_entries - 1) * sizeof(time_t)); + record->count--; + updated++; + } + if (record->trigger_active) { + if (difftime(this_time, record->trigger_active) >= (double)retention) { + /* expire old trigger, but only do this if the sliding window is clean */ + if (!record->count) { + logmsg(LOG_DEBUG, "expiring old trigger for %s", + print_ip(record, ipbuf, sizeof(ipbuf))); + record->trigger_active = (time_t)0L; + run_trigger("del", record); + updated++; + } + } else { + if (options & OPT_SYNC) { + run_trigger("sync", record); + } + } + } + return updated; } - -/* - gdbm has encountered a fatal error -*/ +/* gdbm has encountered a fatal error */ void fatal_func(const char *str) { - logmsg(LOG_ERR, "gdbm encountered a fatal error : %s; resetting the database", str); - - gdbm_close(dbf); - if ((dbf = gdbm_open(dbfile, 512, GDBM_NEWDB, (mode_t)0600, fatal_func)) == NULL) - logmsg(LOG_ERR, "failed to create new gdbm file '%s' : %s", dbfile, gdbm_strerror(gdbm_errno)); + logmsg(LOG_ERR, "gdbm encountered a fatal error : %s; resetting the database", + str); + + gdbm_close(dbf); + if ((dbf = gdbm_open(dbfile, 512, GDBM_NEWDB, (mode_t)0600, fatal_func)) == + NULL) + logmsg(LOG_ERR, "failed to create new gdbm file '%s' : %s", dbfile, + gdbm_strerror(gdbm_errno)); } #pragma GCC visibility pop -/* EOB */ diff --git a/pam_shield_lib.h b/pam_shield_lib.h index 2a09692..5189b09 100644 --- a/pam_shield_lib.h +++ b/pam_shield_lib.h @@ -1,9 +1,10 @@ /* - pam_shield_lib.h + pam_shield_lib.h - pam_shield 0.9.7 - Copyright (C) 2007-2012 Walter de Jong - and Jonathan Niehof + Copyright (C) 2007-2024 + Walter de Jong + Jonathan Niehof + Jeffrey Clark 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 @@ -20,45 +21,45 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include #include -#include -#include -#include -#include -#include -#include -#include +#include #include +#include #include -#include -#include -#include -#include -#include +#include +#include +#include #include "config.h" #include "pam_shield.h" #pragma GCC visibility push(hidden) -#define DEFAULT_MAX_CONNS 10 -#define DEFAULT_INTERVAL 60L -#define DEFAULT_RETENTION (3600L * 24L) +#define DEFAULT_MAX_CONNS 10 +#define DEFAULT_INTERVAL 60L +#define DEFAULT_RETENTION (3600L * 24L) -#define MAX_LINE 1024 +#define MAX_LINE 1024 -#define OPT_DEBUG 0x001 -#define OPT_BLOCK_ALL 0x002 /* block all, including known users */ -#define OPT_DRYRUN 0x004 -#define OPT_LISTDB 0x008 -#define OPT_MISSING_DNS 0x010 /* allow missing DNS */ -#define OPT_MISSING_REVERSE 0x020 /* allow missing reverse DNS */ -#define OPT_FORCE 0x040 /* purge unexpired entries */ -#define OPT_REMOVEIP 0x080 -#define OPT_SYNC 0x100 +#define OPT_DEBUG 0x001 +#define OPT_BLOCK_ALL 0x002 /* block all, including known users */ +#define OPT_DRYRUN 0x004 +#define OPT_LISTDB 0x008 +#define OPT_MISSING_DNS 0x010 /* allow missing DNS */ +#define OPT_MISSING_REVERSE 0x020 /* allow missing reverse DNS */ +#define OPT_FORCE 0x040 /* purge unexpired entries */ +#define OPT_REMOVEIP 0x080 +#define OPT_SYNC 0x100 extern int options; extern GDBM_FILE dbf; @@ -88,15 +89,15 @@ void destroy_ip_list(ip_list *list); void add_ip_list(ip_list **root, ip_list *ip); /* - try to match an IP number against the allow list - returns 1 if it matches + try to match an IP number against the allow list + returns 1 if it matches */ int match_ipv4_list(unsigned char *saddr); int match_ipv6_list(unsigned char *saddr); /* - name_lists are hostnames and/or network names + name_lists are hostnames and/or network names */ name_list *new_name_list(char *name); @@ -105,75 +106,63 @@ void destroy_name_list(name_list *list); void add_name_list(name_list **root, name_list *n); /* - see if 'name' matches our whitelist - return 1 if it does + see if 'name' matches our whitelist + return 1 if it does */ int match_name_list(char *name); - -/* - initialize variables -*/ +/* initialize variables */ int init_module(void); void deinit_module(void); -/* - strip leading and trailing whitespace from a string -*/ +/* strip leading and trailing whitespace from a string */ void strip(char *str); /* - multipliers: - 1s second - 1m minute - 1h hour - 1d day - 1w week - 1M month - 1y year - - default is 1 - returns 0 on error + multipliers: + 1s second + 1m minute + 1h hour + 1d day + 1w week + 1M month + 1y year + + default is 1 + returns 0 on error */ long get_multiplier(char *str); /* - generate bitmask from '/24' notation + generate bitmask from '/24' notation - mask is struct in_addr.saddr, size is the size of the array - (4 for IPv4, 16 for IPv6) + mask is struct in_addr.saddr, size is the size of the array + (4 for IPv4, 16 for IPv6) */ void ip_bitmask(int bits, unsigned char *mask, int size); /* - allow network/netmask, for both IPv4 and IPv6 - netmask can be in canonical or decimal notation + allow network/netmask, for both IPv4 and IPv6 + netmask can be in canonical or decimal notation */ int allow_ip(char *ipnum, int line_no); -/* - read configuration file -*/ +/* read configuration file */ int read_config(void); /* - print the IP number of a db_record - return NULL on error, or buf on success + print the IP number of a db_record + return NULL on error, or buf on success */ const char *print_ip(_pam_shield_db_rec_t *record, char *buf, int buflen); -/* - run external command -*/ +/* run external command */ int run_trigger(char *cmd, _pam_shield_db_rec_t *record); int expire_record(_pam_shield_db_rec_t *record); -/* - gdbm has encountered a fatal error -*/ +/* gdbm has encountered a fatal error */ void fatal_func(const char *str); #pragma GCC visibility pop -/* EOB */ diff --git a/scripts/shield-trigger b/scripts/shield-trigger index 65adf85..35174bd 100755 --- a/scripts/shield-trigger +++ b/scripts/shield-trigger @@ -1,31 +1,28 @@ #!/bin/sh # shellcheck disable=SC2086 # -# shield-trigger +# shield-trigger # -# pam_shield 0.9.7 -# Copyright (C) 2007-2012 Walter de Jong -# and Jonathan Niehof +# Copyright (C) 2007-2024 +# Walter de Jong +# Jonathan Niehof +# Jeffrey Clark # -# 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 free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA null_route() { - # - # louzy detection of IPv4 or IPv6 address - # TASK="$1" INET="" GW="127.0.0.1" @@ -34,9 +31,8 @@ null_route() { GW="::1" fi - if [ -x /sbin/ip ] - then - if [ "$TASK" = "show" ]; then + if [ -x /sbin/ip ] ; then + if [ "$TASK" = "show" ] ; then if /sbin/ip $INET route "$TASK" "$2" | read -r _x ; then return fi @@ -44,21 +40,16 @@ null_route() { fi /sbin/ip $INET route "$TASK" blackhole "$2" 2>/dev/null else - if [ -n "$INET" ] - then - INET="-A inet6" - fi + [ -n "$INET" ] && INET="-A inet6" /sbin/route $INET "$TASK" -host "$2" gw "$GW" dev lo fi - -# mail -s "[security] pam_shield blocked $2" root < -# and Carl Thompson +# Copyright (C) 2007-2024 +# Walter de Jong +# Jonathan Niehof +# Jeffrey Clark # -# 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 free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA run_iptables() { IPT="iptables" [ "${2#*:}" != "$2" ] && IPT="ip6tables" -# switch -A for iptables to -I -if [ "$1" = "-A" ] -then - TASK="-I" -else + # switch -A for iptables to -I TASK=$1 -fi + [ "$1" = "-A" ] && TASK="-I" -# check to see if pam_shield chain exists and create if necessary -CHAIN_TEST=$($IPT -L pam_shield 2>/dev/null) -if [ -z "$CHAIN_TEST" ] -then - "$IPT" -N pam_shield - "$IPT" -I pam_shield -j DROP - if [ "$TASK" = "-D" ]; then - return + # check to see if pam_shield chain exists and create if necessary + if [ -z "$($IPT -L pam_shield 2>/dev/null)" ] ; then + "$IPT" -N pam_shield + "$IPT" -I pam_shield -j DROP + [ "$TASK" = "-D" ] && return fi -fi -# -# CUSTOMIZE THIS RULE if you want to -# -# $TASK is the iptables command: -A/-I or -D -# $2 is the IP number -# -# * put in the correct chain name (pam_shield or INPUT) -# * put in the correct network interface name (e.g. -i eth0) -# Currently blocks on all interfaces -# * put in a port number (e.g.--destination-port 22 for ssh only) -# Currently blocks all ports -# * add additional rules for additional services as needed -# - -if ! "$IPT" "$TASK" INPUT -p tcp -s "$2" -j pam_shield ; then - if [ "$TASK" = "-C" ]; then - run_iptables "-I" "$2" + # CUSTOMIZE THIS RULE if you want to + # + # $TASK is the iptables command: -A/-I or -D + # $2 is the IP number + # + # * put in the correct chain name (pam_shield or INPUT) + # * put in the correct network interface name (e.g. -i eth0) + # Currently blocks on all interfaces + # * put in a port number (e.g.--destination-port 22 for ssh only) + # Currently blocks all ports + # * add additional rules for additional services as needed + + if ! "$IPT" "$TASK" INPUT -p tcp -s "$2" -j pam_shield ; then + [ "$TASK" = "-C" ] && run_iptables "-I" "$2" fi -fi - -# mail -s "[security] pam_shield blocked $2" root < -# and Jonathan Niehof +# Copyright (C) 2007-2024 +# Walter de Jong +# Jonathan Niehof +# Jeffrey Clark # -# 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 free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +log() { + logger -i -t shield-trigger -p authpriv.info "$*" +} usage() { echo "shield-trigger-ufw" @@ -29,34 +33,22 @@ usage() { exit 1 } - PATH=/sbin:/usr/sbin:/bin:/usr/bin -if [ -z "$2" ] -then - usage -fi +[ -z "$2" ] && usage case "$1" in add) - logger -i -t shield-trigger-ufw -p authpriv.info "blocking $2" + log "blocking $2" ufw insert 1 deny from "$2" - # mail -s "[security] pam_shield blocked $2" root < - never lock out this network # -# never lock out this network -# You should list all your local networks here to make sure no local user can -# lock you out from the inside +# You should list all your local networks here to make sure no local user can +# lock you out from the inside # +# This syntax is also supported: allow 127.0.0.1/8 allow 127.0.0.1/255.0.0.0 -# this syntax is also supported: -#allow 127.0.0.1/8 - - -# -# location of the database file -# +# location of the database file db /var/lib/pam_shield/db -# -# external command that is run when a site should be blocked/unblocked -# -#default: block with null routing +# external command that is run when a site should be blocked/unblocked +# default: block with null routing trigger_cmd /usr/sbin/shield-trigger -#option: use iptables instead -#trigger_cmd /usr/sbin/shield-trigger-iptables -#option: use ufw instead -#trigger_cmd /usr/sbin/shield-trigger-ufw +# or block using iptables +# trigger_cmd /usr/sbin/shield-trigger-iptables +# or block using ufw +# trigger_cmd /usr/sbin/shield-trigger-ufw -# -# number of connections per interval from one site that triggers us -# +# number of connections per interval from one site that triggers us max_conns 10 +# the interval and retention period may be specified in seconds, or +# with a postfix: # -# the interval and retention period may be specified in seconds, or -# with a postfix: -# -# 1s seconds 1w weeks -# 1m minutes 1M months (30 days) -# 1h hours 1y years -# 1d days +# 1s seconds 1w weeks +# 1m minutes 1M months (30 days) +# 1h hours 1y years +# 1d days # interval 5m -# -# period until the entry expires from the database again -# +# period until the entry expires from the database again retention 1w - -# EOB diff --git a/shield_purge.c b/shield_purge.c index dfe3fb2..bd0cb1c 100644 --- a/shield_purge.c +++ b/shield_purge.c @@ -1,9 +1,10 @@ /* - shield_purge.c + shield_purge.c - pam_shield 0.9.7 - Copyright (C) 2007-2012 Walter de Jong - and Jonathan Niehof + Copyright (C) 2007-2024 + Walter de Jong + Jonathan Niehof + Jeffrey Clark 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 @@ -22,322 +23,312 @@ #include "pam_shield.h" -#include #include +#include #include "pam_shield_lib.h" - void logmsg(int level, const char *fmt, ...) { -va_list varargs; + va_list varargs; - if (level == LOG_DEBUG && !(options & OPT_DEBUG)) - return; + if (level == LOG_DEBUG && !(options & OPT_DEBUG)) + return; - va_start(varargs, fmt); - vfprintf(stderr, fmt, varargs); - fprintf(stderr, "\n"); - va_end(varargs); + va_start(varargs, fmt); + vfprintf(stderr, fmt, varargs); + fprintf(stderr, "\n"); + va_end(varargs); } - static void usage(char *progname) { - printf( -"shield-purge " PAM_SHIELD_VERSION "\n" -"usage: %s \n" -"options:\n" -" -h, --help Display this information\n" -" -c, --conf=file Specify config file (default: " DEFAULT_CONFFILE ")\n" -" -d, --debug Verbose output for debugging purposes\n" -" -n, --dry-run Do not perform any updates\n" -" -l, --list List all database entries\n" -" -f, --force Delete all entries, even if unexpired\n" -" -r, --remove=ip Remove IP from database\n" -" -s, --sync Trigger sync for active records\n" -" (rebuild/verify firewall rules)\n" -, basename(progname)); - - printf("\n" -"This program is part of the PAM-shield package.\n" -"PAM-shield comes with ABSOLUTELY NO WARRANTY. This is free software, and you\n" -"are welcome to redistribute it under certain conditions. See the GNU\n" -"General Public Licence for details.\n" -"\n" -"Copyright (C) 2007-2011 by Walter de Jong \n" -"Copyright 2010 Jonathan Niehof \n"); - exit(1); + printf("shield-purge " PAM_SHIELD_VERSION "\n" + "usage: %s \n" + "options:\n" + " -h, --help Display this information\n" + " -c, --conf=file Specify config file (default: " DEFAULT_CONFFILE + ")\n" + " -d, --debug Verbose output for debugging purposes\n" + " -n, --dry-run Do not perform any updates\n" + " -l, --list List all database entries\n" + " -f, --force Delete all entries, even if unexpired\n" + " -r, --remove=ip Remove IP from database\n" + " -s, --sync Trigger sync for active records\n" + " (rebuild/verify firewall rules)\n", + basename(progname)); + + printf( + "\n" + "This program is part of the PAM-shield package.\n" + "PAM-shield comes with ABSOLUTELY NO WARRANTY. This is free software, " + "and you\n" + "are welcome to redistribute it under certain conditions. See the GNU\n" + "General Public Licence for details.\n" + "\n" + "Copyright (C) 2007-2011 by Walter de Jong \n" + "Copyright 2010 Jonathan Niehof \n"); + exit(1); } static void get_options(int argc, char **argv) { -int opt; -struct option long_options[] = { - { "help", 0, NULL, 'h' }, - { "debug", 0, NULL, 'd' }, - { "conf", 1, NULL, 'c' }, - { "dry-run", 0, NULL, 'n' }, - { "list", 0, NULL, 'l' }, - { "force", 0, NULL, 'f' }, - { "remove", 1, NULL, 'r' }, - { "sync", 0, NULL, 's' }, - { NULL, 0, NULL, 0 }, -}; - - while((opt = getopt_long(argc, argv, "hdc:nlfr:s", long_options, NULL)) != -1) { - switch(opt) { - case 'h': - case '?': - usage(argv[0]); - - case 'd': - options |= OPT_DEBUG; - logmsg(LOG_DEBUG, "logging debug info"); - break; - - case 'c': - if (optarg == NULL || !*optarg) { - logmsg(LOG_ERR, "missing filename"); - exit(1); - } - if ((conffile = strdup(optarg)) == NULL) { - logmsg(LOG_ERR, "out of memory"); - exit(-1); - } - break; - - case 'n': - options |= OPT_DRYRUN; - logmsg(LOG_DEBUG, "performing dry-run"); - break; - - case 'l': - options |= OPT_LISTDB; - logmsg(LOG_DEBUG, "list database"); - break; - - case 'f': - options |= OPT_FORCE; - logmsg(LOG_DEBUG, "force purge"); - break; - - case 'r': - options |= OPT_REMOVEIP; - if (optarg == NULL || !*optarg) { - logmsg(LOG_ERR, "missing ip"); - exit(1); - } - if ((removeip = strdup(optarg)) == NULL) { - logmsg(LOG_ERR, "out of memory"); - exit(-1); - } - break; - - case 's': - options |= OPT_SYNC; - logmsg(LOG_DEBUG, "sync"); - break; - - default: - logmsg(LOG_ERR, "bad command line option"); - exit(1); - } - } + int opt; + struct option long_options[] = { + {"help", 0, NULL, 'h'}, {"debug", 0, NULL, 'd'}, + {"conf", 1, NULL, 'c'}, {"dry-run", 0, NULL, 'n'}, + {"list", 0, NULL, 'l'}, {"force", 0, NULL, 'f'}, + {"remove", 1, NULL, 'r'}, {"sync", 0, NULL, 's'}, + {NULL, 0, NULL, 0}, + }; + + while ((opt = getopt_long(argc, argv, "hdc:nlfr:s", long_options, NULL)) != + -1) { + switch (opt) { + case 'h': + case '?': + usage(argv[0]); + + case 'd': + options |= OPT_DEBUG; + logmsg(LOG_DEBUG, "logging debug info"); + break; + + case 'c': + if (optarg == NULL || !*optarg) { + logmsg(LOG_ERR, "missing filename"); + exit(1); + } + if ((conffile = strdup(optarg)) == NULL) { + logmsg(LOG_ERR, "out of memory"); + exit(-1); + } + break; + + case 'n': + options |= OPT_DRYRUN; + logmsg(LOG_DEBUG, "performing dry-run"); + break; + + case 'l': + options |= OPT_LISTDB; + logmsg(LOG_DEBUG, "list database"); + break; + + case 'f': + options |= OPT_FORCE; + logmsg(LOG_DEBUG, "force purge"); + break; + + case 'r': + options |= OPT_REMOVEIP; + if (optarg == NULL || !*optarg) { + logmsg(LOG_ERR, "missing ip"); + exit(1); + } + if ((removeip = strdup(optarg)) == NULL) { + logmsg(LOG_ERR, "out of memory"); + exit(-1); + } + break; + + case 's': + options |= OPT_SYNC; + logmsg(LOG_DEBUG, "sync"); + break; + + default: + logmsg(LOG_ERR, "bad command line option"); + exit(1); + } + } } -/* - lists one record from the DB -*/ +/* lists one record from the DB */ static void print_record(_pam_shield_db_rec_t *record) { -char ipbuf[INET6_ADDRSTRLEN]; -unsigned int i; -char * time = ctime(&record->trigger_active); - - time[strlen(time)-1] = '\0'; - - print_ip(record, ipbuf, INET6_ADDRSTRLEN); - - printf("\n {\n" - " \"ip\": \"%s\",\n" - " \"max_entries\": %u,\n" - " \"count\": %u,\n" - " \"trigger_active\": \"%s\",\n" - " \"timestamps\": [\n", - ipbuf, record->max_entries, record->count, - (record->trigger_active > 0) ? time : "" - ); - for(i = 0; i < record->max_entries; i++) - if (record->timestamps[i] > 0) { - time = ctime(&record->timestamps[i]); - time[strlen(time)-1] = '\0'; - printf(" \"%s\"%s\n", time, (record->timestamps[(i+1)] > 0) ? "," : ""); - } - - printf(" ]\n }"); + char ipbuf[INET6_ADDRSTRLEN]; + unsigned int i; + char *time = ctime(&record->trigger_active); + + time[strlen(time) - 1] = '\0'; + + print_ip(record, ipbuf, INET6_ADDRSTRLEN); + + printf("\n {\n" + " \"ip\": \"%s\",\n" + " \"max_entries\": %u,\n" + " \"count\": %u,\n" + " \"trigger_active\": \"%s\",\n" + " \"timestamps\": [\n", + ipbuf, record->max_entries, record->count, + (record->trigger_active > 0) ? time : ""); + for (i = 0; i < record->max_entries; i++) + if (record->timestamps[i] > 0) { + time = ctime(&record->timestamps[i]); + time[strlen(time) - 1] = '\0'; + printf(" \"%s\"%s\n", time, + (record->timestamps[(i + 1)] > 0) ? "," : ""); + } + + printf(" ]\n }"); } /* - list database entries - this is also mostly meant for debugging and looking at what's in the DB + list database entries + this is also mostly meant for debugging and looking at what's in the DB */ static void list_db(void) { -_pam_shield_db_rec_t *record; -datum key, next_key, data; - - key = gdbm_firstkey(dbf); - - if (key.dptr == NULL) { - printf("database is empty\n"); - return; - } - - printf("{\n \"db\": ["); - - while(key.dptr != NULL) { - data = gdbm_fetch(dbf, key); - - if (data.dptr != NULL) { - record = (_pam_shield_db_rec_t *)data.dptr; - print_record(record); - } - next_key = gdbm_nextkey(dbf, key); - free(key.dptr); - key = next_key; - if (data.dptr != NULL && key.dptr != NULL) { - printf(","); - } - } - - printf("\n ]\n}\n"); + _pam_shield_db_rec_t *record; + datum key, next_key, data; + + key = gdbm_firstkey(dbf); + + if (key.dptr == NULL) { + printf("database is empty\n"); + return; + } + + printf("{\n \"db\": ["); + + while (key.dptr != NULL) { + data = gdbm_fetch(dbf, key); + + if (data.dptr != NULL) { + record = (_pam_shield_db_rec_t *)data.dptr; + print_record(record); + } + next_key = gdbm_nextkey(dbf, key); + free(key.dptr); + key = next_key; + if (data.dptr != NULL && key.dptr != NULL) { + printf(","); + } + } + + printf("\n ]\n}\n"); } -/* - expire old entries from the database -*/ +/* expire old entries from the database */ static void purge_db(void) { -_pam_shield_db_rec_t *record; -datum key, next_key, data; -int deleted=0; /*If any key deleted, order changes; must revisit all keys*/ - - key = gdbm_firstkey(dbf); - - while(key.dptr != NULL) { - data = gdbm_fetch(dbf, key); - next_key = gdbm_nextkey(dbf, key); - - if (options & OPT_FORCE) { - logmsg(LOG_DEBUG, "force-expiring entry"); - if (!(options & OPT_DRYRUN)) { - gdbm_delete(dbf, key); - deleted=1; - } - } - else if (data.dptr == NULL) { - logmsg(LOG_DEBUG, "cleaning up empty key"); - if (!(options & OPT_DRYRUN)) { - gdbm_delete(dbf, key); - deleted=1; - } - } else { - record = (_pam_shield_db_rec_t *)data.dptr; - -/* store any changes */ - if (expire_record(record)) { - if (!record->count && !record->trigger_active) { - logmsg(LOG_DEBUG, "expiring entry"); - if (!(options & OPT_DRYRUN)) { - gdbm_delete(dbf, key); - deleted=1; - } - } else { - logmsg(LOG_DEBUG, "storing updated entry"); - if (!(options & OPT_DRYRUN)) - gdbm_store(dbf, key, data, GDBM_REPLACE); - } - } - free(data.dptr); - } - free(key.dptr); - key = next_key; - if (deleted && !key.dptr) { - deleted=0; - key = gdbm_firstkey(dbf); - } - } + _pam_shield_db_rec_t *record; + datum key, next_key, data; + int deleted = 0; /*If any key deleted, order changes; must revisit all keys*/ + + key = gdbm_firstkey(dbf); + + while (key.dptr != NULL) { + data = gdbm_fetch(dbf, key); + next_key = gdbm_nextkey(dbf, key); + + if (options & OPT_FORCE) { + logmsg(LOG_DEBUG, "force-expiring entry"); + if (!(options & OPT_DRYRUN)) { + gdbm_delete(dbf, key); + deleted = 1; + } + } else if (data.dptr == NULL) { + logmsg(LOG_DEBUG, "cleaning up empty key"); + if (!(options & OPT_DRYRUN)) { + gdbm_delete(dbf, key); + deleted = 1; + } + } else { + record = (_pam_shield_db_rec_t *)data.dptr; + + /* store any changes */ + if (expire_record(record)) { + if (!record->count && !record->trigger_active) { + logmsg(LOG_DEBUG, "expiring entry"); + if (!(options & OPT_DRYRUN)) { + gdbm_delete(dbf, key); + deleted = 1; + } + } else { + logmsg(LOG_DEBUG, "storing updated entry"); + if (!(options & OPT_DRYRUN)) + gdbm_store(dbf, key, data, GDBM_REPLACE); + } + } + free(data.dptr); + } + free(key.dptr); + key = next_key; + if (deleted && !key.dptr) { + deleted = 0; + key = gdbm_firstkey(dbf); + } + } } -/* - remove ip from the database -*/ +/* remove ip from the database */ static int remove_ip(void) { -_pam_shield_db_rec_t *record; -datum key, next_key, data; -int deleted=0; /*If any key deleted, order changes; must revisit all keys*/ -char ipbuf[INET6_ADDRSTRLEN]; - - key = gdbm_firstkey(dbf); - - while(key.dptr != NULL) { - data = gdbm_fetch(dbf, key); - next_key = gdbm_nextkey(dbf, key); - - if (data.dptr == NULL) { - logmsg(LOG_DEBUG, "cleaning up empty key"); - if (!(options & OPT_DRYRUN)) { - gdbm_delete(dbf, key); - deleted=1; - } - } else { - record = (_pam_shield_db_rec_t *)data.dptr; - - print_ip(record, ipbuf, INET6_ADDRSTRLEN); - if (!strcmp(removeip, ipbuf)) { - logmsg(LOG_DEBUG, "remove entry: %s", ipbuf); - deleted=1; - if (!(options & OPT_DRYRUN)) { - record->trigger_active = (time_t)0L; - run_trigger("del", record); - gdbm_delete(dbf, key); - } - } - free(data.dptr); - } - free(key.dptr); - key = next_key; - if (deleted && !key.dptr) { - if (!(options & OPT_DRYRUN)) { - key = gdbm_firstkey(dbf); - } - return 0; - } - } - - logmsg(LOG_ERR, "not found: %s", removeip); - return 1; + _pam_shield_db_rec_t *record; + datum key, next_key, data; + int deleted = 0; /*If any key deleted, order changes; must revisit all keys*/ + char ipbuf[INET6_ADDRSTRLEN]; + + key = gdbm_firstkey(dbf); + + while (key.dptr != NULL) { + data = gdbm_fetch(dbf, key); + next_key = gdbm_nextkey(dbf, key); + + if (data.dptr == NULL) { + logmsg(LOG_DEBUG, "cleaning up empty key"); + if (!(options & OPT_DRYRUN)) { + gdbm_delete(dbf, key); + deleted = 1; + } + } else { + record = (_pam_shield_db_rec_t *)data.dptr; + + print_ip(record, ipbuf, INET6_ADDRSTRLEN); + if (!strcmp(removeip, ipbuf)) { + logmsg(LOG_DEBUG, "remove entry: %s", ipbuf); + deleted = 1; + if (!(options & OPT_DRYRUN)) { + record->trigger_active = (time_t)0L; + run_trigger("del", record); + gdbm_delete(dbf, key); + } + } + free(data.dptr); + } + free(key.dptr); + key = next_key; + if (deleted && !key.dptr) { + if (!(options & OPT_DRYRUN)) { + key = gdbm_firstkey(dbf); + } + return 0; + } + } + + logmsg(LOG_ERR, "not found: %s", removeip); + return 1; } int main(int argc, char **argv) { - int retval = 0; - init_module(); - - read_config(); - get_options(argc, argv); - - this_time = time(NULL); - - if ((dbf = gdbm_open(dbfile, 512, GDBM_WRITER, (mode_t)0600, fatal_func)) == NULL) { - logmsg(LOG_ERR, "failed to open db '%s' : %s", dbfile, gdbm_strerror(gdbm_errno)); - return -1; - } - if (options & OPT_LISTDB) - list_db(); - else if (options & OPT_REMOVEIP) - retval = remove_ip(); - else - purge_db(); - - gdbm_close(dbf); - - deinit_module(); - return retval; + int retval = 0; + init_module(); + + read_config(); + get_options(argc, argv); + + this_time = time(NULL); + + if ((dbf = gdbm_open(dbfile, 512, GDBM_WRITER, (mode_t)0600, fatal_func)) == + NULL) { + logmsg(LOG_ERR, "failed to open db '%s' : %s", dbfile, + gdbm_strerror(gdbm_errno)); + return -1; + } + if (options & OPT_LISTDB) + list_db(); + else if (options & OPT_REMOVEIP) + retval = remove_ip(); + else + purge_db(); + + gdbm_close(dbf); + + deinit_module(); + return retval; } - -/* EOB */