├── .gitignore ├── COPYING ├── CREDITS ├── Changelog ├── INSTALL ├── Makefile.am ├── README ├── autogen.sh ├── configure.ac ├── cron.daily └── pam_shield ├── man ├── shield-purge.8 ├── shield-trigger-iptables.8 ├── shield-trigger-ufw.8 ├── shield-trigger.8 └── shield.conf.5 ├── pam-configs └── pam_shield ├── pam_shield.c ├── pam_shield.h ├── pam_shield_lib.c ├── pam_shield_lib.h ├── scripts ├── shield-trigger ├── shield-trigger-iptables └── shield-trigger-ufw ├── shield.conf └── shield_purge.c /.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | .libs 3 | aclocal.m4 4 | autom4te.cache/ 5 | build-aux/ 6 | config.* 7 | configure 8 | Makefile 9 | Makefile.in 10 | libtool 11 | stamp-h1 12 | *.lo 13 | *.la 14 | *.o 15 | shield-purge* 16 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 19yy 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) 19yy name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | Credit goes to: 2 | 3 | Walter de Jong for creating pam_shield and releasing it under the GNU GPL 4 | Jon Niehof for submitting patches and maintaining the project from 2010-2022 5 | Dag Wieers for reporting bugs 6 | Jan Engelhardt for submitting a patch that makes pam_shield much better 7 | Carl Thompson for man pages, patches to shield-trigger-iptables 8 | Jeffrey Clark for patches and maintaining the project from 2022- 9 | -------------------------------------------------------------------------------- /Changelog: -------------------------------------------------------------------------------- 1 | 30 Aug 2022: 2 | - release 0.9.7 3 | - fix gdbm_option compiler warnings 4 | - format database list output as json 5 | - parse config file first to support runtime overrides 6 | - new optional argument to remove single IP from database 7 | - new primary maintainer Jeffrey Clark 8 | 9 | 3 Jun 2012: 10 | - release 0.9.6 11 | - Change build system to autoconf 12 | - Add man pages (Thanks to Carl Thompson) 13 | - Make shield-trigger-iptables more flexible (Thanks Carl Thompson) 14 | - Make cron job behave if shield-purge disappears from under it 15 | - use iptables to block bad IPs on all interfaces, not just eth0 16 | - add optional trigger script to use ufw instead of iptables directly 17 | - new primary maintainer Jonathan Niehof 18 | 19 | 12 Jan 2011: 20 | - release 0.9.5 21 | Bugfix by Heino Gutschmidt: 22 | - shield_purge would delete entries that were still active, 23 | causing the delete-rule trigger never to be executed 24 | 25 | 4 Sept 2010: 26 | - release 0.9.4 27 | 28 | 1 Sept 2010: 29 | - bugfix: treat missing DNS parameters properly 30 | - bugfix: for race of blocking same IP multiple times 31 | - bugfix: check for exit code of run_trigger 32 | - read_config() may return an (syntax) error, but continue anyway 33 | and try to make the best of it 34 | - use ip blockhole routing 35 | - added lots of explanatory text to INSTALL 36 | 37 | 20 April 2010: release 0.9.3 38 | Bugfixes by Jonathan Niehof: 39 | - fix memory leak in purger 40 | - fix bug in the code where the purger deletes multiple entries 41 | - when opening the database fails, retry a couple of times 42 | 43 | New feature by Jonathan Niehof: 44 | - added option --force to purger to delete all entries 45 | 46 | 47 | 2007: release 0.9.2 48 | Version 0.9.2 was the first public release 49 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | pam_shield by Walter de Jong and 2 | Jonathan Niehof 3 | Copyright 2007-2012 4 | 5 | pam_shield COMES WITH NO WARRANTY. synctool IS FREE SOFTWARE. 6 | pam_shield is distributed under terms described in the GNU General Public 7 | License. 8 | 9 | See the README file for some information about pam_shield. 10 | 11 | 12 | Read the README and this file carefully. Failure to setup pam_shield 13 | correctly, will render it useless. 14 | 15 | 16 | Building pam_shield 17 | ------------------- 18 | 19 | Pre-reqs on a Debian or Ubuntu-like system: 20 | libpam0g-dev 21 | libgdbm-dev 22 | 23 | Pre-reqs on a RedHat or Fedora-like system: 24 | pam-devel 25 | gdbm-devel 26 | 27 | 28 | pam_shield contains a standard GNU configure script; basic setup: 29 | ./configure 30 | make 31 | make install 32 | 33 | If using a git checkout instead of a tarball, use: 34 | autoreconf -i 35 | first. 36 | 37 | pam_shield consists of: 38 | - one PAM module: /lib/security/pam_shield.so 39 | or /lib64/security/pam_shield.so on 64-bit systems 40 | - one binary: /usr/sbin/shield-purge 41 | - three scripts: /usr/sbin/shield-trigger 42 | /usr/sbin/shield-trigger-iptables 43 | /usr/sbin/shield-trigger-ufw 44 | - one cron script: /etc/cron.daily/pam_shield 45 | - one config file: /etc/security/shield.conf 46 | - a gdbm database: /var/lib/pam_shield/db 47 | - PAM config: /usr/share/pam-config/pam_shield 48 | 49 | Type 'make' to build the software. 50 | Do a 'make install' as root to install the software. Note that, by default, 51 | it will install into /usr rather than /usr/local (use the --prefix option 52 | to configure to change this.) 53 | You may do 'make uninstall' to remove the software. 54 | 55 | 56 | Configuring pam_shield 57 | ---------------------- 58 | Edit the config file /etc/security/shield.conf and make sure all paths are 59 | correct. Also, create an 'allow' line for your local networks. (If you do not 60 | list your local networks, a local user may be able to lock you out (DoS 61 | attack)). 62 | 63 | pam_shield uses a shell script named shield-trigger to block and unblock 64 | sites. It will use null-routing to do so. 65 | A script named shield-trigger-iptables is provided too, which uses iptables 66 | to block sites, but beware that it may need system-specific customization to 67 | work correctly. 68 | A third option is shield-trigger-ufw, which uses (and requires) the 69 | Uncomplicated FireWall ufw, usually associated with Ubuntu. 70 | 71 | 72 | Configuring PAM 73 | --------------- 74 | The PAM config files usually reside under /etc/pam.d/ 75 | Note that exact content of the PAM config files tends to differ between 76 | distributions. If your distribution supports pam-auth-update, the installed 77 | /usr/share/pam-config/pam_shield file should be enough for pam-auth-update 78 | to recognize and activate PAM (roughly in configuration 1, below). 79 | 80 | pam_shield.so must be included in the PAM configuration file for any 81 | services where it might be useful. In general, /etc/pam.d/sshd. 82 | 83 | Different configurations are possible, depending on your taste. 84 | There is a certain "play" between the config of your PAM stack and what 85 | you put into the shield.conf file, so pay attention. 86 | Here are some examples of configurations: 87 | 88 | 1. Block many failing login attempts 89 | 90 | auth sufficient pam_unix.so 91 | auth required pam_shield.so 92 | 93 | Explanation: good logins will succeed by pam_unix, and pam_shield will 94 | not even be invoked. Bad logins fall through and are caught by pam_shield, 95 | which may activate null-routing for the failing IPaddress. 96 | The shield.conf option 'block unknown-users' will do what it says, and 97 | 'block all-users' will activate the IP block even for known users _if_ 98 | there are many failed logins for that user. 99 | The options 'allow_missing_dns' and 'allow_missing_reverse' do not do 100 | anything useful in this configuration. 101 | 102 | 2. Block suspicious looking folks 103 | 104 | auth requisite pam_shield.so 105 | auth include system-auth 106 | 107 | Explanation: Every login attempt will be screened by pam_shield. If all 108 | looks well, the system may continue to authenticate the user as usual. 109 | If all is not well, pam_shield will abort the login immediately. 110 | The shield.conf options 'allow_missing_dns' and 'allow_missing_reverse' 111 | play an important role here. Missing DNS entries are suspicious because 112 | hackers use this to try stay hidden. However, pam_shield will still allow 113 | logins for _known_ users if you set 'block unknown-users'. 114 | If you enable 'block all-users', it is easy to lock yourself out from a 115 | machine that has no DNS entry. You will also block legitimate users that 116 | do too many logins in a short period of time. For example, when a user 117 | does this: 118 | for file in *; do scp $file remotemachine: ; done 119 | 120 | NB. If there are other "required" modules in the stack that must run no 121 | matter what, you may change "requisite" to "required". 122 | 123 | 3. Block login attempts no matter what 124 | 125 | auth optional pam_shield.so 126 | auth include common-auth 127 | 128 | Explanation: Let pam_shield do its job, and then do the usual stuff to 129 | authenticate the user. 130 | This is not an optimal configuration. "optional" means that pam_shield can 131 | not abort the login attempt (unless it is already null-routing the IP). 132 | The shield.conf parameters 'allow_missing_dns' and 'allow_missing_reverse' 133 | have no useful meaning here, they don't do anything in this configuration. 134 | 135 | So, if you want to be nice to the users of your system, go for config #1. 136 | If you are more paranoid, go for config #2. 137 | Whatever you do, make sure pam_shield is not the only auth module that 138 | is listed for the service. pam_shield does not do any authentication by 139 | itself and trying to run it as standalone auth module will leave your system 140 | wide open. 141 | 142 | 143 | Testing pam_shield 144 | ------------------ 145 | When testing pam_shield, be wary that you are going to lock yourself out. 146 | The safest way to test it, is to have console access with a root prompt open 147 | just in case things go wrong and you can not access your machine anymore. 148 | 149 | Edit /etc/security/shield.conf and set max_conns to a small value 150 | like 3 or so. Set the interval and the retention period both to 60 seconds. 151 | Set debug on. 152 | Now simulate an attack on your system by doing 4 quick logins to a 153 | non-existing user from a remote host. If you check the syslog (often 154 | /var/log/secure or /var/log/auth.log) you will see that pam_shield 155 | is triggering and later, expiring. To see what hosts are blocked, 156 | use any of the following commands (whichever you prefer): 157 | 158 | netstat -r 159 | route 160 | ip route show 161 | 162 | If you check the debug log (often /var/log/debug) you will see more 163 | debug info from pam_shield. 164 | 165 | pam_shield should now be completely installed and working. 166 | Edit /etc/security/shield.conf and enter sensible values for max_conns, 167 | interval and retention. 168 | It is wise to periodically check whether pam_shield is still operating 169 | correctly. 170 | 171 | 172 | EOB 173 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | AUTOMAKE_OPTIONS = foreign subdir-objects 2 | moduledir = @PAM_MODDIR@ 3 | module_LTLIBRARIES = pam_shield.la 4 | sbin_PROGRAMS = shield-purge 5 | dist_sbin_SCRIPTS = scripts/* 6 | securitydir = ${sysconfdir}/security 7 | dist_security_DATA = shield.conf 8 | pamcfgdir = ${datarootdir}/pam-configs 9 | dist_pamcfg_DATA = pam-configs/pam_shield 10 | crondir = ${sysconfdir}/cron.daily 11 | dist_cron_SCRIPTS = cron.daily/pam_shield 12 | dist_man_MANS = man/shield.conf.5 man/shield-purge.8 man/shield-trigger.8 man/shield-trigger-iptables.8 man/shield-trigger-ufw.8 13 | EXTRA_DIST = CREDITS Changelog autogen.sh 14 | 15 | AM_CFLAGS = -D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 \ 16 | -D_REENTRANT -Wall -Wmissing-declarations -Wmissing-prototypes \ 17 | -Wredundant-decls -Wshadow -Wstrict-prototypes -Winline -pipe 18 | 19 | pam_shield_la_LDFLAGS = -module -avoid-version 20 | pam_shield_la_LIBADD = -lgdbm -lpam 21 | pam_shield_la_SOURCES = pam_shield.c pam_shield_lib.c \ 22 | pam_shield.h pam_shield_lib.h 23 | shield_purge_LDFLAGS = -lgdbm 24 | shield_purge_SOURCES = shield_purge.c pam_shield_lib.c pam_shield_lib.h 25 | shield_purge_CFLAGS = $(AM_CFLAGS) 26 | 27 | install-exec-hook: 28 | rm -f "${DESTDIR}${moduledir}/pam_shield.la"; 29 | ${INSTALL} -dm0755 "${DESTDIR}${sbindir}"; 30 | ${INSTALL} -dm0755 "${DESTDIR}${localstatedir}/lib/pam_shield"; 31 | 32 | #The slib dir isn't considered exec, so make sure this really runs 33 | #after libtool dumps the la 34 | install-data-hook: 35 | rm -f "${DESTDIR}${moduledir}/pam_shield.la"; 36 | ${INSTALL} -dm0755 "${DESTDIR}${sbindir}"; 37 | 38 | #Remove the .so by hand; since we remove the .la, libtool won't get the .so 39 | #also clean up the database 40 | uninstall-hook: 41 | rm -f "${DESTDIR}${moduledir}/pam_shield.so"; 42 | rm -f "${DESTDIR}${localstatedir}/lib/pam_shield/db" 43 | rmdir --ignore-fail-on-non-empty "${DESTDIR}${localstatedir}/lib/pam_shield"; 44 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Please see the current maintainer's repository at 2 | https://github.com/h0tw1r3/pam_shield 3 | 4 | pam_shield by Walter de Jong and Jonathan Niehof 5 | , copyright 2007-2012. 6 | 7 | pam_shield COMES WITH NO WARRANTY. pam_shield IS FREE SOFTWARE. 8 | pam_shield is distributed under terms described in the GNU General Public 9 | License. 10 | 11 | See the INSTALL file for information on how to install pam_shield. 12 | 13 | 14 | pam_shield is a PAM module that uses iptables or null-routing to lock out 15 | script kiddies that probe your computer for open logins and/or easy guessable 16 | passwords. pam_shield is meant as an aid to protect public computers on the 17 | open internet. 18 | 19 | Everybody knows it is unwise to leave computers largely unprotected 20 | connected to the internet. However, there are cases in which this is still 21 | common practice. For exampe, academic sites with hundreds of users often 22 | have a policy of allowing logins from over the world. They are under 23 | constant attack by "kiddies" trying to break in to the system by 24 | password guessing. pam_shield aims to detect and block these "kiddies". 25 | 26 | 27 | (Not So) Random Remarks 28 | ----------------------- 29 | * pam_shield is a PAM (Pluggable Authentication Module). When used 30 | inappropriately, your system might be at risk. Use with care. 31 | 32 | * pam_shield blocks IPs. This means that when it blocks a multi-user 33 | system, it blocks all users from that system. 34 | For example, it may happen that an attacker is performing his 35 | attack from a university system, from which many students connect. 36 | By blocking the attacker, all students get blocked as well. This should 37 | be no problem, but you should be aware that this can happen. 38 | 39 | * pam_shield works by counting login attempts coming from a remote host 40 | during a period of time. If there are too many attempts, it triggers 41 | and blocks the remote host. 42 | 43 | * To block and unblock IPs, pam_shield runs the shield-trigger script. 44 | By default, it uses null-routing to block hosts. 45 | A script for using iptables is also provided, but you should customize 46 | this script to fit your situation if you decide to use it. 47 | 48 | * similar tools are daemon_shield and BlockHosts, which work by scanning 49 | system logs. pam_shield works with PAM and a gdbm database. 50 | 51 | * pam_shield is by no means THE solution for all your security problems. 52 | Always remain on guard. 53 | 54 | 55 | See Also 56 | -------- 57 | * iptables homepage: http://www.netfilter.org/ 58 | 59 | * Linux PAM documentation: 60 | http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/Linux-PAM_SAG.html 61 | 62 | * fail2ban: http://www.fail2ban.org/ 63 | 64 | * daemon_shield: https://sourceforge.net/projects/daemonshield/ 65 | 66 | * BlockHosts: http://www.aczoom.com/cms/blockhosts/ 67 | 68 | 69 | History 70 | ------- 71 | 2007 Walter de Jong created pam_shield. 72 | 2010 Walter and Jonathan Niehof started co-maintaining. 73 | 2012 Jonathan became the primary maintainer. 74 | 2022 Jeffrey Clark became the primary maintainer. 75 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export AUTOMAKE=/usr/bin/automake-1.11 4 | export ACLOCAL=/usr/bin/aclocal-1.11 5 | 6 | exec autoreconf -fi; 7 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | 2 | AC_INIT(pam-shield, 0.9.7) 3 | AC_CONFIG_HEADERS(config.h) 4 | AC_CONFIG_AUX_DIR([build-aux]) 5 | AM_INIT_AUTOMAKE 6 | 7 | AC_PROG_CC 8 | AC_PROG_INSTALL 9 | AC_DISABLE_STATIC 10 | AM_PROG_LIBTOOL 11 | AC_PROG_LN_S 12 | 13 | AC_PREFIX_DEFAULT(/usr) 14 | AC_ARG_WITH([slibdir], AS_HELP_STRING([--with-slibdir=PATH], 15 | [Path to the super lib directory [/lib]]), 16 | [slibdir="$withval"], [slibdir="/lib"]) 17 | AC_SUBST(slibdir) 18 | 19 | AC_CHECK_HEADER(gdbm.h, [], 20 | [AC_MSG_ERROR([You need to have gdbm-devel installed])]) 21 | AC_CHECK_HEADER(security/pam_modules.h,[have_pamheader="yes"],) 22 | # Mac OS X 10.3 puts PAM headers in /usr/include/pam. 23 | AC_CHECK_HEADER(pam/pam_modules.h,[have_pamheader="yes"],) 24 | if test x"$have_pamheader" != x"yes"; then 25 | AC_MSG_ERROR(You are missing PAM headers) 26 | fi 27 | 28 | case "$host" in 29 | (*-*-linux*) 30 | PAM_MODDIR="\$(slibdir)/security"; 31 | ;; 32 | (*-*-darwin*) 33 | PAM_MODDIR="/usr/lib/pam"; 34 | ;; 35 | (*) 36 | PAM_MODDIR="/usr/lib"; 37 | ;; 38 | esac; 39 | AC_SUBST(PAM_MODDIR) 40 | 41 | #This is a bad hack to use /etc instead of /usr/etc if prefix not specified 42 | #After https://fedorahosted.org/pipermail/netcf-devel/2011-May/000540.html 43 | #Note this makes documentation for --sysconfdir lie. 44 | if test "$prefix" = "NONE" && test "$sysconfdir" = '${prefix}/etc' ; then 45 | sysconfdir='/etc' 46 | fi 47 | if test "$prefix" = "NONE" && test "$localstatedir" = '${prefix}/var' ; then 48 | localstatedir='/var' 49 | fi 50 | 51 | #conniptions to get real paths into the C without affecting makefiles 52 | ac_save_prefix="$prefix" 53 | ac_save_exec_prefix="$exec_prefix" 54 | test "x$prefix" = xNONE && prefix=$ac_default_prefix 55 | test "x$exec_prefix" = xNONE && exec_prefix=$prefix 56 | REAL_ETC=`eval echo $sysconfdir` 57 | AC_DEFINE_UNQUOTED([DEFAULT_CONFFILE], ["$REAL_ETC/security/shield.conf"], 58 | [Location of configuration file]) 59 | REAL_VAR=`eval echo $localstatedir` 60 | AC_DEFINE_UNQUOTED([DEFAULT_DBFILE], ["$REAL_VAR/lib/pam_shield/db"], 61 | [Location of database]) 62 | REAL_SBIN=`eval echo $sbindir` 63 | AC_DEFINE_UNQUOTED([DEFAULT_TRIGGER_CMD], ["$REAL_SBIN/shield-trigger"], 64 | [Command to block/unblock IP]) 65 | AC_DEFINE_UNQUOTED([PAM_SHIELD_VERSION], ["$VERSION"], 66 | [Version of pam_shield]) 67 | prefix="$ac_save_prefix" 68 | exec_prefix="$ac_save_exec_prefix" 69 | 70 | AC_OUTPUT(Makefile) 71 | -------------------------------------------------------------------------------- /cron.daily/pam_shield: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | if [ -x /usr/sbin/shield-purge ]; then /usr/sbin/shield-purge -c /etc/security/shield.conf 4 | fi 5 | 6 | -------------------------------------------------------------------------------- /man/shield-purge.8: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Generated by Carl Thompson 3 | .\" 4 | .\" This is free documentation; you can redistribute it and/or 5 | .\" modify it under the terms of the GNU General Public License as 6 | .\" published by the Free Software Foundation; either version 2 of 7 | .\" the License, or (at your option) any later version. 8 | .\" 9 | .\" The GNU General Public License's references to "object code" 10 | .\" and "executables" are to be interpreted as the output of any 11 | .\" document formatting or typesetting system, including 12 | .\" intermediate and printed output. 13 | .\" 14 | .\" This manual is distributed in the hope that it will be useful, 15 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | .\" GNU General Public License for more details. 18 | .\" 19 | .\" You should have received a copy of the GNU General Public 20 | .\" License along with this manual; if not, write to the Free 21 | .\" Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 22 | .\" USA. 23 | .\" 24 | .TH shield-purge 8 "30 Aug 2022" "pam_shield 0.9.7" 25 | .SH NAME 26 | shield-purge \- manage hosts rules for the pam_shield package. 27 | .SH SYNOPSIS 28 | .BI "shield-purge [-hdnlf] [-c config file]" 29 | .SH DESCRIPTION 30 | shield-purge deletes old entries from the database. A binary for the 31 | pam-shield module, shield_purge is not intended to be invoked manualy. 32 | It is a halper program for job in crontab. 33 | 34 | .B shield-purge 35 | clears records from the database that are older than the time specified by 36 | .B retention 37 | in 38 | .BR shield.conf (5). 39 | 40 | This program is part of the PAM-shield package. 41 | PAM-shield comes with ABSOLUTELY NO WARRANTY. This is free software, and you 42 | are welcome to redistribute it under certain conditions. See the GNU 43 | General Public Licence for details. 44 | .SH OPTIONS 45 | .LP 46 | .IP \fB\-h,\ \-\-help\fR 47 | .br 48 | Display this information 49 | 50 | .IP \fB\-c,\ \-\-conf=file\fR 51 | .br 52 | Specify config file (default: /etc/security/shield.conf) 53 | 54 | .IP \fB\-d,\ \-\-debug\fR 55 | .br 56 | Verbose output for debugging purposes 57 | 58 | .IP \fB\-n,\ \-\-dry\-run\fR 59 | .br 60 | Do not perform any updates 61 | 62 | .IP \fB\-l,\ \-\-list\fR 63 | .br 64 | List all database entries 65 | 66 | .IP \fB\-f,\ \-\-force\fR 67 | .br 68 | Delete all entries, even if unexpired 69 | 70 | .SH AUTHOR 71 | .br 72 | Walter de Jong 73 | 74 | 75 | .SH PROJECT LOCATION 76 | https://github.com/jtniehof/pam_shield 77 | 78 | .SH COPYRIGHT 79 | Copyright (C) 2007-2011 by Walter de Jong 80 | .br 81 | Copyright 2010 Jonathan Niehof 82 | -------------------------------------------------------------------------------- /man/shield-trigger-iptables.8: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Generated by Carl Thompson 3 | .\" 4 | .\" This is free documentation; you can redistribute it and/or 5 | .\" modify it under the terms of the GNU General Public License as 6 | .\" published by the Free Software Foundation; either version 2 of 7 | .\" the License, or (at your option) any later version. 8 | .\" 9 | .\" The GNU General Public License's references to "object code" 10 | .\" and "executables" are to be interpreted as the output of any 11 | .\" document formatting or typesetting system, including 12 | .\" intermediate and printed output. 13 | .\" 14 | .\" This manual is distributed in the hope that it will be useful, 15 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | .\" GNU General Public License for more details. 18 | .\" 19 | .\" You should have received a copy of the GNU General Public 20 | .\" License along with this manual; if not, write to the Free 21 | .\" Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 22 | .\" USA. 23 | .\" 24 | .TH shield-trigger-iptables 8 "30 Aug 2022" "pam_shield 0.9.7" 25 | .SH NAME 26 | shield-trigger-iptables \- program used with pam_shield PAM module to manage ip blocking 27 | .SH SYNOPSIS 28 | .BI "shield-trigger-iptables [add|del] " 29 | .SH DESCRIPTION 30 | This is a program that manages the ip blocks for pam_shield PAM module. 31 | shield-trigger-iptables is normally called by the pam_shield PAM module 32 | .SH AUTHOR 33 | .br 34 | Walter de Jong 35 | 36 | 37 | .SH PROJECT LOCATION 38 | https://github.com/jtniehof/pam_shield 39 | -------------------------------------------------------------------------------- /man/shield-trigger-ufw.8: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" This is free documentation; you can redistribute it and/or 3 | .\" modify it under the terms of the GNU General Public License as 4 | .\" published by the Free Software Foundation; either version 2 of 5 | .\" the License, or (at your option) any later version. 6 | .\" 7 | .\" The GNU General Public License's references to "object code" 8 | .\" and "executables" are to be interpreted as the output of any 9 | .\" document formatting or typesetting system, including 10 | .\" intermediate and printed output. 11 | .\" 12 | .\" This manual is distributed in the hope that it will be useful, 13 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | .\" GNU General Public License for more details. 16 | .\" 17 | .\" You should have received a copy of the GNU General Public 18 | .\" License along with this manual; if not, write to the Free 19 | .\" Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 20 | .\" USA. 21 | .\" 22 | .TH shield-trigger-iptables 8 "30 Aug 2022" "pam_shield 0.9.7" 23 | .SH NAME 24 | shield-trigger-ufw \- program used with pam_shield PAM module to manage ip blocking 25 | .SH SYNOPSIS 26 | .BI "shield-trigger-ufw [add|del] " 27 | .SH DESCRIPTION 28 | This is a program that manages the ip blocks for pam_shield PAM module via 29 | the "uncomplicated firewall" 30 | .BR ufw (8). 31 | shield-trigger-ufw is normally called by the pam_shield PAM module. 32 | .SH AUTHOR 33 | .br 34 | Jonathan Niehof 35 | 36 | 37 | .SH PROJECT LOCATION 38 | https://github.com/jtniehof/pam_shield 39 | -------------------------------------------------------------------------------- /man/shield-trigger.8: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Generated by Carl Thompson 3 | .\" 4 | .\" This is free documentation; you can redistribute it and/or 5 | .\" modify it under the terms of the GNU General Public License as 6 | .\" published by the Free Software Foundation; either version 2 of 7 | .\" the License, or (at your option) any later version. 8 | .\" 9 | .\" The GNU General Public License's references to "object code" 10 | .\" and "executables" are to be interpreted as the output of any 11 | .\" document formatting or typesetting system, including 12 | .\" intermediate and printed output. 13 | .\" 14 | .\" This manual is distributed in the hope that it will be useful, 15 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | .\" GNU General Public License for more details. 18 | .\" 19 | .\" You should have received a copy of the GNU General Public 20 | .\" License along with this manual; if not, write to the Free 21 | .\" Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 22 | .\" USA. 23 | .\" 24 | .TH shield-trigger 8 "30 Aug 2022" "pam_shield 0.9.7" 25 | .SH NAME 26 | shield-trigger \- program used with pam_shield PAM module to manage ip blocking 27 | .SH SYNOPSIS 28 | .BI "shield-trigger [add|del] " 29 | .SH DESCRIPTION 30 | This is a program that manages the ip blocks for pam_shield PAM module. 31 | shield-trigger is normally called by the pam_shield PAM module 32 | .SH AUTHOR 33 | .br 34 | Walter de Jong 35 | 36 | 37 | .SH PROJECT LOCATION 38 | https://github.com/jtniehof/pam_shield 39 | -------------------------------------------------------------------------------- /man/shield.conf.5: -------------------------------------------------------------------------------- 1 | .\" shield.conf - pam-shield configuration file 2 | .\" Copyright 2010-2012 Jonathan Niehof 3 | .\" 4 | .\" This program is free software; you can redistribute it and/or modify 5 | .\" it under the terms of the GNU General Public License as published by 6 | .\" the Free Software Foundation; either version 2 of the License, or 7 | .\" (at your option) any later version. 8 | .\" 9 | .\" This program is distributed in the hope that it will be useful, 10 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | .\" GNU General Public License for more details. 13 | .\" 14 | .\" You should have received a copy of the GNU General Public License 15 | .\" along with this program; if not, write to the Free Software 16 | .\" Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. 17 | .\" 18 | .TH SHIELD.CONF 5 "30 Aug 2022" "pam_shield 0.9.7" 19 | .SH NAME 20 | shield.conf \- pam_shield configuration file 21 | .SH DESCRIPTION 22 | .I /etc/security/shield.conf 23 | is the configuration file for PAM module pam_shield, which locks out 24 | remote attackers trying password guessing. 25 | 26 | 27 | .SH OPTIONS 28 | 29 | .HP 30 | .B debug 31 | [on|off] 32 | .br 33 | Log (or do not log) debugging information via 34 | .BR syslog (3). 35 | .HP 36 | .B block 37 | [all-users|unknown-users] 38 | .br 39 | Block all users, or only unknown users. Whether users are "known" is determined from 40 | .BR getpwnam (3) 41 | .HP 42 | .B allow_missing_dns 43 | [yes|no] 44 | .br 45 | If no, reject any connection that comes from a numerical IP address with no DNS 46 | name (as returned by 47 | .BR pam_get_item (3) 48 | with 49 | .I item_type 50 | set to PAM_RHOST). 51 | .HP 52 | .B allow_missing_reverse 53 | [yes|no] 54 | .br 55 | If no, reject any connection that comes from a host with no reverse DNS 56 | entry. 57 | .HP 58 | .B allow 59 | .I hostname 60 | .br 61 | Host or network to whitelist. These hosts are passed through with no checks or 62 | logging. Multiple 63 | .B allow 64 | lines are permitted. 65 | .I hostname 66 | may be IP address, hostname, network/netmask, or network in CIDR 67 | format. 68 | .HP 69 | .B db 70 | .I filename 71 | .br 72 | Database file where login attempts are stored. 73 | .HP 74 | .B trigger_cmd 75 | .I command 76 | .br 77 | Command to run to block/unblock a host. See 78 | .BR shield-trigger (8) 79 | and 80 | .BR shield-trigger-iptables (8) 81 | for two examples. 82 | .HP 83 | .B max_conns 84 | .I n 85 | .br 86 | Host will be blocked if more than 87 | .I n 88 | connection attempts from one host in 89 | .B interval 90 | time. 91 | .HP 92 | .B interval 93 | .I n 94 | .br 95 | Host blocked if more than 96 | .B max_conns 97 | attempts in 98 | .I n 99 | seconds. Instead of seconds, suffix may be used: s for seconds, m minutes, 100 | h hours, d days, w weeks, M months (30 days), y years. 101 | .HP 102 | .B retention 103 | .I n 104 | .br 105 | Record of connection attempts retained for 106 | .I n 107 | seconds. Suffixes may be used as in 108 | .B interval. 109 | Each host is checked for expiration when it attempts to connect, and the 110 | entire database is checked whenever 111 | .BR shield-purge (8) 112 | is run (by default, once a day). 113 | 114 | .SH FILES 115 | .PD 0 116 | .HP 117 | .I /etc/security/shield.conf 118 | Configuration file for 119 | .B pam-shield 120 | 121 | .SH SEE ALSO 122 | .BR shield-purge (8), 123 | .BR shield-trigger (8), 124 | .BR shield-trigger-iptables (8) 125 | 126 | .SH AUTHORS 127 | pam-shield was written by and copyright 2007 Walter de Jong \%. This manpage copyright 2010-2012 Jonathan Niehof 128 | \%. 129 | -------------------------------------------------------------------------------- /pam-configs/pam_shield: -------------------------------------------------------------------------------- 1 | Name: PAM shield: block IPs trying password guessing 2 | Default: yes 3 | Priority: 128 4 | Auth-Type: Primary 5 | Auth: 6 | optional pam_shield.so 7 | -------------------------------------------------------------------------------- /pam_shield.c: -------------------------------------------------------------------------------- 1 | /* 2 | pam_shield.c 3 | 4 | pam_shield 0.9.7 5 | Copyright (C) 2007-2012 Walter de Jong 6 | and Jonathan Niehof 7 | 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 2 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | /* 23 | pam_shield is a PAM module that uses route/iptables to lock out script kiddies 24 | that probe your machine for open logins and/or easy guessable passwords. 25 | 26 | You can run this module with 27 | 28 | auth optional pam_shield.so 29 | 30 | But just make sure it's not the only auth module you run..! 31 | This module does not do any authentication, it just monitors access. 32 | 33 | 34 | (btw, if you don't like my indentation, try setting tabsize to 4) 35 | */ 36 | 37 | #include "pam_shield.h" 38 | 39 | #define PAM_SM_AUTH 1 40 | 41 | #include 42 | 43 | #include "pam_shield_lib.h" 44 | 45 | #pragma GCC visibility push(hidden) 46 | 47 | void logmsg(int level, const char *fmt, ...) { 48 | va_list varargs; 49 | 50 | if (level == LOG_DEBUG && !(options & OPT_DEBUG)) 51 | return; 52 | 53 | #ifdef LOG_AUTHPRIV 54 | openlog("PAM-shield", LOG_PID, LOG_AUTHPRIV); 55 | #else 56 | openlog("PAM-shield", LOG_PID, LOG_AUTH); 57 | #endif 58 | 59 | va_start(varargs, fmt); 60 | vsyslog(level, fmt, varargs); 61 | va_end(varargs); 62 | 63 | closelog(); 64 | } 65 | 66 | /* 67 | Mind that argv[0] is an argument, not the name of the module 68 | */ 69 | static void get_options(int argc, char **argv) { 70 | int i; 71 | 72 | for(i = 0; i < argc; i++) { 73 | if (!strcmp(argv[i], "debug")) { 74 | options |= OPT_DEBUG; 75 | logmsg(LOG_DEBUG, "logging debug info"); 76 | continue; 77 | } 78 | if (!strcmp(argv[i], "use_first_pass")) /* Thorsten Kukuk sez all modules should accept this argument */ 79 | continue; 80 | 81 | if (!strncmp(argv[i], "conf=", 5)) { 82 | conffile = argv[i] + 5; 83 | continue; 84 | } 85 | logmsg(LOG_ERR, "unknown argument '%s', ignored", argv[i]); 86 | } 87 | } 88 | 89 | static _pam_shield_db_rec_t *new_db_record(int window_size) { 90 | _pam_shield_db_rec_t *record; 91 | int size; 92 | 93 | if (window_size <= 0) { 94 | window_size = 1; 95 | size = sizeof(_pam_shield_db_rec_t); 96 | } else 97 | size = sizeof(_pam_shield_db_rec_t) + (window_size-1) * sizeof(time_t); 98 | 99 | if ((record = (_pam_shield_db_rec_t *)malloc(size)) == NULL) { 100 | logmsg(LOG_CRIT, "new_db_record(): out of memory allocating %d bytes", size); 101 | return NULL; 102 | } 103 | memset(record, 0, size); 104 | record->max_entries = window_size; 105 | return record; 106 | } 107 | 108 | static void destroy_db_record(_pam_shield_db_rec_t *record) { 109 | if (record != NULL) 110 | free(record); 111 | } 112 | 113 | /* 114 | get remote IPs for the rhost 115 | 116 | the return value must be freed with freeaddrinfo() 117 | */ 118 | static struct addrinfo *get_addr_info(char *rhost) { 119 | struct addrinfo hints, *res; 120 | int err; 121 | 122 | memset(&hints, 0, sizeof(struct addrinfo)); 123 | hints.ai_family = PF_UNSPEC; 124 | hints.ai_socktype = SOCK_STREAM; 125 | 126 | if ((err = getaddrinfo(rhost, NULL, &hints, &res)) != 0) { 127 | logmsg(LOG_ERR, "%s: %s\n", rhost, gai_strerror(err)); 128 | return NULL; 129 | } 130 | return res; 131 | } 132 | 133 | #pragma GCC visibility pop 134 | 135 | /* 136 | the authenticate function always returns PAM_IGNORE, because this 137 | module does not really authenticate 138 | */ 139 | PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { 140 | char *user, *rhost; 141 | struct passwd *pwd; 142 | unsigned int retry_count; 143 | int suspicious_dns; 144 | 145 | if (init_module()) 146 | return PAM_IGNORE; 147 | 148 | logmsg(LOG_DEBUG, "this is version " PAM_SHIELD_VERSION); 149 | 150 | /* 151 | read_config() may fail (due to syntax errors, etc.), try to make the best of it 152 | by continuing anyway 153 | */ 154 | read_config(); 155 | get_options(argc, (char **)argv); 156 | 157 | /* get the username */ 158 | if (pam_get_item(pamh, PAM_USER, (const void **)(void *)&user) != PAM_SUCCESS) 159 | user = NULL; 160 | 161 | if (user != NULL && !*user) 162 | user = NULL; 163 | 164 | logmsg(LOG_DEBUG, "user %s", (user == NULL) ? "(unknown)" : user); 165 | 166 | /* if not blocking all and the user is known, let go */ 167 | if (!(options & OPT_BLOCK_ALL) && user != NULL && (pwd = getpwnam(user)) != NULL) { 168 | logmsg(LOG_DEBUG, "ignoring known user %s", user); 169 | deinit_module(); 170 | return PAM_IGNORE; 171 | } 172 | 173 | /* get the remotehost address */ 174 | if (pam_get_item(pamh, PAM_RHOST, (const void **)(void *)&rhost) != PAM_SUCCESS) 175 | rhost = NULL; 176 | 177 | if (rhost != NULL && !*rhost) 178 | rhost = NULL; 179 | 180 | logmsg(LOG_DEBUG, "remotehost %s", (rhost == NULL) ? "(unknown)" : rhost); 181 | 182 | /* 183 | if rhost is NULL, pam_shield is probably being used for a local service here 184 | Because pam_shield only makes sense in a networked environment, bail out now 185 | */ 186 | if (rhost == NULL) { 187 | deinit_module(); 188 | return PAM_IGNORE; 189 | } 190 | 191 | /* 192 | if rhost is completely numeric, then it has no DNS entry 193 | */ 194 | suspicious_dns = 0; 195 | if (strspn(rhost, "0123456789.") == strlen(rhost) 196 | || strspn(rhost, "0123456789:abcdefABCDEF") == strlen(rhost)) { 197 | if (options & OPT_MISSING_DNS) 198 | logmsg(LOG_DEBUG, "missing DNS entry for %s (allowed)", rhost); 199 | else { 200 | logmsg(LOG_DEBUG, "missing DNS entry for %s (denied)", rhost); 201 | suspicious_dns = 1; 202 | } 203 | } else { 204 | /* 205 | see if this rhost is whitelisted 206 | */ 207 | if (match_name_list(rhost)) { 208 | deinit_module(); 209 | return PAM_IGNORE; 210 | } 211 | } 212 | do { 213 | struct addrinfo *addr_info, *addr_p; 214 | unsigned char addr_family; 215 | char ipbuf[INET6_ADDRSTRLEN], *saddr; 216 | _pam_shield_db_rec_t *record; 217 | datum key, data; 218 | int whitelisted; 219 | 220 | if ((addr_info = get_addr_info(rhost)) == NULL) { /* missing reverse DNS entry */ 221 | if (options & OPT_MISSING_REVERSE) 222 | logmsg(LOG_DEBUG, "missing reverse DNS entry for %s (allowed)", rhost); 223 | else { 224 | logmsg(LOG_DEBUG, "missing reverse DNS entry for %s (denied)", rhost); 225 | suspicious_dns = 1; 226 | } 227 | } 228 | /* for every address that this host is known for, check for whitelist entry */ 229 | for(addr_p = addr_info; addr_p != NULL; addr_p = addr_p->ai_next) { 230 | whitelisted = 0; 231 | switch(addr_p->ai_family) { 232 | case PF_INET: 233 | saddr = (char *)&((struct sockaddr_in *)(addr_p->ai_addr))->sin_addr.s_addr; 234 | 235 | if (match_ipv4_list((unsigned char *)saddr)) { 236 | logmsg(LOG_DEBUG, "remoteip %s (whitelisted)", inet_ntop(AF_INET, (char *)&((struct sockaddr_in *)(addr_p->ai_addr))->sin_addr, ipbuf, sizeof(ipbuf))); 237 | whitelisted = 1; 238 | } else 239 | logmsg(LOG_DEBUG, "remoteip %s", inet_ntop(AF_INET, (char *)&((struct sockaddr_in *)(addr_p->ai_addr))->sin_addr, ipbuf, sizeof(ipbuf))); 240 | break; 241 | 242 | case PF_INET6: 243 | saddr = (char *)&((struct sockaddr_in6 *)(addr_p->ai_addr))->sin6_addr.s6_addr; 244 | 245 | if (match_ipv6_list((unsigned char *)saddr)) { 246 | logmsg(LOG_DEBUG, "remoteip %s (whitelisted)", inet_ntop(AF_INET6, (char *)&((struct sockaddr_in6 *)(addr_p->ai_addr))->sin6_addr, ipbuf, sizeof(ipbuf))); 247 | whitelisted = 1; 248 | } else 249 | logmsg(LOG_DEBUG, "remoteip %s", inet_ntop(AF_INET6, (char *)&((struct sockaddr_in6 *)(addr_p->ai_addr))->sin6_addr, ipbuf, sizeof(ipbuf))); 250 | break; 251 | 252 | default: 253 | logmsg(LOG_DEBUG, "remoteip unknown (not IP)"); 254 | 255 | freeaddrinfo(addr_info); 256 | deinit_module(); 257 | return (suspicious_dns) ? PAM_AUTH_ERR : PAM_IGNORE; 258 | } 259 | /* host is whitelisted by an allow line in the config file, so exit */ 260 | if (whitelisted) { 261 | freeaddrinfo(addr_info); 262 | deinit_module(); 263 | return PAM_IGNORE; 264 | } 265 | } 266 | /* open the database */ 267 | retry_count=0; 268 | while ((dbf = gdbm_open(dbfile, 512, GDBM_WRCREAT, (mode_t)0600, fatal_func)) == NULL) { 269 | if (gdbm_errno != GDBM_CANT_BE_WRITER || retry_count>500) { 270 | logmsg(LOG_ERR, "failed to open gdbm file '%s' : %s", dbfile, 271 | gdbm_strerror(gdbm_errno)); 272 | freeaddrinfo(addr_info); 273 | deinit_module(); 274 | return (suspicious_dns) ? PAM_AUTH_ERR : PAM_IGNORE; 275 | } 276 | logmsg(LOG_DEBUG,"waiting to open db, try %d",retry_count); 277 | usleep(1000); 278 | retry_count++; 279 | } 280 | /* for every address that this host is known for, check the database */ 281 | for(addr_p = addr_info; addr_p != NULL; addr_p = addr_p->ai_next) { 282 | whitelisted = 0; 283 | switch(addr_p->ai_family) { 284 | case PF_INET: 285 | addr_family = PAM_SHIELD_ADDR_IPV4; 286 | key.dptr = saddr = (char *)&((struct sockaddr_in *)(addr_p->ai_addr))->sin_addr.s_addr; 287 | key.dsize = sizeof(struct in_addr); 288 | break; 289 | 290 | case PF_INET6: 291 | addr_family = PAM_SHIELD_ADDR_IPV6; 292 | key.dptr = saddr = (char *)&((struct sockaddr_in6 *)(addr_p->ai_addr))->sin6_addr.s6_addr; 293 | key.dsize = sizeof(struct in6_addr); 294 | break; 295 | 296 | default: 297 | addr_family = -1; 298 | key.dptr = saddr = NULL; 299 | key.dsize = 0; 300 | } 301 | if (key.dptr == NULL) 302 | continue; 303 | 304 | data = gdbm_fetch(dbf, key); /* get db record */ 305 | if (data.dptr != NULL) { 306 | record = (_pam_shield_db_rec_t *)data.dptr; 307 | /* 308 | Although this code does some expiration, it only does so for "this ip"; 309 | it is still necessary to run an external database cleanup process every 310 | now and then (eg, from cron.daily) 311 | */ 312 | expire_record(record); 313 | 314 | if (record->count >= record->max_entries) { /* shift, so we always log the most recent time */ 315 | memmove(record->timestamps, &record->timestamps[1], (record->max_entries-1)*sizeof(time_t)); 316 | record->count--; 317 | } 318 | record->timestamps[record->count++] = this_time; 319 | 320 | logmsg(LOG_DEBUG, "%u times from %s", record->count, rhost); 321 | /* 322 | too many in the interval, so trigger 323 | 324 | trigger "add" is subject to a race, so try to be smart about it 325 | and do not add the same block within 20 seconds 326 | */ 327 | if (record->count >= max_conns && this_time - record->trigger_active > 20 328 | && !run_trigger("add", record)) 329 | record->trigger_active = this_time; 330 | } else { 331 | if ((record = new_db_record(max_conns)) != NULL) { 332 | record->addr_family = addr_family; 333 | memcpy(record->ip.any, saddr, key.dsize); 334 | record->timestamps[record->count++] = this_time; 335 | 336 | logmsg(LOG_DEBUG, "putting new record in db"); 337 | 338 | if (max_conns <= 1) { /* (maybe) stupid, but possible */ 339 | record->trigger_active = this_time; 340 | run_trigger("add", record); 341 | } 342 | } 343 | } 344 | if (record != NULL) { 345 | data.dptr = (char *)record; 346 | data.dsize = sizeof(_pam_shield_db_rec_t) + (record->max_entries-1)*sizeof(time_t); 347 | 348 | /* key.dptr and key.dsize are still set to saddr and addr_size */ 349 | 350 | if (gdbm_store(dbf, key, data, GDBM_REPLACE)) 351 | logmsg(LOG_ERR, "failed to write db record"); 352 | } 353 | destroy_db_record(record); 354 | } 355 | freeaddrinfo(addr_info); 356 | gdbm_close(dbf); 357 | } while(0); 358 | 359 | deinit_module(); 360 | return (suspicious_dns) ? PAM_AUTH_ERR : PAM_IGNORE; 361 | } 362 | 363 | PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { 364 | return PAM_SUCCESS; 365 | } 366 | 367 | /* EOB */ 368 | -------------------------------------------------------------------------------- /pam_shield.h: -------------------------------------------------------------------------------- 1 | /* 2 | pam_shield.h 3 | */ 4 | 5 | #ifndef PAM_SHIELD 6 | #define PAM_SHIELD 1 7 | 8 | #include 9 | 10 | #define PAM_SHIELD_ADDR_IPV4 0 11 | #define PAM_SHIELD_ADDR_IPV6 1 12 | 13 | typedef struct { 14 | unsigned char addr_family; /* PAM_SHIELD_ADDR_IPV4|PAM_SHIELD_ADDR_IPV6 */ 15 | union { 16 | struct in_addr in; /* IPv4 number */ 17 | struct in6_addr in6; /* IPv6 number */ 18 | char any[1]; /* access to any */ 19 | } ip; 20 | 21 | unsigned int max_entries; /* number of timestamps */ 22 | unsigned int count; /* number of auth requests done */ 23 | time_t trigger_active; /* time the trigger was triggered (needed for expiration) */ 24 | time_t timestamps[1]; /* sliding window of timestamps */ 25 | } _pam_shield_db_rec_t; 26 | 27 | 28 | /* 29 | the IP list is used for making in-memory whitelists 30 | (they are not in the database, but in the config file) 31 | */ 32 | typedef struct ip_list_tag ip_list; 33 | 34 | struct ip_list_tag { 35 | union { 36 | struct in_addr in; 37 | struct in6_addr in6; 38 | unsigned char any[1]; 39 | } ip; 40 | 41 | union { 42 | struct in_addr in; 43 | struct in6_addr in6; 44 | unsigned char any[1]; 45 | } mask; 46 | 47 | ip_list *prev, *next; 48 | }; 49 | 50 | /* whitelisted hosntnames and network names */ 51 | typedef struct name_list_tag name_list; 52 | 53 | struct name_list_tag { 54 | name_list *prev, *next; 55 | 56 | char name[1]; 57 | }; 58 | 59 | #endif /* PAM_SHIELD */ 60 | 61 | /* EOB */ 62 | -------------------------------------------------------------------------------- /pam_shield_lib.c: -------------------------------------------------------------------------------- 1 | /* 2 | pam_shield_lib.c 3 | 4 | pam_shield 0.9.7 5 | Copyright (C) 2007-2012 Walter de Jong 6 | and Jonathan Niehof 7 | 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 2 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include "pam_shield_lib.h" 43 | 44 | #pragma GCC visibility push(hidden) 45 | 46 | int options = 0; 47 | GDBM_FILE dbf; 48 | 49 | char *conffile = NULL; 50 | char *dbfile = NULL; 51 | char *trigger_cmd = NULL; 52 | char *removeip = NULL; 53 | 54 | /* white lists of addresses */ 55 | ip_list *allow_ipv4_list = NULL; 56 | ip_list *allow_ipv6_list = NULL; 57 | name_list *allow_names = NULL; 58 | 59 | int max_conns = DEFAULT_MAX_CONNS; 60 | long interval = DEFAULT_INTERVAL; 61 | long retention = DEFAULT_RETENTION; 62 | 63 | time_t this_time; 64 | 65 | 66 | ip_list *new_ip_list(void) { 67 | ip_list *ip; 68 | 69 | if ((ip = (ip_list *)malloc(sizeof(ip_list))) == NULL) 70 | return NULL; 71 | 72 | memset(ip, 0, sizeof(ip_list)); 73 | return ip; 74 | } 75 | 76 | void destroy_ip_list(ip_list *list) { 77 | ip_list *p; 78 | 79 | while(list != NULL) { 80 | p = list; 81 | list = list->next; 82 | free(p); 83 | } 84 | } 85 | 86 | void add_ip_list(ip_list **root, ip_list *ip) { 87 | if (root == NULL || ip == NULL) 88 | return; 89 | 90 | if (options & OPT_DEBUG) { 91 | char addr[INET6_ADDRSTRLEN], mask[INET6_ADDRSTRLEN]; 92 | 93 | if (*root == allow_ipv4_list) /* (butt ugly check, just to get nice debug output) */ 94 | logmsg(LOG_DEBUG, "allowing from %s/%s", inet_ntop(AF_INET, &ip->ip.in, addr, sizeof(addr)), 95 | inet_ntop(AF_INET, &ip->mask.in, mask, sizeof(mask))); 96 | else 97 | logmsg(LOG_DEBUG, "allowing from %s/%s", inet_ntop(AF_INET6, &ip->ip.in6, addr, sizeof(addr)), 98 | inet_ntop(AF_INET6, &ip->mask.in6, mask, sizeof(mask))); 99 | } 100 | ip->prev = ip->next = NULL; 101 | 102 | if (*root == NULL) { 103 | *root = ip; 104 | return; 105 | } 106 | /* prepend it */ 107 | (*root)->prev = ip; 108 | ip->next = *root; 109 | *root = ip; 110 | } 111 | 112 | /* 113 | try to match an IP number against the allow list 114 | returns 1 if it matches 115 | */ 116 | int match_ipv4_list(unsigned char *saddr) { 117 | ip_list *ip; 118 | int i, match; 119 | 120 | for(ip = allow_ipv4_list; ip != NULL; ip = ip->next) { 121 | match = 1; 122 | for(i = 0; i < sizeof(ip->ip.in.s_addr); i++) { 123 | if ((ip->ip.any[i] & ip->mask.any[i]) != (saddr[i] & ip->mask.any[i])) { 124 | match = 0; 125 | break; 126 | } 127 | } 128 | if (match) { 129 | char addr1[INET_ADDRSTRLEN], addr2[INET_ADDRSTRLEN], mask[INET_ADDRSTRLEN]; 130 | 131 | logmsg(LOG_DEBUG, "whitelist match: %s %s/%s", inet_ntop(AF_INET, saddr, addr1, sizeof(addr1)), 132 | inet_ntop(AF_INET, &ip->ip.in, addr2, sizeof(addr2)), 133 | inet_ntop(AF_INET, &ip->mask.in, mask, sizeof(mask))); 134 | return 1; 135 | } 136 | } 137 | return 0; 138 | } 139 | 140 | int match_ipv6_list(unsigned char *saddr) { 141 | ip_list *ip; 142 | int i, match; 143 | 144 | for(ip = allow_ipv6_list; ip != NULL; ip = ip->next) { 145 | match = 1; 146 | for(i = 0; i < sizeof(ip->ip.in6.s6_addr); i++) { 147 | if ((ip->ip.any[i] & ip->mask.any[i]) != (saddr[i] & ip->mask.any[i])) { 148 | match = 0; 149 | break; 150 | } 151 | } 152 | if (match) { 153 | char addr1[INET6_ADDRSTRLEN], addr2[INET6_ADDRSTRLEN], mask[INET6_ADDRSTRLEN]; 154 | 155 | logmsg(LOG_DEBUG, "whitelist match: %s %s/%s", inet_ntop(AF_INET6, saddr, addr1, sizeof(addr1)), 156 | inet_ntop(AF_INET6, &ip->ip.in6, addr2, sizeof(addr2)), 157 | inet_ntop(AF_INET6, &ip->mask.in6, mask, sizeof(mask))); 158 | return 1; 159 | } 160 | } 161 | return 0; 162 | } 163 | 164 | /* 165 | name_lists are hostnames and/or network names 166 | */ 167 | name_list *new_name_list(char *name) { 168 | name_list *n; 169 | 170 | if (name == NULL || !*name) 171 | return NULL; 172 | 173 | if ((n = (name_list *)malloc(sizeof(name_list) + strlen(name))) == NULL) 174 | return NULL; 175 | 176 | memset(n, 0, sizeof(name_list)); 177 | strcpy(n->name, name); 178 | return n; 179 | } 180 | 181 | void destroy_name_list(name_list *list) { 182 | name_list *p; 183 | 184 | while(list != NULL) { 185 | p = list; 186 | list = list->next; 187 | free(p); 188 | } 189 | } 190 | 191 | void add_name_list(name_list **root, name_list *n) { 192 | if (root == NULL || n == NULL) 193 | return; 194 | 195 | logmsg(LOG_DEBUG, "allowing from %s", n->name); 196 | 197 | n->prev = n->next = NULL; 198 | 199 | if (*root == NULL) { 200 | *root = n; 201 | return; 202 | } 203 | /* prepend it */ 204 | (*root)->prev = n; 205 | n->next = *root; 206 | *root = n; 207 | } 208 | 209 | 210 | /* 211 | see if 'name' matches our whitelist 212 | return 1 if it does 213 | */ 214 | int match_name_list(char *name) { 215 | name_list *n; 216 | 217 | if (name == NULL || !*name) 218 | return 0; 219 | 220 | for(n = allow_names; n != NULL; n = n->next) { 221 | if (n->name[0] == '.') { 222 | if ((strlen(name) > strlen(n->name)) && !strcasecmp(n->name, name + strlen(name) - strlen(n->name))) { 223 | logmsg(LOG_DEBUG, "whitelist match: host %s in domain %s", name, n->name); 224 | return 1; 225 | } 226 | } else { 227 | if (!strcasecmp(n->name, name)) { 228 | logmsg(LOG_DEBUG, "whitelist match: host %s", name); 229 | return 1; 230 | } 231 | } 232 | } 233 | return 0; 234 | } 235 | 236 | 237 | /* 238 | initialize variables 239 | */ 240 | int init_module(void) { 241 | this_time = time(NULL); 242 | 243 | conffile = strdup(DEFAULT_CONFFILE); 244 | dbfile = strdup(DEFAULT_DBFILE); 245 | trigger_cmd = strdup(DEFAULT_TRIGGER_CMD); 246 | 247 | if (conffile == NULL || dbfile == NULL || trigger_cmd == NULL) { 248 | logmsg(LOG_CRIT, "out of memory"); 249 | return -1; 250 | } 251 | return 0; 252 | } 253 | 254 | void deinit_module(void) { 255 | if (conffile != NULL) { 256 | free(conffile); 257 | conffile = NULL; 258 | } 259 | if (dbfile != NULL) { 260 | free(dbfile); 261 | dbfile = NULL; 262 | } 263 | if (trigger_cmd != NULL) { 264 | free(trigger_cmd); 265 | trigger_cmd = NULL; 266 | } 267 | destroy_ip_list(allow_ipv4_list); 268 | allow_ipv4_list = NULL; 269 | 270 | destroy_ip_list(allow_ipv6_list); 271 | allow_ipv6_list = NULL; 272 | 273 | destroy_name_list(allow_names); 274 | allow_names = NULL; 275 | } 276 | 277 | /* 278 | strip leading and trailing whitespace from a string 279 | */ 280 | void strip(char *str) { 281 | char *p; 282 | int i; 283 | 284 | if (str == NULL || !*str) 285 | return; 286 | 287 | p = str; 288 | 289 | if (*p == ' ' || *p == '\t') { 290 | while(*p && (*p == ' ' || *p == '\t')) 291 | p++; 292 | 293 | memmove(str, p, strlen(p)+1); 294 | } 295 | i = strlen(str)-1; 296 | while(i >= 0 && (str[i] == ' ' || str[i] == '\t' || str[i] == '\r' || str[i] == '\n')) 297 | str[i--] = 0; 298 | } 299 | 300 | 301 | /* 302 | multipliers: 303 | 1s second 304 | 1m minute 305 | 1h hour 306 | 1d day 307 | 1w week 308 | 1M month 309 | 1y year 310 | 311 | default is 1 312 | returns 0 on error 313 | */ 314 | long get_multiplier(char *str) { 315 | if (str == NULL || !*str) 316 | return 1L; 317 | 318 | if (str[1]) /* we expect only a single character here */ 319 | return 0L; 320 | 321 | switch(*str) { 322 | case 's': 323 | return 1L; 324 | 325 | case 'm': 326 | return 60L; 327 | 328 | case 'h': 329 | return 3600L; 330 | 331 | case 'd': 332 | return (3600L * 24L); 333 | 334 | case 'w': 335 | return (7L * 3600L * 24L); 336 | 337 | case 'M': 338 | return (30L * 3600L * 24L); 339 | 340 | case 'y': 341 | case 'Y': 342 | return (365L * 3600L * 24L); 343 | } 344 | return 0L; 345 | } 346 | 347 | /* 348 | generate bitmask from '/24' notation 349 | 350 | mask is struct in_addr.saddr, size is the size of the array 351 | (4 for IPv4, 16 for IPv6) 352 | */ 353 | void ip_bitmask(int bits, unsigned char *mask, int size) { 354 | int i, num, rest; 355 | 356 | if (mask == NULL) 357 | return; 358 | 359 | memset(mask, 0, size); 360 | 361 | if (bits < 0) 362 | bits = 0; 363 | 364 | if (bits > size*8) 365 | bits = size*8; 366 | 367 | num = bits / 8; 368 | rest = bits % 8; 369 | 370 | for(i = 0; i < num; i++) 371 | mask[i] = 0xff; 372 | 373 | if (rest) 374 | mask[i++] = ~(0xff >> rest); 375 | 376 | while(i < size) 377 | mask[i++] = 0; 378 | } 379 | 380 | /* 381 | allow network/netmask, for both IPv4 and IPv6 382 | netmask can be in canonical or decimal notation 383 | */ 384 | int allow_ip(char *ipnum, int line_no) { 385 | char *netmask; 386 | ip_list *ip; 387 | name_list *name; 388 | int bits; 389 | 390 | if (ipnum == NULL || !*ipnum) { 391 | logmsg(LOG_ALERT, "%s:%d: missing argument to 'allow'", conffile, line_no); 392 | return -1; 393 | } 394 | if ((netmask = strchr(ipnum, '/')) != NULL) { 395 | *netmask = 0; 396 | netmask++; 397 | if (!*netmask) { 398 | logmsg(LOG_ALERT, "%s:%d: missing netmask, assuming it is a host", conffile, line_no); 399 | netmask = NULL; 400 | } 401 | } 402 | if ((ip = new_ip_list()) == NULL) { 403 | logmsg(LOG_ALERT, "%s:%d: out of memory adding 'allow' line", conffile, line_no); 404 | return -1; 405 | } 406 | /* try network address as IPv4 */ 407 | if (inet_pton(AF_INET, ipnum, &ip->ip.in) > 0) { 408 | if (netmask == NULL) { /* no netmask given, treat as host */ 409 | memset(&ip->mask.in.s_addr, 0xff, sizeof(ip->mask.in.s_addr)); 410 | add_ip_list(&allow_ipv4_list, ip); 411 | return 0; 412 | } 413 | /* netmask in '/24' like notation? */ 414 | if (strspn(netmask, "0123456789") == strlen(netmask)) { 415 | bits = atoi(netmask); 416 | if (bits <= 0 || bits > 32) { 417 | logmsg(LOG_ALERT, "%s:%d: syntax error in netmask", conffile, line_no); 418 | destroy_ip_list(ip); 419 | return -1; 420 | } 421 | ip_bitmask(bits, (unsigned char *)&ip->mask.in.s_addr, sizeof(ip->mask.in.s_addr)); 422 | 423 | add_ip_list(&allow_ipv4_list, ip); 424 | return 0; 425 | } 426 | /* netmask in canonical notation? */ 427 | if (inet_pton(AF_INET, netmask, &ip->mask.in) > 0) { 428 | add_ip_list(&allow_ipv4_list, ip); 429 | return 0; 430 | } 431 | logmsg(LOG_ALERT, "%s:%d: syntax error in netmask", conffile, line_no); 432 | destroy_ip_list(ip); 433 | return -1; 434 | } 435 | /* try network address as IPv6 */ 436 | if (inet_pton(AF_INET6, ipnum, &ip->ip.in6) > 0) { 437 | if (netmask == NULL) { /* no netmask given, treat as host */ 438 | memset(ip->mask.in6.s6_addr, 0xff, sizeof(ip->mask.in6.s6_addr)); 439 | add_ip_list(&allow_ipv6_list, ip); 440 | return 0; 441 | } 442 | /* netmask in '/24' like notation? */ 443 | if (strspn(netmask, "0123456789") == strlen(netmask)) { 444 | bits = atoi(netmask); 445 | if (bits <= 0 || bits > 32) { 446 | logmsg(LOG_ALERT, "%s:%d: syntax error in netmask", conffile, line_no); 447 | destroy_ip_list(ip); 448 | return -1; 449 | } 450 | ip_bitmask(bits, (unsigned char *)ip->mask.in6.s6_addr, sizeof(ip->mask.in6.s6_addr)); 451 | 452 | add_ip_list(&allow_ipv6_list, ip); 453 | return 0; 454 | } 455 | /* netmask in canonical notation? */ 456 | if (inet_pton(AF_INET6, netmask, &ip->mask.in6) > 0) { 457 | add_ip_list(&allow_ipv6_list, ip); 458 | return 0; 459 | } 460 | logmsg(LOG_ALERT, "%s:%d: syntax error in netmask", conffile, line_no); 461 | destroy_ip_list(ip); 462 | return -1; 463 | } 464 | /* 465 | when we get here it's either a syntax error or a hostname or a network name 466 | with names, you can not specify a netmask 467 | */ 468 | destroy_ip_list(ip); 469 | ip = NULL; 470 | 471 | if (netmask != NULL) { 472 | logmsg(LOG_ALERT, "%s:%d: syntax error in internet address", conffile, line_no); 473 | return -1; 474 | } 475 | if ((name = new_name_list(ipnum)) == NULL) { 476 | logmsg(LOG_ALERT, "%s:%d: out of memory while adding 'allow' line", conffile, line_no); 477 | return -1; 478 | } 479 | add_name_list(&allow_names, name); 480 | return 0; 481 | } 482 | 483 | /* 484 | read configuration file 485 | */ 486 | int read_config(void) { 487 | FILE *f; 488 | struct stat statbuf; 489 | char buf[MAX_LINE], *p, *endp; 490 | int line_no, err; 491 | long multiplier; 492 | 493 | logmsg(LOG_DEBUG, "reading config file '%s'", conffile); 494 | 495 | if ((f = fopen(conffile, "r")) == NULL) { 496 | logmsg(LOG_ALERT, "failed to read config file '%s'", conffile); 497 | return -1; 498 | } 499 | line_no = 0; 500 | err = 0; 501 | 502 | while(fgets(buf, MAX_LINE, f) != NULL) { 503 | line_no++; 504 | 505 | strip(buf); 506 | if (!*buf || buf[0] == '#') 507 | continue; 508 | 509 | /* keyword value */ 510 | 511 | p = buf; 512 | while(*p && *p != ' ' && *p != '\t') 513 | p++; 514 | 515 | if (!*p) { 516 | logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); 517 | err--; 518 | continue; 519 | } 520 | *p = 0; 521 | p++; 522 | 523 | strip(buf); 524 | if (!*buf) { 525 | logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); 526 | err--; 527 | continue; 528 | } 529 | strip(p); 530 | if (!*p) { 531 | logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); 532 | err--; 533 | continue; 534 | } 535 | 536 | /* buf is the key, p is the value */ 537 | 538 | if (!strcmp(buf, "debug")) { 539 | if (!strcmp(p, "on") || !strcmp(p, "yes")) { 540 | options |= OPT_DEBUG; 541 | logmsg(LOG_DEBUG, "logging debug info"); 542 | continue; 543 | } 544 | if (!strcmp(p, "off") || !strcmp(p, "no")) { 545 | logmsg(LOG_DEBUG, "ignoring config option 'debug %s' (overruled by PAM command line argument 'debug')", p); 546 | continue; 547 | } 548 | logmsg(LOG_ALERT, "%s:%d: unknown argument '%s' to 'debug'", conffile, line_no, p); 549 | continue; 550 | } 551 | if (!strcmp(buf, "block")) { 552 | if (!strcmp(p, "all-users")) { 553 | options |= OPT_BLOCK_ALL; 554 | continue; 555 | } 556 | if (!strcmp(p, "unknown-users")) { 557 | options &= ~OPT_BLOCK_ALL; 558 | continue; 559 | } 560 | logmsg(LOG_ALERT, "%s:%d: unknown argument '%s' to 'block'", conffile, line_no, p); 561 | err--; 562 | continue; 563 | } 564 | if (!strcmp(buf, "allow_missing_dns")) { 565 | if (!strcasecmp(p, "yes") || !strcasecmp(p, "allow") || !strcasecmp(p, "on")) { 566 | options |= OPT_MISSING_DNS; 567 | continue; 568 | } 569 | if (!strcasecmp(p, "no") || !strcasecmp(p, "deny") || !strcasecmp(p, "off")) { 570 | options &= ~OPT_MISSING_DNS; 571 | continue; 572 | } 573 | logmsg(LOG_ALERT, "%s:%d: unknown argument '%s' to 'allow_missing_dns'", conffile, line_no, p); 574 | err--; 575 | continue; 576 | } 577 | if (!strcmp(buf, "allow_missing_reverse")) { 578 | if (!strcasecmp(p, "yes") || !strcasecmp(p, "allow") || !strcasecmp(p, "on")) { 579 | options |= OPT_MISSING_REVERSE; 580 | continue; 581 | } 582 | if (!strcasecmp(p, "no") || !strcasecmp(p, "deny") || !strcasecmp(p, "off")) { 583 | options &= ~OPT_MISSING_REVERSE; 584 | continue; 585 | } 586 | logmsg(LOG_ALERT, "%s:%d: unknown argument '%s' to 'allow_missing_reverse'", conffile, line_no, p); 587 | err--; 588 | continue; 589 | } 590 | if (!strcmp(buf, "allow")) { 591 | if (allow_ip(p, line_no)) 592 | err--; 593 | continue; 594 | } 595 | if (!strcmp(buf, "db")) { 596 | free(dbfile); 597 | if ((dbfile = strdup(p)) == NULL) { 598 | logmsg(LOG_CRIT, "out of memory"); 599 | err--; 600 | } 601 | continue; 602 | } 603 | if (!strcmp(buf, "trigger_cmd")) { 604 | free(trigger_cmd); 605 | if ((trigger_cmd = strdup(p)) == NULL) { 606 | logmsg(LOG_CRIT, "out of memory"); 607 | err--; 608 | } 609 | if (stat(trigger_cmd, &statbuf) == -1) { 610 | logmsg(LOG_ALERT, "%s:%d: command '%s' not found", conffile, line_no, trigger_cmd); 611 | err--; 612 | } 613 | continue; 614 | } 615 | if (!strcmp(buf, "max_conns")) { 616 | max_conns = (int)strtol(p, &endp, 10); 617 | if (*endp) { 618 | logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); 619 | err--; 620 | max_conns = DEFAULT_MAX_CONNS; 621 | } 622 | continue; 623 | } 624 | if (!strcmp(buf, "interval")) { 625 | interval = (int)strtol(p, &endp, 10); 626 | if (!(multiplier = get_multiplier(endp))) { 627 | logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); 628 | err--; 629 | interval = DEFAULT_INTERVAL; 630 | } else 631 | interval *= multiplier; 632 | continue; 633 | } 634 | if (!strcmp(buf, "retention")) { 635 | retention = (int)strtol(p, &endp, 10); 636 | if (!(multiplier = get_multiplier(endp))) { 637 | logmsg(LOG_ALERT, "%s:%d: syntax error", conffile, line_no); 638 | err--; 639 | retention = DEFAULT_RETENTION; 640 | } else 641 | retention *= multiplier; 642 | continue; 643 | } 644 | logmsg(LOG_ALERT, "%s:%d: unknown keyword '%s'", conffile, line_no, buf); 645 | err--; 646 | } 647 | fclose(f); 648 | 649 | logmsg(LOG_DEBUG, "done reading config file, %d errors", -err); 650 | 651 | return err; 652 | } 653 | 654 | /* 655 | print the IP number of a db_record 656 | return NULL on error, or buf on success 657 | */ 658 | const char *print_ip(_pam_shield_db_rec_t *record, char *buf, int buflen) { 659 | if (buf == NULL || buflen <= 1) 660 | return NULL; 661 | 662 | buflen--; 663 | if (!buflen) { 664 | *buf = 0; 665 | return buf; 666 | } 667 | if (record == NULL) { 668 | strncpy(buf, "(null)", buflen); 669 | buf[buflen] = 0; 670 | return buf; 671 | } 672 | switch(record->addr_family) { 673 | case PAM_SHIELD_ADDR_IPV4: 674 | return inet_ntop(AF_INET, &record->ip.in, buf, buflen); 675 | 676 | case PAM_SHIELD_ADDR_IPV6: 677 | return inet_ntop(AF_INET6, &record->ip.in6, buf, buflen); 678 | } 679 | return NULL; 680 | } 681 | 682 | /* 683 | run external command 684 | */ 685 | int run_trigger(char *cmd, _pam_shield_db_rec_t *record) { 686 | char ipbuf[INET6_ADDRSTRLEN]; 687 | pid_t pid; 688 | 689 | if (cmd == NULL || record == NULL) 690 | return -1; 691 | 692 | if (print_ip(record, ipbuf, sizeof(ipbuf)) == NULL) 693 | return -1; 694 | 695 | logmsg(LOG_DEBUG, "running command '%s %s'", cmd, ipbuf); 696 | 697 | if (options & OPT_DRYRUN) 698 | return 0; 699 | 700 | pid = fork(); 701 | if (pid == (pid_t)-1) { 702 | logmsg(LOG_CRIT, "can not fork, failed to run trigger"); 703 | return -1; 704 | } 705 | if (!pid) { 706 | char *argv[4]; 707 | 708 | argv[0] = trigger_cmd; 709 | argv[1] = cmd; 710 | argv[2] = ipbuf; 711 | argv[3] = NULL; 712 | 713 | execvp(argv[0], argv); 714 | 715 | logmsg(LOG_CRIT, "failed to execute command '%s %s %s'", trigger_cmd, cmd, ipbuf); 716 | exit(-1); 717 | } else { 718 | pid_t err; 719 | int status; 720 | 721 | while((err = waitpid(pid, &status, 0)) > 0); 722 | 723 | if (WEXITSTATUS(status) != 0) 724 | return -1; 725 | } 726 | return 0; 727 | } 728 | 729 | int expire_record(_pam_shield_db_rec_t *record) { 730 | int updated; 731 | char ipbuf[INET6_ADDRSTRLEN]; 732 | 733 | if (record == NULL) 734 | return 0; 735 | 736 | updated = 0; 737 | /* 738 | expire entries that are no longer in this interval (sliding window) 739 | */ 740 | while(record->count > 0 && difftime(this_time, record->timestamps[0]) >= (double)interval) { 741 | memmove(record->timestamps, &record->timestamps[1], (record->max_entries-1)*sizeof(time_t)); 742 | record->count--; 743 | updated++; 744 | } 745 | if (record->trigger_active) { 746 | if (difftime(this_time, record->trigger_active) >= (double)retention) { 747 | /* 748 | expire old trigger, but only do this if the sliding window is clean 749 | */ 750 | if (!record->count) { 751 | logmsg(LOG_DEBUG, "expiring old trigger for %s", print_ip(record, ipbuf, sizeof(ipbuf))); 752 | record->trigger_active = (time_t)0L; 753 | run_trigger("del", record); 754 | updated++; 755 | } 756 | } else { 757 | if (options & OPT_SYNC) { 758 | run_trigger("sync", record); 759 | } 760 | } 761 | } 762 | return updated; 763 | } 764 | 765 | 766 | /* 767 | gdbm has encountered a fatal error 768 | */ 769 | void fatal_func(const char *str) { 770 | logmsg(LOG_ERR, "gdbm encountered a fatal error : %s; resetting the database", str); 771 | 772 | gdbm_close(dbf); 773 | if ((dbf = gdbm_open(dbfile, 512, GDBM_NEWDB, (mode_t)0600, fatal_func)) == NULL) 774 | logmsg(LOG_ERR, "failed to create new gdbm file '%s' : %s", dbfile, gdbm_strerror(gdbm_errno)); 775 | } 776 | 777 | #pragma GCC visibility pop 778 | /* EOB */ 779 | -------------------------------------------------------------------------------- /pam_shield_lib.h: -------------------------------------------------------------------------------- 1 | /* 2 | pam_shield_lib.h 3 | 4 | pam_shield 0.9.7 5 | Copyright (C) 2007-2012 Walter de Jong 6 | and Jonathan Niehof 7 | 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 2 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include "config.h" 43 | #include "pam_shield.h" 44 | 45 | #pragma GCC visibility push(hidden) 46 | 47 | #define DEFAULT_MAX_CONNS 10 48 | #define DEFAULT_INTERVAL 60L 49 | #define DEFAULT_RETENTION (3600L * 24L) 50 | 51 | #define MAX_LINE 1024 52 | 53 | #define OPT_DEBUG 0x001 54 | #define OPT_BLOCK_ALL 0x002 /* block all, including known users */ 55 | #define OPT_DRYRUN 0x004 56 | #define OPT_LISTDB 0x008 57 | #define OPT_MISSING_DNS 0x010 /* allow missing DNS */ 58 | #define OPT_MISSING_REVERSE 0x020 /* allow missing reverse DNS */ 59 | #define OPT_FORCE 0x040 /* purge unexpired entries */ 60 | #define OPT_REMOVEIP 0x080 61 | #define OPT_SYNC 0x100 62 | 63 | extern int options; 64 | extern GDBM_FILE dbf; 65 | 66 | extern char *conffile; 67 | extern char *dbfile; 68 | extern char *trigger_cmd; 69 | extern char *removeip; 70 | 71 | /* white lists of addresses */ 72 | extern ip_list *allow_ipv4_list; 73 | extern ip_list *allow_ipv6_list; 74 | extern name_list *allow_names; 75 | 76 | extern int max_conns; 77 | extern long interval; 78 | extern long retention; 79 | 80 | extern time_t this_time; 81 | 82 | void logmsg(int level, const char *fmt, ...); 83 | 84 | ip_list *new_ip_list(void); 85 | 86 | void destroy_ip_list(ip_list *list); 87 | 88 | void add_ip_list(ip_list **root, ip_list *ip); 89 | 90 | /* 91 | try to match an IP number against the allow list 92 | returns 1 if it matches 93 | */ 94 | int match_ipv4_list(unsigned char *saddr); 95 | 96 | int match_ipv6_list(unsigned char *saddr); 97 | 98 | /* 99 | name_lists are hostnames and/or network names 100 | */ 101 | name_list *new_name_list(char *name); 102 | 103 | void destroy_name_list(name_list *list); 104 | 105 | void add_name_list(name_list **root, name_list *n); 106 | 107 | /* 108 | see if 'name' matches our whitelist 109 | return 1 if it does 110 | */ 111 | int match_name_list(char *name); 112 | 113 | 114 | /* 115 | initialize variables 116 | */ 117 | int init_module(void); 118 | 119 | void deinit_module(void); 120 | 121 | /* 122 | strip leading and trailing whitespace from a string 123 | */ 124 | void strip(char *str); 125 | 126 | /* 127 | multipliers: 128 | 1s second 129 | 1m minute 130 | 1h hour 131 | 1d day 132 | 1w week 133 | 1M month 134 | 1y year 135 | 136 | default is 1 137 | returns 0 on error 138 | */ 139 | long get_multiplier(char *str); 140 | 141 | /* 142 | generate bitmask from '/24' notation 143 | 144 | mask is struct in_addr.saddr, size is the size of the array 145 | (4 for IPv4, 16 for IPv6) 146 | */ 147 | void ip_bitmask(int bits, unsigned char *mask, int size); 148 | 149 | /* 150 | allow network/netmask, for both IPv4 and IPv6 151 | netmask can be in canonical or decimal notation 152 | */ 153 | int allow_ip(char *ipnum, int line_no); 154 | 155 | /* 156 | read configuration file 157 | */ 158 | int read_config(void); 159 | 160 | /* 161 | print the IP number of a db_record 162 | return NULL on error, or buf on success 163 | */ 164 | const char *print_ip(_pam_shield_db_rec_t *record, char *buf, int buflen); 165 | 166 | /* 167 | run external command 168 | */ 169 | int run_trigger(char *cmd, _pam_shield_db_rec_t *record); 170 | 171 | int expire_record(_pam_shield_db_rec_t *record); 172 | 173 | /* 174 | gdbm has encountered a fatal error 175 | */ 176 | void fatal_func(const char *str); 177 | 178 | #pragma GCC visibility pop 179 | /* EOB */ 180 | -------------------------------------------------------------------------------- /scripts/shield-trigger: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # shield-trigger 4 | # 5 | # pam_shield 0.9.7 6 | # Copyright (C) 2007-2012 Walter de Jong 7 | # and Jonathan Niehof 8 | # 9 | # This program is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 | # 23 | 24 | null_route() { 25 | # 26 | # louzy detection of IPv4 or IPv6 address 27 | # 28 | TASK="$1" 29 | INET=`echo "$2" | sed 's/[0-9\.]//g'` 30 | if [ -z "$INET" ] 31 | then 32 | INET="" 33 | GW="127.0.0.1" 34 | else 35 | INET="-f inet6" 36 | GW="::1" 37 | fi 38 | 39 | if [ -x /sbin/ip ] 40 | then 41 | if [ "$TASK" == "show" ]; then 42 | /sbin/ip $INET route $TASK $2 | read -t 1 -N 1 43 | if [ $? -eq 0 ]; then 44 | return 45 | fi 46 | TASK="add" 47 | fi 48 | /sbin/ip $INET route $TASK blackhole $2 2>/dev/null 49 | else 50 | if [ ! -z "$INET" ] 51 | then 52 | INET="-A inet6" 53 | fi 54 | /sbin/route $INET $TASK -host $2 gw $GW dev lo 55 | fi 56 | 57 | # mail -s "[security] pam_shield blocked $2" root <" 69 | echo 70 | echo "shield-trigger is normally called by the pam_shield PAM module" 71 | exit 1 72 | } 73 | 74 | 75 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 76 | 77 | if [ -z "$2" ] 78 | then 79 | usage 80 | fi 81 | 82 | case "$1" in 83 | add) 84 | logger -i -t shield-trigger -p authpriv.info "blocking $2" 85 | 86 | CMD="add" 87 | IP=$2 88 | ;; 89 | 90 | del) 91 | logger -i -t shield-trigger -p authpriv.info "unblocking $2" 92 | 93 | CMD="del" 94 | IP=$2 95 | ;; 96 | 97 | sync) 98 | logger -i -t shield-trigger -p authpriv.info "sync $2" 99 | CMD="show" 100 | IP=$2 101 | ;; 102 | 103 | *) 104 | usage 105 | ;; 106 | esac 107 | 108 | null_route "$CMD" "$IP" 109 | 110 | exit 0 # make pam_shield happy 111 | 112 | # EOB 113 | -------------------------------------------------------------------------------- /scripts/shield-trigger-iptables: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # shield-trigger-iptables 4 | # 5 | # pam_shield 0.9.7 6 | # Copyright (C) 2007-2012 Walter de Jong 7 | # and Carl Thompson 8 | # 9 | # This program is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 | # 23 | 24 | run_iptables() { 25 | # 26 | # louzy detection of IPv4 or IPv6 address 27 | # 28 | IPT=`echo "$2" | sed 's/[0-9\.]//g'` 29 | if [ -z "$IPT" ] 30 | then 31 | IPT=iptables 32 | else 33 | IPT=ip6tables 34 | fi 35 | 36 | # switch -A for iptables to -I 37 | if [ "$1" == "-A" ] 38 | then 39 | TASK="-I" 40 | else 41 | TASK=$1 42 | fi 43 | 44 | # check to see if pam_shield chain exists and create if necessary 45 | CHAIN_TEST=`$IPT -L pam_shield 2>/dev/null` 46 | if [ -z "$CHAIN_TEST" ] 47 | then 48 | "$IPT" -N pam_shield 49 | "$IPT" -I pam_shield -j DROP 50 | if [ "$TASK" == "-D" ]; then 51 | return 52 | fi 53 | fi 54 | 55 | # 56 | # CUSTOMIZE THIS RULE if you want to 57 | # 58 | # $TASK is the iptables command: -A/-I or -D 59 | # $2 is the IP number 60 | # 61 | # * put in the correct chain name (pam_shield or INPUT) 62 | # * put in the correct network interface name (e.g. -i eth0) 63 | # Currently blocks on all interfaces 64 | # * put in a port number (e.g.--destination-port 22 for ssh only) 65 | # Currently blocks all ports 66 | # * add additional rules for additional services as needed 67 | # 68 | 69 | "$IPT" "$TASK" INPUT -p tcp -s "$2" -j pam_shield 70 | 71 | if [ $? -ne 0 ]; then 72 | if [ "$TASK" == "-C" ]; then 73 | run_iptables "-I" "$2" 74 | fi 75 | fi 76 | 77 | # mail -s "[security] pam_shield blocked $2" root <" 89 | echo 90 | echo "shield-trigger-iptables is normally called by the pam_shield PAM module" 91 | exit 1 92 | } 93 | 94 | 95 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 96 | 97 | if [ -z "$2" ] 98 | then 99 | usage 100 | fi 101 | 102 | case "$1" in 103 | add) 104 | logger -i -t shield-trigger -p authpriv.info "blocking $2" 105 | 106 | CMD="-A" 107 | IP=$2 108 | ;; 109 | 110 | del) 111 | logger -i -t shield-trigger -p authpriv.info "unblocking $2" 112 | 113 | CMD="-D" 114 | IP=$2 115 | ;; 116 | 117 | sync) 118 | logger -i -t shield-trigger -p authpriv.info "sync $2" 119 | CMD="-C" 120 | IP=$2 121 | ;; 122 | *) 123 | usage 124 | ;; 125 | esac 126 | 127 | run_iptables "$CMD" "$IP" 128 | 129 | # EOB 130 | -------------------------------------------------------------------------------- /scripts/shield-trigger-ufw: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # shield-trigger-ufw 4 | # 5 | # pam_shield 0.9.7 6 | # Copyright (C) 2007-2012 Walter de Jong 7 | # and Jonathan Niehof 8 | # 9 | # This program is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 | # 23 | 24 | usage() { 25 | echo "shield-trigger-ufw" 26 | echo "usage: ${0##*/} [add|del] " 27 | echo 28 | echo "shield-trigger-ufw is normally called by the pam_shield PAM module" 29 | exit 1 30 | } 31 | 32 | 33 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 34 | 35 | if [ -z "$2" ] 36 | then 37 | usage 38 | fi 39 | 40 | case "$1" in 41 | add) 42 | logger -i -t shield-trigger-ufw -p authpriv.info "blocking $2" 43 | ufw insert 1 deny from $2 44 | # mail -s "[security] pam_shield blocked $2" root < 6 | and Jonathan Niehof 7 | 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 2 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #include "pam_shield.h" 24 | 25 | #include 26 | #include 27 | 28 | #include "pam_shield_lib.h" 29 | 30 | 31 | void logmsg(int level, const char *fmt, ...) { 32 | va_list varargs; 33 | 34 | if (level == LOG_DEBUG && !(options & OPT_DEBUG)) 35 | return; 36 | 37 | va_start(varargs, fmt); 38 | vfprintf(stderr, fmt, varargs); 39 | fprintf(stderr, "\n"); 40 | va_end(varargs); 41 | } 42 | 43 | 44 | static void usage(char *progname) { 45 | printf( 46 | "shield-purge " PAM_SHIELD_VERSION "\n" 47 | "usage: %s \n" 48 | "options:\n" 49 | " -h, --help Display this information\n" 50 | " -c, --conf=file Specify config file (default: " DEFAULT_CONFFILE ")\n" 51 | " -d, --debug Verbose output for debugging purposes\n" 52 | " -n, --dry-run Do not perform any updates\n" 53 | " -l, --list List all database entries\n" 54 | " -f, --force Delete all entries, even if unexpired\n" 55 | " -r, --remove=ip Remove IP from database\n" 56 | " -s, --sync Trigger sync for active records\n" 57 | " (rebuild/verify firewall rules)\n" 58 | , basename(progname)); 59 | 60 | printf("\n" 61 | "This program is part of the PAM-shield package.\n" 62 | "PAM-shield comes with ABSOLUTELY NO WARRANTY. This is free software, and you\n" 63 | "are welcome to redistribute it under certain conditions. See the GNU\n" 64 | "General Public Licence for details.\n" 65 | "\n" 66 | "Copyright (C) 2007-2011 by Walter de Jong \n" 67 | "Copyright 2010 Jonathan Niehof \n"); 68 | exit(1); 69 | } 70 | 71 | static void get_options(int argc, char **argv) { 72 | int opt; 73 | struct option long_options[] = { 74 | { "help", 0, NULL, 'h' }, 75 | { "debug", 0, NULL, 'd' }, 76 | { "conf", 1, NULL, 'c' }, 77 | { "dry-run", 0, NULL, 'n' }, 78 | { "list", 0, NULL, 'l' }, 79 | { "force", 0, NULL, 'f' }, 80 | { "remove", 1, NULL, 'r' }, 81 | { "sync", 0, NULL, 's' }, 82 | { NULL, 0, NULL, 0 }, 83 | }; 84 | 85 | while((opt = getopt_long(argc, argv, "hdc:nlfr:s", long_options, NULL)) != -1) { 86 | switch(opt) { 87 | case 'h': 88 | case '?': 89 | usage(argv[0]); 90 | 91 | case 'd': 92 | options |= OPT_DEBUG; 93 | logmsg(LOG_DEBUG, "logging debug info"); 94 | break; 95 | 96 | case 'c': 97 | if (optarg == NULL || !*optarg) { 98 | logmsg(LOG_ERR, "missing filename"); 99 | exit(1); 100 | } 101 | if ((conffile = strdup(optarg)) == NULL) { 102 | logmsg(LOG_ERR, "out of memory"); 103 | exit(-1); 104 | } 105 | break; 106 | 107 | case 'n': 108 | options |= OPT_DRYRUN; 109 | logmsg(LOG_DEBUG, "performing dry-run"); 110 | break; 111 | 112 | case 'l': 113 | options |= OPT_LISTDB; 114 | logmsg(LOG_DEBUG, "list database"); 115 | break; 116 | 117 | case 'f': 118 | options |= OPT_FORCE; 119 | logmsg(LOG_DEBUG, "force purge"); 120 | break; 121 | 122 | case 'r': 123 | options |= OPT_REMOVEIP; 124 | if (optarg == NULL || !*optarg) { 125 | logmsg(LOG_ERR, "missing ip"); 126 | exit(1); 127 | } 128 | if ((removeip = strdup(optarg)) == NULL) { 129 | logmsg(LOG_ERR, "out of memory"); 130 | exit(-1); 131 | } 132 | break; 133 | 134 | case 's': 135 | options |= OPT_SYNC; 136 | logmsg(LOG_DEBUG, "sync"); 137 | break; 138 | 139 | default: 140 | logmsg(LOG_ERR, "bad command line option"); 141 | exit(1); 142 | } 143 | } 144 | } 145 | 146 | /* 147 | lists one record from the DB 148 | */ 149 | static void print_record(_pam_shield_db_rec_t *record) { 150 | char ipbuf[INET6_ADDRSTRLEN]; 151 | unsigned int i; 152 | char * time = ctime(&record->trigger_active); 153 | 154 | time[strlen(time)-1] = '\0'; 155 | 156 | print_ip(record, ipbuf, INET6_ADDRSTRLEN); 157 | 158 | printf("\n {\n" 159 | " \"ip\": \"%s\",\n" 160 | " \"max_entries\": %u,\n" 161 | " \"count\": %u,\n" 162 | " \"trigger_active\": \"%s\",\n" 163 | " \"timestamps\": [\n", 164 | ipbuf, record->max_entries, record->count, 165 | (record->trigger_active > 0) ? time : "" 166 | ); 167 | for(i = 0; i < record->max_entries; i++) 168 | if (record->timestamps[i] > 0) { 169 | time = ctime(&record->timestamps[i]); 170 | time[strlen(time)-1] = '\0'; 171 | printf(" \"%s\"%s\n", time, (record->timestamps[(i+1)] > 0) ? "," : ""); 172 | } 173 | 174 | printf(" ]\n }"); 175 | } 176 | 177 | /* 178 | list database entries 179 | this is also mostly meant for debugging and looking at what's in the DB 180 | */ 181 | static void list_db(void) { 182 | _pam_shield_db_rec_t *record; 183 | datum key, next_key, data; 184 | 185 | key = gdbm_firstkey(dbf); 186 | 187 | if (key.dptr == NULL) { 188 | printf("database is empty\n"); 189 | return; 190 | } 191 | 192 | printf("{\n \"db\": ["); 193 | 194 | while(key.dptr != NULL) { 195 | data = gdbm_fetch(dbf, key); 196 | 197 | if (data.dptr != NULL) { 198 | record = (_pam_shield_db_rec_t *)data.dptr; 199 | print_record(record); 200 | } 201 | next_key = gdbm_nextkey(dbf, key); 202 | free(key.dptr); 203 | key = next_key; 204 | if (data.dptr != NULL && key.dptr != NULL) { 205 | printf(","); 206 | } 207 | } 208 | 209 | printf("\n ]\n}\n"); 210 | } 211 | 212 | /* 213 | expire old entries from the database 214 | */ 215 | static void purge_db(void) { 216 | _pam_shield_db_rec_t *record; 217 | datum key, next_key, data; 218 | int deleted=0; /*If any key deleted, order changes; must revisit all keys*/ 219 | 220 | key = gdbm_firstkey(dbf); 221 | 222 | while(key.dptr != NULL) { 223 | data = gdbm_fetch(dbf, key); 224 | next_key = gdbm_nextkey(dbf, key); 225 | 226 | if (options & OPT_FORCE) { 227 | logmsg(LOG_DEBUG, "force-expiring entry"); 228 | if (!(options & OPT_DRYRUN)) { 229 | gdbm_delete(dbf, key); 230 | deleted=1; 231 | } 232 | } 233 | else if (data.dptr == NULL) { 234 | logmsg(LOG_DEBUG, "cleaning up empty key"); 235 | if (!(options & OPT_DRYRUN)) { 236 | gdbm_delete(dbf, key); 237 | deleted=1; 238 | } 239 | } else { 240 | record = (_pam_shield_db_rec_t *)data.dptr; 241 | 242 | /* store any changes */ 243 | if (expire_record(record)) { 244 | if (!record->count && !record->trigger_active) { 245 | logmsg(LOG_DEBUG, "expiring entry"); 246 | if (!(options & OPT_DRYRUN)) { 247 | gdbm_delete(dbf, key); 248 | deleted=1; 249 | } 250 | } else { 251 | logmsg(LOG_DEBUG, "storing updated entry"); 252 | if (!(options & OPT_DRYRUN)) 253 | gdbm_store(dbf, key, data, GDBM_REPLACE); 254 | } 255 | } 256 | free(data.dptr); 257 | } 258 | free(key.dptr); 259 | key = next_key; 260 | if (deleted && !key.dptr) { 261 | deleted=0; 262 | key = gdbm_firstkey(dbf); 263 | } 264 | } 265 | } 266 | 267 | /* 268 | remove ip from the database 269 | */ 270 | static int remove_ip(void) { 271 | _pam_shield_db_rec_t *record; 272 | datum key, next_key, data; 273 | int deleted=0; /*If any key deleted, order changes; must revisit all keys*/ 274 | char ipbuf[INET6_ADDRSTRLEN]; 275 | 276 | key = gdbm_firstkey(dbf); 277 | 278 | while(key.dptr != NULL) { 279 | data = gdbm_fetch(dbf, key); 280 | next_key = gdbm_nextkey(dbf, key); 281 | 282 | if (data.dptr == NULL) { 283 | logmsg(LOG_DEBUG, "cleaning up empty key"); 284 | if (!(options & OPT_DRYRUN)) { 285 | gdbm_delete(dbf, key); 286 | deleted=1; 287 | } 288 | } else { 289 | record = (_pam_shield_db_rec_t *)data.dptr; 290 | 291 | print_ip(record, ipbuf, INET6_ADDRSTRLEN); 292 | if (!strcmp(removeip, ipbuf)) { 293 | logmsg(LOG_DEBUG, "remove entry: %s", ipbuf); 294 | deleted=1; 295 | if (!(options & OPT_DRYRUN)) { 296 | record->trigger_active = (time_t)0L; 297 | run_trigger("del", record); 298 | gdbm_delete(dbf, key); 299 | } 300 | } 301 | free(data.dptr); 302 | } 303 | free(key.dptr); 304 | key = next_key; 305 | if (deleted && !key.dptr) { 306 | if (!(options & OPT_DRYRUN)) { 307 | key = gdbm_firstkey(dbf); 308 | } 309 | return 0; 310 | } 311 | } 312 | 313 | logmsg(LOG_ERR, "not found: %s", removeip); 314 | return 1; 315 | } 316 | 317 | int main(int argc, char **argv) { 318 | int retval = 0; 319 | init_module(); 320 | 321 | read_config(); 322 | get_options(argc, argv); 323 | 324 | this_time = time(NULL); 325 | 326 | if ((dbf = gdbm_open(dbfile, 512, GDBM_WRITER, (mode_t)0600, fatal_func)) == NULL) { 327 | logmsg(LOG_ERR, "failed to open db '%s' : %s", dbfile, gdbm_strerror(gdbm_errno)); 328 | return -1; 329 | } 330 | if (options & OPT_LISTDB) 331 | list_db(); 332 | else if (options & OPT_REMOVEIP) 333 | retval = remove_ip(); 334 | else 335 | purge_db(); 336 | 337 | gdbm_close(dbf); 338 | 339 | deinit_module(); 340 | return retval; 341 | } 342 | 343 | /* EOB */ 344 | --------------------------------------------------------------------------------