├── .gitignore ├── CHANGES ├── COPYING ├── Example ├── MANIFEST ├── README ├── dist └── pynids-0.6.2.tar.gz ├── libnids-1.25.tar.gz ├── nidsmodule.c └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | libnids-1.25 3 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | pynids 2 | 3 | Attribution: 4 | - Michael J. Pomraning 5 | JN - Jose Nazario 6 | RB - Richard Bejtlich 7 | JO - Jon Oberheide 8 | EC - Evan Cooke 9 | JF - Jeff Nathan 10 | WXS - Wesley Shields 11 | 12 | 18 Feb 2013 13 | - version 0.6.2 (WXS) 14 | - don't require libglib and libnet 15 | - bump bundled libnids to 1.25 (WXS) 16 | 17 | 9 May 2010 18 | - version 0.6.1 (JO) 19 | - bump bundled libnids to 1.24 (JO) 20 | - support for packet timestamps (JF) 21 | - support for pcap stats (JF) 22 | 23 | 9 Oct 2009 24 | - version 0.6 (JO) 25 | - bump bundled libnids to 1.23 (JO) 26 | - added -fPIC to fix builds (JO) 27 | 28 | 3 May 2007 29 | - version 0.5a (JO) 30 | - bump bundled libnids to 1.21 (JO) 31 | - add checksum control function (chksum_ctl) (EC) 32 | 33 | 31 Jan 2005 34 | - version 0.5 35 | - documented nids.param() variables. 36 | - documented TcpStream and HalfStream methods and members in module 37 | docstring. 38 | - added 'Prerequisites' to README. 39 | 40 | 27 Jan 2005 41 | - version 0.5rc1 42 | - setup.py nonstandard patch(1) invocation flummoxed FreeBSD (rpt. RB) 43 | 44 | 26 Jan 2005 45 | - version 0.4 46 | - docstrings added to module and module functions. 47 | - libnids-1.19 and ``nids_dispatch()'' patch bundled, allowing proper 48 | exception handling in callbacks all around. 49 | - API: 'dispatch' function added. Undocumented and subject to change if 50 | libnids adopts similar function. 51 | - misc: setup.py searches '/usr/local' by default. 52 | included. 53 | 54 | 24 Dec 2004 55 | - version 0.4rc1 56 | - makeRegisterFunc macro increfs user-supplied function. (On linux, at 57 | least, registration of bound methods would precipitate segfault.) 58 | - hs_get_data, hs_get_urgdata return strings now, not read-only buffers 59 | objects. 60 | 61 | 06 Dec 2004 62 | - version 0.3 63 | - API: 'pcap_timeout' parameter support for libnids >= 1.19 64 | - API: 'collect' and 'collect_urg' are boolean-ified 65 | - tp_getset/tp_method reimplementation of TcpStream and HalfStream 66 | - fix possible memory corruption (noticed by JN): hlfs->data member not 67 | guaranteed to be allocated (e.g., in a NIDS_JUST_EST conn). 68 | API: We treat as an empty buffer in this case. 69 | - misc: Py_RETURN_NONE, PyModule_AddIntConstant(), "N" fmt specifier in 70 | udp handler 71 | - updated README 72 | 73 | 05 Dec 2004 74 | - version 0.2 75 | - "filename" awareness in nids.param() (patch JN) 76 | - exception throwing retracted for next()/run(), after difficulty 77 | indicated by JN. next() now returns as does nids_next(), and run() 78 | returns None. 79 | - updated README, Example 80 | 81 | 13 Aug 2003 82 | - half-stream "data" member now raw PyBuffer 83 | - plug minor nids_param memleak 84 | - errbuf() retired in favor of module exception (nids.error). init, 85 | run, next and getfd modified accordingly 86 | - updated README 87 | 88 | 20 Jul 2003 89 | - version 0.1 90 | - mem: fix callTcpFunc() memleak, discretionary HalfStream ctor 91 | - run()/next() exception propagation 92 | - added Example, installation blurb 93 | 94 | 18 Jul 2003 95 | - complete register_*, remove some error-checking (!) 96 | - pytuple4 97 | - README, CHANGES 98 | 99 | 17 Jul 2003 100 | - init, run, next, discard, getfd, register_tcp 101 | - TcpStream objects, HalfStream objects 102 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 675 Mass Ave, Cambridge, MA 02139, 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 | Appendix: 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., 675 Mass Ave, Cambridge, MA 02139, USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) 19yy name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Library General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Example: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # pynids Example 4 | # $Id: Example,v 1.3 2005/01/27 04:53:45 mjp Exp $ 5 | 6 | import os, pwd 7 | import sys 8 | import nids 9 | 10 | NOTROOT = "nobody" # edit to taste 11 | end_states = (nids.NIDS_CLOSE, nids.NIDS_TIMEOUT, nids.NIDS_RESET) 12 | 13 | def handleTcpStream(tcp): 14 | print "tcps -", str(tcp.addr), " state:", tcp.nids_state 15 | if tcp.nids_state == nids.NIDS_JUST_EST: 16 | # new to us, but do we care? 17 | ((src, sport), (dst, dport)) = tcp.addr 18 | print tcp.addr 19 | if dport in (80, 8000, 8080): 20 | print "collecting..." 21 | tcp.client.collect = 1 22 | tcp.server.collect = 1 23 | elif tcp.nids_state == nids.NIDS_DATA: 24 | # keep all of the stream's new data 25 | tcp.discard(0) 26 | elif tcp.nids_state in end_states: 27 | print "addr:", tcp.addr 28 | print "To server:" 29 | print tcp.server.data[:tcp.server.count] # WARNING - may be binary 30 | print "To client:" 31 | print tcp.client.data[:tcp.client.count] # WARNING - as above 32 | 33 | def main(): 34 | 35 | #nids.param("pcap_filter", "tcp") # bpf restrict to TCP only, note 36 | # libnids caution about fragments 37 | 38 | nids.param("scan_num_hosts", 0) # disable portscan detection 39 | 40 | nids.chksum_ctl([('0.0.0.0/0', False)]) # disable checksumming 41 | 42 | if len(sys.argv) == 2: # read a pcap file? 43 | nids.param("filename", sys.argv[1]) 44 | 45 | nids.init() 46 | 47 | (uid, gid) = pwd.getpwnam(NOTROOT)[2:4] 48 | os.setgroups([gid,]) 49 | os.setgid(gid) 50 | os.setuid(uid) 51 | if 0 in [os.getuid(), os.getgid()] + list(os.getgroups()): 52 | print "error - drop root, please!" 53 | sys.exit(1) 54 | 55 | nids.register_tcp(handleTcpStream) 56 | print "pid", os.getpid() 57 | 58 | # Loop forever (network device), or until EOF (pcap file) 59 | # Note that an exception in the callback will break the loop! 60 | try: 61 | nids.run() 62 | except nids.error, e: 63 | print "nids/pcap error:", e 64 | except Exception, e: 65 | print "misc. exception (runtime error in user callback?):", e 66 | 67 | if __name__ == '__main__': 68 | main() 69 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | CHANGES 2 | COPYING 3 | Example 4 | libnids-1.25.tar.gz 5 | nidsmodule.c 6 | README 7 | setup.py -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | pynids is a python wrapper for libnids, a Network Intrusion Detection System 4 | library offering sniffing, IP defragmentation, TCP stream reassembly and TCP 5 | port scan detection. 6 | 7 | pynids is free software, copyright (C) 2003, 2004, 2005 Michael J. Pomraning 8 | . See the file COPYING for 9 | license information. 10 | 11 | Changes since 2013 copyright (c) The MITRE Corporation. 12 | 13 | libnids is (c) 1999 Rafal Wojtczuk and licensed under the 14 | GNU GPL. See http://www.packetfactory.net/projects/libnids/ for more 15 | information. 16 | 17 | Installation 18 | ============ 19 | 20 | Prerequisites 21 | ------------- 22 | Python >= 2.2 (www.python.org) 23 | libpcap (www.tcpdump.org) 24 | libnet (www.packetfactory.net/libnet) 25 | tar(1) and patch(1) 26 | 27 | libnids itself is supplied in the pynids distribution. 28 | 29 | Build and Install 30 | ----------------- 31 | $ python setup.py build 32 | $ python setup.py install 33 | 34 | API Translation 35 | =============== 36 | 37 | General 38 | ------- 39 | #include import nids 40 | 41 | extern nids_params nids.param(what [, new_val]) 42 | 43 | extern char nids_errbuf[] nids.error Exception instance 44 | 45 | struct tcp_stream TcpStream type 46 | nids_killtcp(tcp_s) tcpStreamObj.kill() 47 | nids_discard(tcp_s, 0) tcpStreamObj.discard(0) 48 | 49 | struct half_stream HalfStream type 50 | 51 | struct tuple4 ((src, sport), (dst, dport)) 52 | 53 | Callback Arguments 54 | ------------------ 55 | Packets and payloads are string buffers, whereas libnids-specific structs 56 | are their own types. Either bound methods or plain functions may be 57 | registered as callbacks -- their call signature differs only in the presence 58 | or absence of an initial 'self' argument. 59 | 60 | Examples of plain function callbacks: 61 | 62 | def ip_callback(pkt): 63 | pass 64 | 65 | def frag_callback(pkt): 66 | pass 67 | 68 | def udp_callback(addrs, payload, pkt): 69 | ((sip, sport), (dip, dport)) = addrs 70 | .... 71 | 72 | def tcp_callback(tcpStreamObj): 73 | clientHlf = tcpStreamObj.client 74 | serverHlf = tcpStreamObj.server 75 | 76 | Significant Differences 77 | ----------------------- 78 | - error handling (global nids_errbuf[], python exceptions) 79 | No function returns an error code; instead, a nids.error exception is 80 | raised for init() and getfd(). Unlike libnids, our next() function can 81 | detect pcap errors (again raised as nids.error). Exceptions in user 82 | callbacks will break either next() or run() calls. 83 | 84 | - nids_params (global settings) 85 | nids.param() handles accessing and changing libnet state variables; there 86 | is no object corresponding to a struct nids_prm. Some parameters are 87 | unimplemented -- see BUGS below. 88 | 89 | - half_stream members 90 | Only "collect" and "collect_urg" are mutable attributes. 91 | 92 | - Only one handler per register_* type 93 | Successive calls to, e.g., register_tcp() will simply replace the 94 | user-defined handle slotted for TCP packets. If you want multiple 95 | functions to process packet, implement your own: 96 | 97 | for f in tcp_func_list: 98 | f(tcp_s) 99 | 100 | - user_tcp_func(..., void **param) 101 | The user-controlled pointer has no analog in pynids. Programmers may 102 | store connection-specific data in a global dict, for example, keyed on 103 | tcpStream.addr. A bound methods registered as callbacks may of course 104 | access members of its corresponding object. 105 | 106 | - tuple4 structs 107 | IP addresses are represented as dotted quad strings, and tuple4 members 108 | are (re)arranged into a pair of two-tuples suitable for use as AF_INET 109 | addresses in the socket module. 110 | 111 | Significant Likenesses 112 | ---------------------- 113 | - pynids use, like libnids use, should be restricted to one and only one 114 | thread (static variables under the hood). See also "threads and GIL" in 115 | BUGS, below. 116 | 117 | - pcap_close() is apparently only called when nids_run() returns in libnids 118 | 1.18; beware fd leaks and inheritance. More generally, there is no way 119 | to de-initialize the underlying nids library (reclaiming memory allocated 120 | for stream reassembly, for instance). 121 | 122 | BUGS 123 | ==== 124 | 125 | - nids.param() 126 | . missing function hooks (syslog, no_mem, ip_filter) 127 | Should we just nail no_mem() down to throw nids.error and invalidate 128 | subsequent nids calls? 129 | . cannot distinguish between invalid members and char * members set to NULL; 130 | param("foo") and param("pcap_filter") could both return None, e.g. 131 | . missing type checking 132 | . implementation is awkward, comparable to old tp_getattr; better/easier as 133 | a module object with members/getsets. 134 | . invocation constrained; perhaps introduce a keyword function? 135 | 136 | - testing 137 | . generally insufficient (ip_fragments, tcpO.kill(), etc.) 138 | . memory profiling 139 | 140 | - threads and GIL 141 | libnids/libpcap routines have no knowledge of the python GIL, so pynids 142 | method calls will block other python threads. 143 | 144 | -------------------------------------------------------------------------------- /dist/pynids-0.6.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MITRECND/pynids/03ec9657d5ec6051f6ff1419fd13375c97d40cce/dist/pynids-0.6.2.tar.gz -------------------------------------------------------------------------------- /libnids-1.25.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MITRECND/pynids/03ec9657d5ec6051f6ff1419fd13375c97d40cce/libnids-1.25.tar.gz -------------------------------------------------------------------------------- /nidsmodule.c: -------------------------------------------------------------------------------- 1 | /* 2 | nidsmodule.c - C implementation of pynids 3 | Copyright (c) 2003 Michael J. Pomraning 4 | $Id: nidsmodule.c,v 1.11 2005/02/01 05:50:06 mjp Exp $ 5 | 6 | This file is part of the pynids package, a python interface to libnids. 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, USA 21 | */ 22 | 23 | #include "Python.h" 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #ifdef DEBUG 33 | #define DBG(f, ...) fprintf(stderr, f, ##__VA_ARGS__) 34 | #else 35 | #define DBG(p, ...) 36 | #endif /* DEBUG */ 37 | 38 | #ifndef Py_RETURN_NONE 39 | # define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None 40 | #endif /* Py_RETURN_NONE */ 41 | 42 | /* ====================================================================== */ 43 | /* Module Globals and Utility Functions */ 44 | /* ====================================================================== */ 45 | 46 | static PyObject *pynids_error; /* nids.error */ 47 | 48 | static int pynids_offline_read = 0; /* see nids.init(), nids.next() */ 49 | 50 | static PyObject *tcpFunc = NULL; 51 | static PyObject *udpFunc = NULL; 52 | static PyObject *ipFunc = NULL; 53 | static PyObject *fragFunc = NULL; 54 | 55 | static struct nids_prm origNidsParams; 56 | 57 | typedef struct { 58 | PyObject_HEAD 59 | struct tcp_stream *tcps; 60 | PyObject *client; 61 | PyObject *server; 62 | } TcpStream; 63 | 64 | typedef struct { 65 | PyObject_HEAD 66 | struct half_stream *hlfs; 67 | } HalfStream; 68 | 69 | staticforward PyTypeObject TcpStream_Type; 70 | staticforward PyTypeObject HalfStream_Type; 71 | 72 | /* wrapHalfStream used by TcpStream getter */ 73 | static HalfStream *wrapHalfStream(struct half_stream *); 74 | 75 | static char pynidsmodule__doc__[] = 76 | "A wrapper around the libnids Network Intrusion Detection library.\n\ 77 | \n\ 78 | Functions:\n\ 79 | \n\ 80 | param() -- set various libnids parameters\n\ 81 | init() -- open the capture stream, prepare internal \n\ 82 | getfd() -- return the file descriptor associated with the capture stream\n\ 83 | get_pkt_ts() -- return the timestamp of the most recently received packet\n\ 84 | get_pcap_stats() -- return num packets rcvd, num pkts dropped,\n\ 85 | num pkts dropped by interface as a tuple\n\ 86 | register_ip_frag() -- install a callback for IP fragment processing\n\ 87 | register_ip() -- install a callback for reassembled IP packet processing\n\ 88 | register_tcp() -- install a callback for reaseembled TCP packet processing\n\ 89 | register_udp() -- install a callback for reassembled UDP packet processing\n\ 90 | chksum_ctl() -- control whether packets are checksum by source address\n\ 91 | next() -- process one packet from the capture stream, invoking callbacks\n\ 92 | run() -- process all packets from the capture stream, invoking callbacks\n\ 93 | \n\ 94 | Special objects and classes:\n\ 95 | \n\ 96 | error -- exception raised for serious libnids/pcap errors\n\ 97 | TcpStream -- class of argument to TCP callback function. Features:\n\ 98 | addr -- Connection tuple: ((src, sport), (dst, dport))\n\ 99 | discard(n) -- purge n bytes from the data buffer\n\ 100 | kill() -- send symmetric RSTs to tear down the connection\n\ 101 | nids_state -- see 'Constants,' below\n\ 102 | client -- half of the connection; a TcpStream object\n\ 103 | server -- half of the connection; a TcpStream object\n\ 104 | HalfStream -- class of TcpStream 'client' and 'server' members. Features:\n\ 105 | collect -- boolean controlling whether data is collected\n\ 106 | collect_urg -- boolean controlling URG data collection\n\ 107 | count -- number of bytes appended to 'data' since creation\n\ 108 | count_new -- number of newly collected bytes in 'data'\n\ 109 | count_new_urg -- number of new urgent bytes\n\ 110 | data -- string buffer of normal (non-urgent) data\n\ 111 | urgdata -- one-byte string buffer\n\ 112 | offset -- offset of newly collected bytes in 'data'\n\ 113 | state -- [*] numeric socket state\n\ 114 | \n\ 115 | * TCP state constants (e.g., TCP_ESTABLISHED) are not supplied by pynids\n\ 116 | \n\ 117 | See the libnids documentation or the pynids README for more information\n\ 118 | on the TcpStream and HalfStream objects.\n\ 119 | \n\ 120 | Constants:\n\ 121 | \n\ 122 | NIDS_CLOSE, NIDS_DATA, NIDS_EXITING, NIDS_JUST_EST, NIDS_RESET,\n\ 123 | NIDS_TIMED_OUT, NIDS_TIMEOUT -- possible values of the 'nids_state' member\n\ 124 | of a TcpStream object.\n"; 125 | 126 | static PyObject * 127 | raisePynidsError(void) 128 | { 129 | extern char nids_errbuf[]; 130 | 131 | PyErr_SetString(pynids_error, nids_errbuf); 132 | return NULL; 133 | } 134 | 135 | /* nids_dispatch_exc() 136 | * 137 | * Like nids_dispatch, but setting a Python exception upon serious error. 138 | * Non-serious error conditions (timeout, EOF) do not raise an exception. 139 | * 140 | * args: none 141 | * rtrn: >0 on successfully processing one packet 142 | * 0 timeout or EOF 143 | * -1 exception thrown (either in user callback or in nids/pcap) 144 | * 145 | */ 146 | static int 147 | nids_dispatch_exc(int n) 148 | { 149 | int ret; 150 | 151 | DBG("nids_dispatch_exc(%d)\n", n); 152 | ret = nids_dispatch(n); 153 | if (ret == -1) { /* pcap error trumps user callback exception */ 154 | raisePynidsError(); 155 | return -1; 156 | } 157 | if (PyErr_Occurred()) return -1; /* check for callback exception */ 158 | return ret; 159 | } 160 | 161 | /* pytuple4(tuple4): ((src, sport), (dst, dport)) */ 162 | static PyObject * 163 | pytuple4(struct tuple4 *addr) 164 | { 165 | struct in_addr in; 166 | PyObject *t1, *t2, *ret; 167 | 168 | in.s_addr = addr->saddr; 169 | t1 = Py_BuildValue("si", inet_ntoa(in), addr->source); 170 | if (! t1) return NULL; 171 | 172 | in.s_addr = addr->daddr; 173 | t2 = Py_BuildValue("si", inet_ntoa(in), addr->dest); 174 | if (! t2) { 175 | Py_DECREF(t1); 176 | return NULL; 177 | } 178 | 179 | ret = Py_BuildValue("OO", t1, t2); 180 | Py_DECREF(t1); 181 | Py_DECREF(t2); 182 | return ret; 183 | } 184 | 185 | /* ====================================================================== */ 186 | /* pynids Object Implementation */ 187 | /* ====================================================================== */ 188 | 189 | /* TcpStream ctor -- called by callTcpFunc, not user */ 190 | static TcpStream * 191 | wrapTcpStream(struct tcp_stream *t) 192 | { 193 | TcpStream *self; 194 | self = PyObject_New(TcpStream, &TcpStream_Type); 195 | if (self == NULL) return NULL; 196 | self->tcps = t; 197 | self->client = NULL; 198 | self->server = NULL; 199 | DBG("TcpStream_ctor(%p)\n", self); 200 | /* 201 | * wrap half streams on demand, if demanded... 202 | self->client = (PyObject *) wrapHalfStream(&t->client); 203 | self->server = (PyObject *) wrapHalfStream(&t->server); 204 | */ 205 | return self; 206 | } 207 | 208 | static void 209 | TcpStream_dealloc(TcpStream *self) 210 | { 211 | DBG("TcpStream_dealloc(%p)\n", self); 212 | self->tcps = NULL; /* libnids will free this when approp. */ 213 | if (self->client) { 214 | Py_DECREF(self->client); 215 | self->client = NULL; 216 | } 217 | if (self->server) { 218 | Py_DECREF(self->server); 219 | self->server = NULL; 220 | } 221 | PyObject_Del(self); 222 | } 223 | 224 | /* ====================================================================== */ 225 | /* TcpStreams: ctor, dtor, methods, members and type */ 226 | /* ====================================================================== */ 227 | 228 | static PyObject * 229 | TcpStream_discard(TcpStream *self, PyObject *args) 230 | { 231 | int i; 232 | if (!PyArg_ParseTuple(args, "i:discard", &i)) 233 | return NULL; 234 | 235 | nids_discard(self->tcps, i); 236 | 237 | Py_RETURN_NONE; 238 | } 239 | 240 | static PyObject * 241 | TcpStream_kill(TcpStream *self, PyObject *args) 242 | { 243 | if (!PyArg_ParseTuple(args, ":kill")) return NULL; 244 | 245 | nids_killtcp(self->tcps); 246 | 247 | Py_RETURN_NONE; 248 | } 249 | 250 | static PyMethodDef TcpStream_methods[] = { 251 | {"discard", (PyCFunction)TcpStream_discard, METH_VARARGS}, 252 | {"kill", (PyCFunction)TcpStream_kill, METH_VARARGS}, 253 | {NULL, NULL} /* sentinel */ 254 | }; 255 | 256 | #define TS_GET_HLFS(ATTR) \ 257 | static PyObject * ts_get_##ATTR(TcpStream *self, void *unused) { \ 258 | if (!self->ATTR) { \ 259 | self->ATTR = (PyObject *) wrapHalfStream(&self->tcps->ATTR); \ 260 | if (!self->ATTR) return NULL; \ 261 | } \ 262 | Py_INCREF(self->ATTR); \ 263 | return self->ATTR; \ 264 | } 265 | 266 | /* RO attributes */ 267 | TS_GET_HLFS(client) 268 | TS_GET_HLFS(server) 269 | static PyObject * 270 | ts_get_addr(TcpStream *self, void *unused) { 271 | return pytuple4(&self->tcps->addr); 272 | } 273 | static PyObject * 274 | ts_get_nids_state(TcpStream *self, void *unused) { 275 | return PyInt_FromLong((long)self->tcps->nids_state); 276 | } 277 | 278 | static PyGetSetDef TcpStream_getsets[] = { 279 | {"client", (getter)ts_get_client}, 280 | {"server", (getter)ts_get_server}, 281 | {"addr", (getter)ts_get_addr}, 282 | {"nids_state", (getter)ts_get_nids_state}, 283 | {NULL} /* Sentinel */ 284 | }; 285 | 286 | statichere PyTypeObject TcpStream_Type = { 287 | /* The ob_type field must be initialized in the module init function 288 | * to be portable to Windows without using C++. */ 289 | PyObject_HEAD_INIT(NULL) 290 | 0, /*ob_size*/ 291 | "TcpStream", /*tp_name*/ 292 | sizeof(TcpStream), /*tp_basicsize*/ 293 | 0, /*tp_itemsize*/ 294 | /* methods */ 295 | (destructor)TcpStream_dealloc, /*tp_dealloc*/ 296 | 0, /*tp_print*/ 297 | 0, /*tp_getattr*/ 298 | 0, /*tp_setattr*/ 299 | 0, /*tp_compare*/ 300 | 0, /*tp_repr*/ 301 | 0, /*tp_as_number*/ 302 | 0, /*tp_as_sequence*/ 303 | 0, /*tp_as_mapping*/ 304 | 0, /*tp_hash*/ 305 | 0, /*tp_call*/ 306 | 0, /*tp_str*/ 307 | PyObject_GenericGetAttr, /*tp_getattro*/ 308 | 0, /*tp_setattro*/ 309 | 0, /*tp_as_buffer*/ 310 | Py_TPFLAGS_HAVE_CLASS, /*tp_flags*/ 311 | 0, /*tp_doc*/ 312 | 0, /*tp_traverse*/ 313 | 0, /*tp_clear*/ 314 | 0, /*tp_richcompare*/ 315 | 0, /*tp_weaklistoffset*/ 316 | 0, /*tp_iter*/ 317 | 0, /*tp_iternext*/ 318 | TcpStream_methods, /*tp_methods*/ 319 | 0, /*tp_members*/ 320 | TcpStream_getsets, /*tp_getset*/ 321 | 0, /*tp_base*/ 322 | }; 323 | 324 | /* ====================================================================== */ 325 | /* HalfStreams: ctor, dtor, methods, members and type */ 326 | /* ====================================================================== */ 327 | 328 | static HalfStream * 329 | wrapHalfStream(struct half_stream *h) { /* called by TcpStream ctor */ 330 | HalfStream *self; 331 | self = PyObject_New(HalfStream, &HalfStream_Type); 332 | if (self == NULL) return NULL; 333 | self->hlfs = h; 334 | DBG("HalfStream_ctor(%p)\n", self); 335 | return self; 336 | } 337 | 338 | static void 339 | HalfStream_dealloc(HalfStream *self) { 340 | DBG("HalfStream_dealloc(%p, %d)\n", self, self->ob_refcnt); 341 | self->hlfs = NULL; 342 | PyObject_Del(self); 343 | } 344 | 345 | #define HS_GET_INT(ATTR) \ 346 | static PyObject * hs_get_##ATTR(HalfStream *self, void *unused) { \ 347 | return PyInt_FromLong((long)self->hlfs->ATTR); \ 348 | } 349 | 350 | /* FIXME - true bool support */ 351 | #define HS_GET_BOOL(ATTR) \ 352 | static PyObject * hs_get_##ATTR(HalfStream *self, void *unused) { \ 353 | return PyInt_FromLong(self->hlfs->ATTR ? 1L : 0L); \ 354 | } 355 | 356 | #define HS_SET_BOOL(ATTR) \ 357 | static int hs_set_##ATTR(HalfStream *self, PyObject *val, void *closure) { \ 358 | if (val == NULL) { \ 359 | PyErr_SetString(PyExc_TypeError, \ 360 | "Cannot delete the " #ATTR "attribute"); \ 361 | return -1; \ 362 | } \ 363 | DBG("hs_set_" #ATTR "(HalfStream * %p, bool %d)\n", \ 364 | self, PyObject_IsTrue(val)); \ 365 | self->hlfs->ATTR = PyObject_IsTrue(val); \ 366 | return 0; /* success */ \ 367 | } 368 | 369 | /* RW attributes */ 370 | HS_GET_BOOL(collect) 371 | HS_SET_BOOL(collect) 372 | HS_GET_BOOL(collect_urg) 373 | HS_SET_BOOL(collect_urg) 374 | /* RO attributes */ 375 | HS_GET_INT(state) 376 | static PyObject *hs_get_data(HalfStream *self, void *unused) { 377 | /* data may not be allocated if the conn/libnids has seen no data */ 378 | if (! self->hlfs->data) return PyString_FromStringAndSize("", 0); 379 | /* bufsize is an undocumented member */ 380 | return PyString_FromStringAndSize(self->hlfs->data, self->hlfs->bufsize); 381 | } 382 | static PyObject *hs_get_urgdata(HalfStream *self, void *unused) { 383 | /* u_char urgdata */ 384 | return PyString_FromStringAndSize(&(self->hlfs->urgdata), 385 | sizeof(self->hlfs->urgdata)); 386 | } 387 | HS_GET_INT(count) 388 | HS_GET_INT(offset) 389 | HS_GET_INT(count_new) 390 | HS_GET_INT(count_new_urg) 391 | 392 | static PyGetSetDef HalfStream_getsets[] = { 393 | {"state", (getter)hs_get_state}, 394 | {"collect", (getter)hs_get_collect, (setter)hs_set_collect}, 395 | {"collect_urg", (getter)hs_get_collect_urg, (setter)hs_set_collect_urg}, 396 | {"data", (getter)hs_get_data}, 397 | {"urgdata", (getter)hs_get_urgdata}, 398 | {"count", (getter)hs_get_count}, 399 | {"offset", (getter)hs_get_offset}, 400 | {"count_new", (getter)hs_get_count_new}, 401 | {"count_new_urg", (getter)hs_get_count_new_urg}, 402 | {NULL} /* Sentinel */ 403 | }; 404 | 405 | statichere PyTypeObject HalfStream_Type = { 406 | /* The ob_type field must be initialized in the module init function 407 | * to be portable to Windows without using C++. */ 408 | PyObject_HEAD_INIT(NULL) 409 | 0, /*ob_size*/ 410 | "HalfStream", /*tp_name*/ 411 | sizeof(HalfStream), /*tp_basicsize*/ 412 | 0, /*tp_itemsize*/ 413 | /* methods */ 414 | (destructor)HalfStream_dealloc, /*tp_dealloc*/ 415 | 0, /*tp_print*/ 416 | 0, /*tp_getattr*/ 417 | 0, /*tp_setattr*/ 418 | 0, /*tp_compare*/ 419 | 0, /*tp_repr*/ 420 | 0, /*tp_as_number*/ 421 | 0, /*tp_as_sequence*/ 422 | 0, /*tp_as_mapping*/ 423 | 0, /*tp_hash*/ 424 | 0, /*tp_call*/ 425 | 0, /*tp_str*/ 426 | PyObject_GenericGetAttr, /*tp_getattro*/ 427 | PyObject_GenericSetAttr, /*tp_setattro*/ 428 | 0, /*tp_as_buffer*/ 429 | Py_TPFLAGS_HAVE_CLASS, /*tp_flags*/ 430 | 0, /*tp_doc*/ 431 | 0, /*tp_traverse*/ 432 | 0, /*tp_clear*/ 433 | 0, /*tp_richcompare*/ 434 | 0, /*tp_weaklistoffset*/ 435 | 0, /*tp_iter*/ 436 | 0, /*tp_iternext*/ 437 | 0, /*tp_methods*/ 438 | 0, /*tp_members*/ 439 | HalfStream_getsets, /*tp_getset*/ 440 | 0, /*tp_base*/ 441 | }; 442 | 443 | /* ====================================================================== */ 444 | /* User-Defined libnids Handler Hooks */ 445 | /* ====================================================================== */ 446 | 447 | static void 448 | callTcpFunc(struct tcp_stream *ts, void **param) 449 | { 450 | PyObject *ret = NULL; 451 | TcpStream *tso = NULL; 452 | 453 | DBG("callTcpFunc - init tso\n"); 454 | tso = wrapTcpStream(ts); 455 | if (! tso) return; 456 | 457 | DBG("callTcpFunc - call func %p(%p)\n", tcpFunc, tso); 458 | ret = PyObject_CallFunction(tcpFunc, "O", tso); 459 | 460 | DBG("callTcpFunc - dealloc tso (ret: %p)\n", ret); 461 | Py_DECREF(tso); 462 | if (ret) { 463 | Py_DECREF(ret); 464 | } 465 | return; 466 | } 467 | 468 | static void 469 | callUdpFunc(struct tuple4 *addr, u_char *data, int len, struct ip *pkt) 470 | { 471 | PyObject *ret = NULL; 472 | 473 | DBG("callUdpFunc...\n"); 474 | ret = PyObject_CallFunction(udpFunc, "(Ns#s#)", 475 | pytuple4(addr), 476 | data, len, 477 | pkt, ntohs(pkt->ip_len)); 478 | if (ret) { 479 | Py_DECREF(ret); 480 | } 481 | return; 482 | } 483 | 484 | static void 485 | callIpFunc(struct ip *pkt) 486 | { 487 | PyObject *ret = NULL; 488 | 489 | DBG("callIpFunc...\n"); 490 | ret = PyObject_CallFunction(ipFunc, "s#", pkt, ntohs(pkt->ip_len)); 491 | if (ret) { 492 | Py_DECREF(ret); 493 | } 494 | return; 495 | } 496 | 497 | static void 498 | callFragFunc(struct ip *pkt) 499 | { 500 | PyObject *ret = NULL; 501 | 502 | ret = PyObject_CallFunction(fragFunc, "s#", pkt, ntohs(pkt->ip_len)); 503 | if (ret) { 504 | Py_DECREF(ret); 505 | } 506 | return; 507 | } 508 | 509 | /* makeRegisterFunc(type, static PyFunction ptr, dispatch) 510 | * 511 | * what PyObject * Invoker 512 | * ----------------------------------- 513 | * tcp tcpFunc callTcpFunc 514 | * udp udpFunc callUdpFunc 515 | * ip ipFunc callIpFunc 516 | * ip_frag fragFunc callFragFunc 517 | */ 518 | 519 | #define makeRegisterFunc(WHAT, FP, PYDISPATCH) \ 520 | \ 521 | static char pynids_register_##WHAT##__doc__[] = \ 522 | "register_" #WHAT "(func) -> None\n" \ 523 | "\n" \ 524 | "Register the given user-defined function as a callback handler.\n"; \ 525 | \ 526 | static PyObject * \ 527 | pynids_register_##WHAT (PyObject *na, PyObject *args) \ 528 | { \ 529 | PyObject *pyFunc = NULL; \ 530 | if (!PyArg_ParseTuple(args, "O:register_" #WHAT, &pyFunc)) \ 531 | return NULL; \ 532 | \ 533 | if (FP != NULL) { \ 534 | /* (re-)set single, global func ptr */ \ 535 | PyObject_Del(FP); \ 536 | } \ 537 | nids_register_##WHAT(PYDISPATCH); \ 538 | DBG("Inside register_" #WHAT "(%p)\n", pyFunc); \ 539 | FP = pyFunc; \ 540 | Py_INCREF(FP); \ 541 | Py_RETURN_NONE; \ 542 | } 543 | 544 | /* What PyFunc * C-level Dispatch */ 545 | /* ---- -------- ---------------- */ 546 | 547 | makeRegisterFunc(tcp, tcpFunc, callTcpFunc); 548 | makeRegisterFunc(udp, udpFunc, callUdpFunc); 549 | makeRegisterFunc(ip, ipFunc, callIpFunc); 550 | makeRegisterFunc(ip_frag, fragFunc, callFragFunc); 551 | 552 | /* ====================================================================== */ 553 | /* Module Functions */ 554 | /* ====================================================================== */ 555 | 556 | static char pynids_chksum_ctl__doc__[] = 557 | "chksum_ctl([(addr1, True), (addr2, False)], ...) -> None\n\ 558 | \n\ 559 | takes as arguments an list of tuples where a tuple should have the\n\ 560 | following format:\n\ 561 | (Source address in CIDR format, Boolean whether to apply checksum)\n\ 562 | e.g. (\"192.168.1.10/24\", True)\n\ 563 | Internal checksumming functions will first check elements of this\n\ 564 | list one by one, and if the source ip of the current packet\n\ 565 | matches the source address and mask of a tuple then the packet with either\n\ 566 | be checksummed if the apply boolean is set to True, or not checksummed if\n\ 567 | the boolean is set to False. If the packet matches none of the list\n\ 568 | elements, the default action is to perform checksumming.\n"; 569 | 570 | static int 571 | _parse_prefix(char *prefix, u_int *netaddr, u_int *mask) 572 | { 573 | struct in_addr in; 574 | char *ptr, *data; 575 | u_int m; 576 | 577 | /* eat up white space */ 578 | data = prefix; 579 | while (*data == ' ' && *data == '\t') 580 | data++; 581 | 582 | /* find end */ 583 | ptr = data; 584 | while (*ptr != '/' && *ptr != '\n' && *ptr != '\0') 585 | ptr++; 586 | 587 | if (*ptr == '/') 588 | { 589 | *ptr = '\0'; 590 | ptr++; 591 | 592 | /* convert the ip to binary */ 593 | if (inet_pton(AF_INET, data, &in) < 0) 594 | { 595 | PyErr_SetFromErrno(PyExc_OSError); 596 | return -1; 597 | } 598 | *netaddr = in.s_addr; 599 | 600 | /* get mask */ 601 | m = 32 - atoi(ptr); 602 | *mask = (m >= 32) ? 0 : htonl((0xffffffff >> m) << m); 603 | } 604 | else if (strlen(data) >= 7) 605 | { 606 | /* convert the ip to binary */ 607 | if (inet_pton(AF_INET, data, &in) < 0) 608 | { 609 | PyErr_SetFromErrno(PyExc_OSError); 610 | return -1; 611 | } 612 | *netaddr = in.s_addr; 613 | *mask = 0xffffffff; 614 | } 615 | 616 | return 0; 617 | } 618 | 619 | static int 620 | _parse_chksum_tuple(struct nids_chksum_ctl *ctl, int i, PyObject *tuple) 621 | { 622 | PyObject *addr, *action; 623 | 624 | addr = PyTuple_GET_ITEM(tuple, 0); 625 | if (PyString_Check(addr) <= 0) 626 | { 627 | PyErr_SetString(PyExc_TypeError, 628 | "in (cidr_address, action) cidr_address must be string"); 629 | return -1; 630 | } 631 | if (_parse_prefix(PyString_AS_STRING(addr), &ctl[i].netaddr, 632 | &ctl[i].mask) < 0) 633 | return -1; 634 | 635 | action = PyTuple_GET_ITEM(tuple, 1); 636 | if (PyBool_Check(action) <= 0) 637 | { 638 | PyErr_SetString(PyExc_TypeError, 639 | "in (cidr_address, action) action must be boolean"); 640 | return -1; 641 | } 642 | if (action == Py_False) 643 | ctl[i].action = NIDS_DONT_CHKSUM; 644 | else 645 | ctl[i].action = NIDS_DO_CHKSUM; 646 | 647 | return 0; 648 | } 649 | 650 | static PyObject * 651 | pynids_chksum_ctl(PyObject *na, PyObject *args) 652 | { 653 | PyObject *items, *tuple; 654 | int i, n; 655 | struct nids_chksum_ctl *ctl = NULL; 656 | 657 | /* parse args */ 658 | if (!PyArg_ParseTuple(args, "O:chksum_ctl", &items)) 659 | return NULL; 660 | 661 | /* parse list of address/action tuples */ 662 | if (PyList_Check(items) > 0) 663 | { 664 | n = PyList_Size(items); 665 | ctl = (struct nids_chksum_ctl *) \ 666 | malloc(sizeof(struct nids_chksum_ctl) * n); 667 | if (ctl == NULL) 668 | { 669 | PyErr_SetString(PyExc_OSError, 670 | "could not allocate temp memory storage"); 671 | return NULL; 672 | } 673 | for (i=0; i old_value\n\ 703 | \n\ 704 | If new_value is specified, set the named nids attribute to the new value.\n\ 705 | Returns the previous value in any case. Supported parameters and their\n\ 706 | defaults are:\n\ 707 | \n\ 708 | device -- network device to use as input (None); see also 'filename'\n\ 709 | filename -- pcap filename to use as input (None); see also 'device'\n\ 710 | dev_addon -- number of bytes in struct sk_buff for layer 2 info (-1)\n\ 711 | one_loop_less -- undocumented\n\ 712 | n_hosts -- size of IP defragmentation info hash table (256)\n\ 713 | n_tcp_streams -- size of TCP connection hash table (1024)\n\ 714 | pcap_filter -- pcap filter string applied to unassembled packets (None)\n" 715 | #if (NIDS_MAJOR > 1 || (NIDS_MAJOR == 1 && NIDS_MINOR >= 19)) 716 | "pcap_timeout -- pcap capture timeout, in milliseconds (1024)\n" 717 | #endif /* libnids >= 1.19 */ 718 | "promisc -- non-zero if promiscuous mode is desired on capture device (1)\n\ 719 | sk_buff_size -- size of struct skbuff, used for queueing packets (168)\n\ 720 | syslog_level -- log level used when syslogging events (LOG_ALERT)\n\ 721 | scan_num_hosts -- hash table size for portscan detection (256)\n\ 722 | scan_num_ports -- minimum ports per src. host to qualify as a portscan (10)\n\ 723 | scan_delay -- maximum delay in milliseconds between (3000)\n\ 724 | tcp_flow_timeout -- timeout in seconds to distinguish flows with sample tuple\n\ 725 | \n\ 726 | Either 'device' or 'filename' must be specified before calling nids_init().\n\ 727 | Portscan detection may be disabled by setting 'scan_num_hosts' to zero. See\n\ 728 | the libnids documentation for more details.\n"; 729 | 730 | static PyObject * 731 | pynids_param(PyObject *na, PyObject *args) 732 | { 733 | PyObject *v = NULL; 734 | PyObject *ret = NULL; 735 | int *int_p = NULL; 736 | char **char_pp = NULL; 737 | char *name; 738 | 739 | if (!PyArg_ParseTuple(args, "s|O", &name, &v)) return NULL; 740 | 741 | /* is it an int parameter? */ 742 | if (!strcmp(name, "n_tcp_streams")) 743 | int_p = &nids_params.n_tcp_streams; 744 | else if (!strcmp(name, "n_hosts")) 745 | int_p = &nids_params.n_hosts; 746 | else if (!strcmp(name, "sk_buff_size")) 747 | int_p = &nids_params.sk_buff_size; 748 | else if (!strcmp(name, "dev_addon")) 749 | int_p = &nids_params.dev_addon; 750 | else if (!strcmp(name, "syslog_level")) 751 | int_p = &nids_params.syslog_level; 752 | else if (!strcmp(name, "scan_num_hosts")) 753 | int_p = &nids_params.scan_num_hosts; 754 | else if (!strcmp(name, "scan_num_ports")) 755 | int_p = &nids_params.scan_num_ports; 756 | else if (!strcmp(name, "scan_delay")) 757 | int_p = &nids_params.scan_delay; 758 | else if (!strcmp(name, "promisc")) 759 | int_p = &nids_params.promisc; 760 | else if (!strcmp(name, "one_loop_less")) 761 | int_p = &nids_params.one_loop_less; 762 | #if (NIDS_MAJOR > 1 || (NIDS_MAJOR == 1 && NIDS_MINOR >= 19)) 763 | else if (!strcmp(name, "pcap_timeout")) 764 | int_p = &nids_params.pcap_timeout; 765 | #endif /* libnids >= 1.19 */ 766 | else if (!strcmp(name, "tcp_flow_timeout")) 767 | int_p = &nids_params.tcp_flow_timeout; 768 | 769 | if (int_p) { 770 | /* FIXME - type check val for intishness */ 771 | ret = PyInt_FromLong((long) *int_p); 772 | if (v) *int_p = (int) PyInt_AsLong(v); 773 | return ret; 774 | } 775 | 776 | /* is it a char * param? */ 777 | if (!strcmp(name, "device")) 778 | char_pp = &nids_params.device; 779 | else if (!strcmp(name, "pcap_filter")) 780 | char_pp = &nids_params.pcap_filter; 781 | else if (!strcmp(name, "filename")) 782 | char_pp = &nids_params.filename; 783 | 784 | if (char_pp) { 785 | /* XXX - error checking, PyMem alloc/free */ 786 | ret = Py_BuildValue("s", *char_pp); 787 | if (v) { 788 | /* free previous strdup -- fortunately libnids inits these to 789 | * NULL... 790 | */ 791 | if (*char_pp) free(*char_pp); 792 | *char_pp = (v == Py_None) ? NULL : strdup(PyString_AsString(v)); 793 | } 794 | return ret; 795 | } 796 | 797 | /****** 798 | if (!strcmp(name, "syslog")) 799 | func_pp = &nids.syslog; 800 | else if (!strcmp(name, "ip_filter")) 801 | func_pp = &nids.ip_filter; 802 | else if (!strcmp(name, "no_mem")) 803 | func_pp = &nids.no_mem; 804 | ******/ 805 | 806 | Py_RETURN_NONE; 807 | } 808 | 809 | static char pynids_getfd__doc__[] = 810 | "getfd() -> fd\n\ 811 | \n\ 812 | Returns the integral file descriptor of the live capture device or pcap\n\ 813 | savefile specified during the call to init(). The resultant fd is suitable\n\ 814 | for I/O polling with select.select(), for example.\n"; 815 | 816 | static PyObject * 817 | pynids_getfd(PyObject *na, PyObject *args) 818 | { 819 | int pcap_fd; 820 | 821 | if (!PyArg_ParseTuple(args, ":getfd")) return NULL; 822 | 823 | if ((pcap_fd = nids_getfd()) == -1) return raisePynidsError(); 824 | return PyInt_FromLong((long) pcap_fd); 825 | } 826 | 827 | static char pynids_next__doc__[] = 828 | "next() -> r\n\ 829 | \n\ 830 | Attempt to process one packet, returning 1 if a packet was processed and 0\n\ 831 | on timeout or EOF, as appropriate to the capture stream. Serious errors in\n\ 832 | pcap raise a nids.error exception.\n"; 833 | 834 | static PyObject * 835 | pynids_next(PyObject *na, PyObject *args) 836 | { 837 | int ret; 838 | 839 | if (!PyArg_ParseTuple(args, ":next")) return NULL; 840 | 841 | ret = nids_dispatch_exc(1); 842 | if (PyErr_Occurred()) return NULL; /* python callback error */ 843 | 844 | return PyInt_FromLong((long) ret); 845 | } 846 | 847 | static char pynids_dispatch__doc__[] = 848 | "dispatch(cnt) -> processed\n\ 849 | \n\ 850 | UNDOCUMENTED -- this function does not exist in libnids <= 1.19.\n"; 851 | 852 | static PyObject * 853 | pynids_dispatch(PyObject *na, PyObject *args) 854 | { 855 | int ret, cnt; 856 | 857 | if (!PyArg_ParseTuple(args, "i:dispatch", &cnt)) return NULL; 858 | 859 | ret = nids_dispatch_exc(cnt); 860 | if (ret == -1) return NULL; 861 | 862 | return PyInt_FromLong((long) ret); 863 | } 864 | 865 | static char pynids_run__doc__[] = 866 | "run() -> None\n\ 867 | \n\ 868 | On a live capture, process packets ad infinitum; on an offline read, process\n\ 869 | packets until EOF. In either case, an exception thrown in a user callback\n\ 870 | or in nids/pcap (as nids.error) may abort processing.\n"; 871 | 872 | static PyObject * 873 | pynids_run(PyObject *na, PyObject *args) 874 | { 875 | int r; 876 | 877 | if (!PyArg_ParseTuple(args, ":run")) return NULL; 878 | 879 | if (pynids_offline_read) { 880 | /* read until EOF, checking for exceptions along the way */ 881 | do { r = nids_dispatch_exc(1); } while (r > 0); 882 | } else { 883 | /* read forever, checking for exceptions along the way */ 884 | do { r = nids_dispatch_exc(1); } while (r >= 0); 885 | } 886 | 887 | if (r == -1) return NULL; 888 | 889 | #if 0 890 | if (r != 0) runtime error! 891 | #endif 892 | 893 | Py_RETURN_NONE; 894 | } 895 | 896 | static char pynids_init__doc__[] = 897 | "init() -> None\n\ 898 | \n\ 899 | Initialize the nids library, as specified by previous calls to param(). In\n\ 900 | particular, the capture device 'device' or pcap savefile 'filename' is\n\ 901 | opened, the 'pcap_filter' compiled, and various internal mechanisms prepared.\n\ 902 | \n\ 903 | It is appropriate and recommended to drop process privileges after making\n\ 904 | this call.\n"; 905 | 906 | static PyObject * 907 | pynids_init(PyObject *na, PyObject *args) 908 | { 909 | int ok; 910 | if (!PyArg_ParseTuple(args, ":init")) return NULL; 911 | 912 | ok = nids_init(); 913 | if (! ok) return raisePynidsError(); 914 | if (nids_params.filename) pynids_offline_read = 1; 915 | else pynids_offline_read = 0; 916 | 917 | Py_RETURN_NONE; 918 | } 919 | 920 | static char pynids_get_pkt_ts__doc__[] = 921 | "get_pkt_time() -> float\n\ 922 | \n\ 923 | Returns the timestamp of the most recent packet as a float.\n"; 924 | 925 | static PyObject * 926 | pynids_get_pkt_ts(PyObject *na, PyObject *args) 927 | { 928 | double pkt_time; 929 | 930 | if (!PyArg_ParseTuple(args, ":get_pkt_ts")) return NULL; 931 | 932 | pkt_time = nids_last_pcap_header->ts.tv_sec + 933 | (nids_last_pcap_header->ts.tv_usec / 1000000.0); 934 | return PyFloat_FromDouble(pkt_time); 935 | } 936 | 937 | static char pynids_get_pcap_stats__doc__[] = 938 | "get_pcap_stats() -> tuple\n\ 939 | \n\ 940 | Returns the pcap recv, drop and interface drop statistics as a tuple.\n"; 941 | 942 | static PyObject * 943 | pynids_get_pcap_stats(PyObject *na, PyObject *args) 944 | { 945 | static struct pcap_stat ps; 946 | PyObject *pcap_stats_tuple; 947 | 948 | if (!PyArg_ParseTuple(args, ":get_pcap_stats")) return NULL; 949 | 950 | if (nids_params.pcap_desc == NULL || 951 | pcap_stats(nids_params.pcap_desc, &ps) != 0) { 952 | raisePynidsError(); 953 | return NULL; 954 | } 955 | 956 | pcap_stats_tuple = Py_BuildValue("III", ps.ps_recv, ps.ps_drop, 957 | ps.ps_ifdrop); 958 | 959 | if (! pcap_stats_tuple) return NULL; 960 | 961 | return pcap_stats_tuple; 962 | } 963 | 964 | /* List of functions defined in the module */ 965 | 966 | #define mkMethod(x) \ 967 | {#x, pynids_##x, METH_VARARGS, pynids_##x##__doc__} 968 | 969 | static PyMethodDef pynids_methods[] = { 970 | mkMethod(run), 971 | mkMethod(dispatch), 972 | mkMethod(getfd), 973 | mkMethod(next), 974 | mkMethod(register_tcp), 975 | mkMethod(register_udp), 976 | mkMethod(register_ip), 977 | mkMethod(register_ip_frag), 978 | mkMethod(chksum_ctl), 979 | mkMethod(init), 980 | mkMethod(param), 981 | mkMethod(get_pkt_ts), 982 | mkMethod(get_pcap_stats), 983 | {NULL, NULL} /* sentinel */ 984 | }; 985 | 986 | #undef mkMethod 987 | 988 | /* ====================================================================== */ 989 | /* Module Initialization */ 990 | /* ====================================================================== */ 991 | 992 | DL_EXPORT(void) 993 | initnids(void) 994 | { 995 | PyObject *m; 996 | 997 | /* Initialize the type of the new type object here; doing it here 998 | * is required for portability to Windows without requiring C++. */ 999 | TcpStream_Type.ob_type = &PyType_Type; 1000 | HalfStream_Type.ob_type = &PyType_Type; 1001 | 1002 | /* Create the module and add the functions */ 1003 | m = Py_InitModule3("nids", pynids_methods, pynidsmodule__doc__); 1004 | 1005 | /* Initialize, add our exception object */ 1006 | pynids_error = PyErr_NewException("nids.error", NULL, NULL); 1007 | Py_INCREF(pynids_error); 1008 | PyModule_AddObject(m, "error", pynids_error); 1009 | 1010 | /* Add versioning info */ 1011 | PyModule_AddStringConstant(m, "__version__", "0.6.2"); 1012 | PyModule_AddObject(m, "__nids_version__", 1013 | PyString_FromFormat("%d.%d", NIDS_MAJOR, NIDS_MINOR)); 1014 | 1015 | /* Add NIDS_ symbolic constants to the module */ 1016 | #define setConst(CONST) \ 1017 | PyModule_AddIntConstant(m, #CONST, CONST) 1018 | 1019 | setConst(NIDS_JUST_EST); 1020 | setConst(NIDS_DATA); 1021 | setConst(NIDS_CLOSE); 1022 | setConst(NIDS_RESET); 1023 | setConst(NIDS_TIMED_OUT); 1024 | #ifndef NIDS_TIMEOUT 1025 | #define NIDS_TIMEOUT NIDS_TIMED_OUT 1026 | #endif 1027 | setConst(NIDS_TIMEOUT); /* compat. w/ manpage */ 1028 | setConst(NIDS_EXITING); 1029 | 1030 | /* Save the original nids_params */ 1031 | origNidsParams = nids_params; 1032 | } 1033 | 1034 | /* 1035 | * vim:noet:ts=4: 1036 | */ 1037 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # setup.py - Distutils instructions for the pynids package 4 | 5 | # This file is part of the pynids package, a python interface to libnids. 6 | # See the file COPYING for license information. 7 | 8 | from distutils.core import setup, Extension 9 | from distutils.command.build import build # nidsMaker 10 | from distutils.spawn import spawn # nidsMaker.run() 11 | import os, os.path 12 | 13 | pathjoin = os.path.join 14 | 15 | PKGNAME = 'libnids-1.25' 16 | PKGTAR = PKGNAME + '.tar.gz' 17 | BUILDDIR = PKGNAME 18 | 19 | INCLUDE_DIRS = ['/usr/local/include', '/opt/local/include'] 20 | LIBRARY_DIRS = ['/usr/local/lib', '/opt/local/lib'] 21 | EXTRA_OBJECTS = [] 22 | 23 | class nidsMaker(build): 24 | NIDSTAR = PKGTAR 25 | NIDSDIR = BUILDDIR 26 | include_dirs = [ pathjoin(NIDSDIR, 'src') ] 27 | library_dirs = [] 28 | extra_objects = [ pathjoin(NIDSDIR, 'src', 'libnids.a') ] 29 | 30 | def buildNids(self): 31 | # extremely crude package builder 32 | try: 33 | os.stat(self.NIDSDIR) 34 | return None # assume already built 35 | except OSError: 36 | pass 37 | 38 | spawn(['tar', '-zxf', self.NIDSTAR], search_path = 1) 39 | os.chdir(self.NIDSDIR) 40 | spawn([pathjoin('.','configure'), 'CFLAGS=-fPIC', '--disable-libglib', '--disable-libnet']) 41 | spawn(['make'], search_path = 1) 42 | os.chdir('..') 43 | 44 | def run(self): 45 | self.buildNids() 46 | build.run(self) 47 | 48 | INCLUDE_DIRS = nidsMaker.include_dirs + INCLUDE_DIRS 49 | EXTRA_OBJECTS = nidsMaker.extra_objects + EXTRA_OBJECTS 50 | 51 | setup (# Distribution meta-data 52 | name = "pynids", 53 | version = "0.6.2", 54 | description = "libnids wrapper", 55 | author = "Wesley Shields", 56 | author_email = "wxs@atarininja.org", 57 | license = "GPL", 58 | long_description = \ 59 | '''pynids is a python wrapper for libnids, a Network Intrusion Detection System 60 | library offering sniffing, IP defragmentation, TCP stream reassembly and TCP 61 | port scan detection. 62 | ------- 63 | ''', 64 | cmdclass = {'build': nidsMaker}, 65 | ext_modules = [ Extension( 66 | "nidsmodule", 67 | #define_macros = [ ("DEBUG", None), ], 68 | sources=["nidsmodule.c"], 69 | include_dirs = INCLUDE_DIRS, 70 | libraries = ["pcap"], 71 | library_dirs = LIBRARY_DIRS, 72 | extra_objects = EXTRA_OBJECTS 73 | ) 74 | ], 75 | url = "http://jon.oberheide.org/pynids/", 76 | ) 77 | --------------------------------------------------------------------------------