├── .gitignore ├── LICENSE ├── README.md ├── SETTINGS.py ├── __init__.py ├── beehive.py ├── hive.db ├── lib ├── __init__.py ├── banners.py ├── beecoroutine.py ├── beethread.py ├── cmd.py ├── cmd2.py ├── db.py ├── exception.py ├── io.py ├── poc.py ├── pprint2.py └── scanner.py ├── menu.py ├── pocdb.json ├── pocs └── __init__.py ├── requirements.txt ├── setup.py └── tmp └── tmp.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # updated db 2 | hive.db 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | .eggs/ 14 | *.egg-info/ 15 | .installed.cfg 16 | *.egg 17 | 18 | # PyInstaller 19 | # Usually these files are written by a python script from a template 20 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 21 | *.manifest 22 | *.spec 23 | 24 | # Installer logs 25 | pip-log.txt 26 | pip-delete-this-directory.txt 27 | 28 | # Unit test / coverage reports 29 | .tox/ 30 | .coverage 31 | .coverage.* 32 | .cache 33 | nosetests.xml 34 | coverage.xml 35 | *,cover 36 | 37 | # Translations 38 | *.mo 39 | *.pot 40 | 41 | # Sphinx documentation 42 | docs/_build/ 43 | 44 | # Beehive 45 | .DS_Store 46 | hive.db 47 | pocs/ 48 | tmp/ 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | 676 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Beehive 2 | 3 | Beehive 是一款基于 Beebeeto-framework 的开源安全漏洞检测利用框架,安全研究人员可以通过使用它快速的进行漏洞挖掘、漏洞利用、后续渗透等工作。 4 | 5 | Beehive is an open-source vulnerability detection framework based on Beebeeto-framework. Security researcher can use it to find vulnerability, exploits, subsequent attacks, etc. 6 | 7 | 8 | # Screenshots 9 | 10 | ![http://docs.beebeeto.com/static/img/Screenshots_beehive_1.png](http://docs.beebeeto.com/static/img/Screenshots_beehive_1.png) 11 | 12 | # Documents 13 | 14 | - [http://docs.beebeeto.com/beehive/index.html](http://docs.beebeeto.com/beehive/index.html) 15 | 16 | # Developers 17 | 18 | - all@beebeeto.com 19 | - win2000@unknown.com 20 | - evi1m0.bat@gmail.com -------------------------------------------------------------------------------- /SETTINGS.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | BASE_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), 6 | '..', 7 | '..',)) 8 | 9 | BEESCAN_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), 10 | '..',)) 11 | 12 | FRAMEWORK_DIR = os.path.abspath(os.path.join(BASE_DIR, 13 | 'Beebeeto-framework',)) 14 | 15 | POC_DIR = os.path.abspath(os.path.join(BEESCAN_DIR, 16 | 'pocs',)) 17 | 18 | 19 | sys.path.extend([BASE_DIR, FRAMEWORK_DIR, POC_DIR]) 20 | 21 | VERSION = '0.1.0' 22 | 23 | 24 | if __name__ == '__main__': 25 | print BASE_DIR 26 | print BEESCAN_DIR 27 | print FRAMEWORK_DIR 28 | print POC_DIR 29 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n0tr00t/Beehive/24227d00f2db5299ffd426bc662deac8469badf3/__init__.py -------------------------------------------------------------------------------- /beehive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # site : www.beebeeto.com 4 | # team : n0tr00t security 5 | 6 | import sys 7 | import menu 8 | import SETTINGS 9 | 10 | from lib.exception import * 11 | 12 | def checkPythonVersion(major=2, minorLowest=6): 13 | if sys.version_info.major != major or \ 14 | sys.version_info.minor < minorLowest: 15 | errInfo = 'your python version is too low, version>=%d.%d.x '\ 16 | 'is needed.' % (major, minorLowest) 17 | raise BeeScanLaunchException(errInfo) 18 | return True 19 | 20 | if __name__ == '__main__': 21 | mm = menu.MainMenu() 22 | mm.cmdloop() 23 | -------------------------------------------------------------------------------- /hive.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n0tr00t/Beehive/24227d00f2db5299ffd426bc662deac8469badf3/hive.db -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | from SETTINGS import * -------------------------------------------------------------------------------- /lib/banners.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding : utf-8 3 | # author : evi1m0 4 | 5 | import random 6 | import textwrap 7 | 8 | one = textwrap.dedent( 9 | r''' 10 | _______ __ __ __ 11 | | \ | \ | \| \ 12 | | $$$$$$$\ ______ ______ | $$ | $$ \$$ __ __ ______ 13 | | $$__/ $$ / \ / \ | $$__| $$| \| \ / \ / \ 14 | | $$ $$| $$$$$$\| $$$$$$\| $$ $$| $$ \$$\ / $$| $$$$$$\ 15 | | $$$$$$$\| $$ $$| $$ $$| $$$$$$$$| $$ \$$\ $$ | $$ $$ 16 | | $$__/ $$| $$$$$$$$| $$$$$$$$| $$ | $$| $$ \$$ $$ | $$$$$$$$ 17 | | $$ $$ \$$ \ \$$ \| $$ | $$| $$ \$$$ \$$ \ 18 | \$$$$$$$ \$$$$$$$ \$$$$$$$ \$$ \$$ \$$ \$ \$$$$$$$ 19 | ''') 20 | 21 | two = textwrap.dedent( 22 | r''' 23 | ____ U _____ uU _____ u _ _ __ __ U _____ u 24 | U | __")u\| ___"|/\| ___"|/ |'| |'| ___ \ \ /"/u\| ___"|/ 25 | \| _ \/ | _|" | _|" /| |_| |\ |_"_| \ \ / // | _|" 26 | | |_) | | |___ | |___ U| _ |u | | /\ V /_,-.| |___ 27 | |____/ |_____| |_____| |_| |_| U/| |\u U \_/-(_/ |_____| 28 | _|| \\_ << >> << >> // \\.-,_|___|_,-.// << >> 29 | (__) (__)(__) (__)(__) (__)(_") ("_)\_)-' '-(_/(__) (__) (__) 30 | ''') 31 | 32 | three = textwrap.dedent( 33 | r''' 34 | ,; ,; ,; 35 | . f#i f#i . . t f#i 36 | Ef. .E#t .E#t Di Dt Ej .E#t 37 | E#Wi i#W, i#W, E#i E#i E#, t .DD. i#W, 38 | E#K#D: L#D. L#D. E#t E#t E#t EK: ,WK. L#D. 39 | E#t,E#f. :K#Wfff; :K#Wfff; E#t E#t E#t E#t i#D :K#Wfff; 40 | E#WEE##Wt i##WLLLLt i##WLLLLt E########f. E#t E#t j#f i##WLLLLt 41 | E##Ei;;;;. .E#L .E#L E#j..K#j... E#t E#tL#i .E#L 42 | E#DWWt f#E: f#E: E#t E#t E#t E#WW, f#E: 43 | E#t f#K; ,WW; ,WW; E#t E#t E#t E#K: ,WW; 44 | E#Dfff##E, .D#; .D#; f#t f#t E#t ED. .D#; 45 | jLLLLLLLLL; tt tt ii ii E#t t tt 46 | ''') 47 | 48 | four = textwrap.dedent( 49 | r''' 50 | ___ _ _ _ 51 | | _ ) ___ ___ | || | (_) __ __ ___ 52 | | _ \ / -_) / -_) | __ | | | \ V / / -_) 53 | |___/ \___| \___| |_||_| _|_|_ _\_/_ \___| 54 | _|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""| 55 | "`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-' 56 | ''') 57 | 58 | def getBanner(): 59 | logo = [one, two, three, four] 60 | statistics = random.choice(logo) 61 | return statistics 62 | -------------------------------------------------------------------------------- /lib/beecoroutine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # author: windows2000 4 | # site : beebeeto.com 5 | # team : n0tr00t security 6 | 7 | import gevent.monkey 8 | 9 | from copy import deepcopy 10 | from gevent.pool import Pool 11 | 12 | gevent.monkey.patch_all() 13 | 14 | 15 | 16 | class WorkerPool(object): 17 | JOB_UNSTART = 0 # poc not run 18 | JOB_RUNNING = 1 19 | JOB_FINISHED = 2 # poc run ok 20 | JOB_ERROR = -1 # error encountered when run poc 21 | JOB_ABORT = -2 # running poc is abort, viz unfinished 22 | 23 | def __init__(self, concurrency=10): 24 | self.concurrency = concurrency 25 | self.jobPool = Pool(size=concurrency) 26 | self.errNum = 0 # failed job(run time error but not aborted) 27 | self.successNum = 0 28 | self.totalNum = 0 29 | self.results = {} 30 | 31 | def work(self, iterJobFuncArgs, jobFunc, timeout=None): 32 | for jobFuncArgs in iterJobFuncArgs: 33 | self.results[hash(str(jobFuncArgs))] = { 34 | 'state': self.JOB_UNSTART, 35 | 'args': jobFuncArgs, 36 | } 37 | self.totalNum += 1 38 | self.jobPool.add( 39 | self.jobPool.apply_async( 40 | self._doJob, 41 | args=(jobFunc, jobFuncArgs,), 42 | kwds=None, 43 | callback=self._cbJobFinished 44 | ) 45 | ) 46 | self.jobPool.join(timeout=timeout, raise_error=False) 47 | return self.results 48 | 49 | def _cbJobFinished(self, jobResult): 50 | if jobResult['state'] == self.JOB_ERROR: 51 | self.errNum += 1 52 | elif jobResult['state'] == self.JOB_FINISHED: 53 | self.successNum += 1 54 | 55 | def _doJob(self, jobFunc, jobFuncArgs): 56 | self.results[hash(str(jobFuncArgs))]['state'] = self.JOB_RUNNING 57 | try: 58 | self.results[hash(str(jobFuncArgs))]['jobRet'] = \ 59 | jobFunc(*jobFuncArgs) if isinstance(jobFuncArgs, list) \ 60 | else jobFunc(jobFuncArgs) 61 | self.results[hash(str(jobFuncArgs))]['state'] = self.JOB_FINISHED 62 | except Exception as err: 63 | self.results[hash(str(jobFuncArgs))]['exception'] = str(err) 64 | self.results[hash(str(jobFuncArgs))]['state'] = self.JOB_ERROR 65 | return self.results[hash(str(jobFuncArgs))] 66 | 67 | def handleAbort(self): 68 | for jobId in self.results.keys(): 69 | if self.results[jobId]['state'] in (self.JOB_RUNNING, 70 | self.JOB_UNSTART): 71 | self.results[jobId]['state'] = self.JOB_ABORT 72 | 73 | 74 | if __name__ == '__main__': 75 | # testing code 76 | import time 77 | from random import randint 78 | from pprint import pprint 79 | 80 | def test(index, sleepTime): 81 | time.sleep(sleepTime) 82 | if sleepTime % 3 == 0: 83 | raise ValueError("%d - %d : 3's multiple!" % (index, 84 | sleepTime)) 85 | else: 86 | return True 87 | 88 | try: 89 | wp = WorkerPool(concurrency=10) 90 | wp.work([(i, randint(1, 3)) for i in xrange(20)], test, timeout=None) 91 | pprint(wp.results) 92 | except KeyboardInterrupt, SystemExit: 93 | wp.handleAbort() 94 | pprint(wp.results) 95 | exit() 96 | finally: 97 | print 'errNum %d, successNum %d, totalNum %d' % \ 98 | (wp.errNum, wp.successNum, wp.totalNum) 99 | -------------------------------------------------------------------------------- /lib/beethread.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # author: windows2000 4 | # site : beebeeto.com 5 | # team : n0tr00t security 6 | 7 | 8 | import threadpool as tp 9 | 10 | from copy import deepcopy 11 | 12 | 13 | class WorkerPool(object): 14 | JOB_UNSTART = 0 # poc not run 15 | JOB_RUNNING = 1 16 | JOB_FINISHED = 2 # poc run ok 17 | JOB_ERROR = -1 # error encountered when run poc 18 | JOB_ABORT = -2 # running poc is abort, viz unfinished 19 | 20 | def __init__(self, concurrency=10): 21 | self.concurrency = concurrency 22 | self.jobPool = tp.ThreadPool(num_workers=concurrency) 23 | self.errNum = 0 24 | self.successNum = 0 25 | self.totalNum = 0 26 | self.results = {} 27 | 28 | def work(self, iterJobFuncArgs, jobFunc, timeout=None): 29 | def argsGenerator(): 30 | for jobFuncArgs in iterJobFuncArgs: 31 | self.results[hash(str(jobFuncArgs))] = { 32 | 'state': self.JOB_UNSTART, 33 | 'args': jobFuncArgs, 34 | } 35 | self.totalNum += 1 36 | yield ((jobFunc, jobFuncArgs), {}) 37 | 38 | requests = tp.makeRequests(callable_=self._doJob, 39 | args_list=argsGenerator(), 40 | callback=self._cbJobFinished, 41 | exc_callback=self._cbHandleErr) 42 | [self.jobPool.putRequest(req) for req in requests] 43 | self.jobPool.wait() 44 | self.jobPool.dismissWorkers(self.concurrency, do_join=True) 45 | return self.results 46 | 47 | def _doJob(self, jobFuc, jobFuncArgs): 48 | self.results[hash(str(jobFuncArgs))]['state'] = self.JOB_RUNNING 49 | jobRet = jobFuc(*jobFuncArgs) if isinstance(jobFuncArgs, list) \ 50 | else jobFuc(jobFuncArgs) 51 | self.results[hash(str(jobFuncArgs))]['jobRet'] = jobRet 52 | return jobRet 53 | 54 | def _cbJobFinished(self, request, result): 55 | self.results[hash(str(request.args[1]))]['state'] = self.JOB_FINISHED 56 | self.successNum += 1 57 | 58 | def _cbHandleErr(self, request, exc_info): 59 | self.results[hash(str(request.args[1]))]['state'] = self.JOB_ERROR 60 | self.results[hash(str(request.args[1]))]['exception'] = str(exc_info[1]) 61 | self.errNum += 1 62 | 63 | def handleAbort(self): 64 | for jobId in self.results.keys(): 65 | if self.results[jobId]['state'] in (self.JOB_RUNNING, 66 | self.JOB_UNSTART): 67 | self.results[jobId]['state'] = self.JOB_ABORT 68 | 69 | if __name__ == '__main__': 70 | # testing code 71 | import time 72 | from random import randint 73 | from pprint import pprint 74 | 75 | def test(index, sleepTime): 76 | time.sleep(sleepTime) 77 | if sleepTime % 3 == 0: 78 | raise ValueError("%d - %d : 3's multiple!" % (index, 79 | sleepTime)) 80 | else: 81 | return True 82 | 83 | try: 84 | wp = WorkerPool(concurrency=10) 85 | wp.work([(i, randint(1, 3)) for i in xrange(20)], test, timeout=None) 86 | pprint(wp.results) 87 | except KeyboardInterrupt, SystemExit: 88 | wp.handleAbort() 89 | pprint(wp.results) 90 | exit() 91 | finally: 92 | print 'errNum %d, successNum %d, totalNum %d' % \ 93 | (wp.errNum, wp.successNum, wp.totalNum) 94 | -------------------------------------------------------------------------------- /lib/cmd.py: -------------------------------------------------------------------------------- 1 | """A generic class to build line-oriented command interpreters. 2 | 3 | Interpreters constructed with this class obey the following conventions: 4 | 5 | 1. End of file on input is processed as the command 'EOF'. 6 | 2. A command is parsed out of each line by collecting the prefix composed 7 | of characters in the identchars member. 8 | 3. A command `foo' is dispatched to a method 'do_foo()'; the do_ method 9 | is passed a single argument consisting of the remainder of the line. 10 | 4. Typing an empty line repeats the last command. (Actually, it calls the 11 | method `emptyline', which may be overridden in a subclass.) 12 | 5. There is a predefined `help' method. Given an argument `topic', it 13 | calls the command `help_topic'. With no arguments, it lists all topics 14 | with defined help_ functions, broken into up to three topics; documented 15 | commands, miscellaneous help topics, and undocumented commands. 16 | 6. The command '?' is a synonym for `help'. The command '!' is a synonym 17 | for `shell', if a do_shell method exists. 18 | 7. If completion is enabled, completing commands will be done automatically, 19 | and completing of commands args is done by calling complete_foo() with 20 | arguments text, line, begidx, endidx. text is string we are matching 21 | against, all returned matches must begin with it. line is the current 22 | input line (lstripped), begidx and endidx are the beginning and end 23 | indexes of the text being matched, which could be used to provide 24 | different completion depending upon which position the argument is in. 25 | 26 | The `default' method may be overridden to intercept commands for which there 27 | is no do_ method. 28 | 29 | The `completedefault' method may be overridden to intercept completions for 30 | commands that have no complete_ method. 31 | 32 | The data member `self.ruler' sets the character used to draw separator lines 33 | in the help messages. If empty, no ruler line is drawn. It defaults to "=". 34 | 35 | If the value of `self.intro' is nonempty when the cmdloop method is called, 36 | it is printed out on interpreter startup. This value may be overridden 37 | via an optional argument to the cmdloop() method. 38 | 39 | The data members `self.doc_header', `self.misc_header', and 40 | `self.undoc_header' set the headers used for the help function's 41 | listings of documented functions, miscellaneous topics, and undocumented 42 | functions respectively. 43 | 44 | These interpreters use raw_input; thus, if the readline module is loaded, 45 | they automatically support Emacs-like command history and editing features. 46 | """ 47 | 48 | import string 49 | 50 | __all__ = ["Cmd"] 51 | 52 | PROMPT = '(Cmd) ' 53 | IDENTCHARS = string.ascii_letters + string.digits + '_' 54 | 55 | class Cmd(object): 56 | """A simple framework for writing line-oriented command interpreters. 57 | 58 | These are often useful for test harnesses, administrative tools, and 59 | prototypes that will later be wrapped in a more sophisticated interface. 60 | 61 | A Cmd instance or subclass instance is a line-oriented interpreter 62 | framework. There is no good reason to instantiate Cmd itself; rather, 63 | it's useful as a superclass of an interpreter class you define yourself 64 | in order to inherit Cmd's methods and encapsulate action methods. 65 | 66 | """ 67 | prompt = PROMPT 68 | identchars = IDENTCHARS 69 | ruler = '=' 70 | lastcmd = '' 71 | intro = None 72 | doc_leader = "" 73 | doc_header = "Documented commands (type help ):" 74 | misc_header = "Miscellaneous help topics:" 75 | undoc_header = "Undocumented commands:" 76 | nohelp = "*** No help on %s" 77 | use_rawinput = 1 78 | 79 | def __init__(self, completekey='tab', stdin=None, stdout=None): 80 | """Instantiate a line-oriented interpreter framework. 81 | 82 | The optional argument 'completekey' is the readline name of a 83 | completion key; it defaults to the Tab key. If completekey is 84 | not None and the readline module is available, command completion 85 | is done automatically. The optional arguments stdin and stdout 86 | specify alternate input and output file objects; if not specified, 87 | sys.stdin and sys.stdout are used. 88 | 89 | """ 90 | import sys 91 | if stdin is not None: 92 | self.stdin = stdin 93 | else: 94 | self.stdin = sys.stdin 95 | if stdout is not None: 96 | self.stdout = stdout 97 | else: 98 | self.stdout = sys.stdout 99 | self.cmdqueue = [] 100 | self.completekey = completekey 101 | 102 | def cmdloop(self, intro=None): 103 | """Repeatedly issue a prompt, accept input, parse an initial prefix 104 | off the received input, and dispatch to action methods, passing them 105 | the remainder of the line as argument. 106 | 107 | """ 108 | 109 | self.preloop() 110 | if self.use_rawinput and self.completekey: 111 | try: 112 | import readline 113 | self.old_completer = readline.get_completer() 114 | readline.set_completer(self.complete) 115 | readline.parse_and_bind(self.completekey+": complete") 116 | except ImportError: 117 | pass 118 | try: 119 | if intro is not None: 120 | self.intro = intro 121 | if self.intro: 122 | self.stdout.write(str(self.intro)+"\n") 123 | stop = None 124 | while not stop: 125 | if self.cmdqueue: 126 | line = self.cmdqueue.pop(0) 127 | else: 128 | if self.use_rawinput: 129 | try: 130 | line = raw_input(self.prompt) 131 | except EOFError: 132 | line = 'EOF' 133 | else: 134 | self.stdout.write(self.prompt) 135 | self.stdout.flush() 136 | line = self.stdin.readline() 137 | if not len(line): 138 | line = 'EOF' 139 | else: 140 | line = line.rstrip('\r\n') 141 | line = self.precmd(line) 142 | stop = self.onecmd(line) 143 | stop = self.postcmd(stop, line) 144 | self.postloop() 145 | finally: 146 | if self.use_rawinput and self.completekey: 147 | try: 148 | import readline 149 | readline.set_completer(self.old_completer) 150 | except ImportError: 151 | pass 152 | 153 | 154 | def precmd(self, line): 155 | """Hook method executed just before the command line is 156 | interpreted, but after the input prompt is generated and issued. 157 | 158 | """ 159 | return line 160 | 161 | def postcmd(self, stop, line): 162 | """Hook method executed just after a command dispatch is finished.""" 163 | return stop 164 | 165 | def preloop(self): 166 | """Hook method executed once when the cmdloop() method is called.""" 167 | pass 168 | 169 | def postloop(self): 170 | """Hook method executed once when the cmdloop() method is about to 171 | return. 172 | 173 | """ 174 | pass 175 | 176 | def parseline(self, line): 177 | """Parse the line into a command name and a string containing 178 | the arguments. Returns a tuple containing (command, args, line). 179 | 'command' and 'args' may be None if the line couldn't be parsed. 180 | """ 181 | line = line.strip() 182 | if not line: 183 | return None, None, line 184 | elif line[0] == '?': 185 | line = 'help ' + line[1:] 186 | elif line[0] == '!': 187 | if hasattr(self, 'do_shell'): 188 | line = 'shell ' + line[1:] 189 | else: 190 | return None, None, line 191 | i, n = 0, len(line) 192 | while i < n and line[i] in self.identchars: i = i+1 193 | cmd, arg = line[:i], line[i:].strip() 194 | return cmd, arg, line 195 | 196 | def onecmd(self, line): 197 | """Interpret the argument as though it had been typed in response 198 | to the prompt. 199 | 200 | This may be overridden, but should not normally need to be; 201 | see the precmd() and postcmd() methods for useful execution hooks. 202 | The return value is a flag indicating whether interpretation of 203 | commands by the interpreter should stop. 204 | 205 | """ 206 | cmd, arg, line = self.parseline(line) 207 | if not line: 208 | return self.emptyline() 209 | if cmd is None: 210 | return self.default(line) 211 | self.lastcmd = line 212 | if line == 'EOF' : 213 | self.lastcmd = '' 214 | if cmd == '': 215 | return self.default(line) 216 | else: 217 | try: 218 | func = getattr(self, 'do_' + cmd) 219 | except AttributeError: 220 | return self.default(line) 221 | return func(arg) 222 | 223 | def emptyline(self): 224 | """Called when an empty line is entered in response to the prompt. 225 | 226 | If this method is not overridden, it repeats the last nonempty 227 | command entered. 228 | 229 | """ 230 | if self.lastcmd: 231 | return self.onecmd(self.lastcmd) 232 | 233 | def default(self, line): 234 | """Called on an input line when the command prefix is not recognized. 235 | 236 | If this method is not overridden, it prints an error message and 237 | returns. 238 | 239 | """ 240 | self.stdout.write('*** Unknown syntax: %s\n'%line) 241 | 242 | def completedefault(self, *ignored): 243 | """Method called to complete an input line when no command-specific 244 | complete_*() method is available. 245 | 246 | By default, it returns an empty list. 247 | 248 | """ 249 | return [] 250 | 251 | def completenames(self, text, *ignored): 252 | dotext = 'do_'+text 253 | return [a[3:] for a in self.get_names() if a.startswith(dotext)] 254 | 255 | def complete(self, text, state): 256 | """Return the next possible completion for 'text'. 257 | 258 | If a command has not been entered, then complete against command list. 259 | Otherwise try to call complete_ to get list of completions. 260 | """ 261 | if state == 0: 262 | import readline 263 | origline = readline.get_line_buffer() 264 | line = origline.lstrip() 265 | stripped = len(origline) - len(line) 266 | begidx = readline.get_begidx() - stripped 267 | endidx = readline.get_endidx() - stripped 268 | if begidx>0: 269 | cmd, args, foo = self.parseline(line) 270 | if cmd == '': 271 | compfunc = self.completedefault 272 | else: 273 | try: 274 | compfunc = getattr(self, 'complete_' + cmd) 275 | except AttributeError: 276 | compfunc = self.completedefault 277 | else: 278 | compfunc = self.completenames 279 | self.completion_matches = compfunc(text, line, begidx, endidx) 280 | try: 281 | return self.completion_matches[state] 282 | except IndexError: 283 | return None 284 | 285 | def get_names(self): 286 | # This method used to pull in base class attributes 287 | # at a time dir() didn't do it yet. 288 | return dir(self.__class__) 289 | 290 | def complete_help(self, *args): 291 | commands = set(self.completenames(*args)) 292 | topics = set(a[5:] for a in self.get_names() 293 | if a.startswith('help_' + args[0])) 294 | return list(commands | topics) 295 | 296 | def do_help(self, arg): 297 | 'List available commands with "help" or detailed help with "help cmd".' 298 | if arg: 299 | # XXX check arg syntax 300 | try: 301 | func = getattr(self, 'help_' + arg) 302 | except AttributeError: 303 | try: 304 | doc=getattr(self, 'do_' + arg).__doc__ 305 | if doc: 306 | self.stdout.write("%s\n"%str(doc)) 307 | return 308 | except AttributeError: 309 | pass 310 | self.stdout.write("%s\n"%str(self.nohelp % (arg,))) 311 | return 312 | func() 313 | else: 314 | names = self.get_names() 315 | cmds_doc = [] 316 | cmds_undoc = [] 317 | help = {} 318 | for name in names: 319 | if name[:5] == 'help_': 320 | help[name[5:]]=1 321 | names.sort() 322 | # There can be duplicates if routines overridden 323 | prevname = '' 324 | for name in names: 325 | if name[:3] == 'do_': 326 | if name == prevname: 327 | continue 328 | prevname = name 329 | cmd=name[3:] 330 | if cmd in help: 331 | cmds_doc.append(cmd) 332 | del help[cmd] 333 | elif getattr(self, name).__doc__: 334 | cmds_doc.append(cmd) 335 | else: 336 | cmds_undoc.append(cmd) 337 | self.stdout.write("%s\n"%str(self.doc_leader)) 338 | self.print_topics(self.doc_header, cmds_doc, 15,80) 339 | self.print_topics(self.misc_header, help.keys(),15,80) 340 | self.print_topics(self.undoc_header, cmds_undoc, 15,80) 341 | 342 | def print_topics(self, header, cmds, cmdlen, maxcol): 343 | if cmds: 344 | self.stdout.write("%s\n"%str(header)) 345 | if self.ruler: 346 | self.stdout.write("%s\n"%str(self.ruler * len(header))) 347 | self.columnize(cmds, maxcol-1) 348 | self.stdout.write("\n") 349 | 350 | def columnize(self, list, displaywidth=80): 351 | """Display a list of strings as a compact set of columns. 352 | 353 | Each column is only as wide as necessary. 354 | Columns are separated by two spaces (one was not legible enough). 355 | """ 356 | if not list: 357 | self.stdout.write("\n") 358 | return 359 | nonstrings = [i for i in range(len(list)) 360 | if not isinstance(list[i], str)] 361 | if nonstrings: 362 | raise TypeError, ("list[i] not a string for i in %s" % 363 | ", ".join(map(str, nonstrings))) 364 | size = len(list) 365 | if size == 1: 366 | self.stdout.write('%s\n'%str(list[0])) 367 | return 368 | # Try every row count from 1 upwards 369 | for nrows in range(1, len(list)): 370 | ncols = (size+nrows-1) // nrows 371 | colwidths = [] 372 | totwidth = -2 373 | for col in range(ncols): 374 | colwidth = 0 375 | for row in range(nrows): 376 | i = row + nrows*col 377 | if i >= size: 378 | break 379 | x = list[i] 380 | colwidth = max(colwidth, len(x)) 381 | colwidths.append(colwidth) 382 | totwidth += colwidth + 2 383 | if totwidth > displaywidth: 384 | break 385 | if totwidth <= displaywidth: 386 | break 387 | else: 388 | nrows = len(list) 389 | ncols = 1 390 | colwidths = [0] 391 | for row in range(nrows): 392 | texts = [] 393 | for col in range(ncols): 394 | i = row + nrows*col 395 | if i >= size: 396 | x = "" 397 | else: 398 | x = list[i] 399 | texts.append(x) 400 | while texts and not texts[-1]: 401 | del texts[-1] 402 | for col in range(len(texts)): 403 | texts[col] = texts[col].ljust(colwidths[col]) 404 | self.stdout.write("%s\n"%str(" ".join(texts))) 405 | -------------------------------------------------------------------------------- /lib/cmd2.py: -------------------------------------------------------------------------------- 1 | """Variant on standard library's cmd with extra features. 2 | 3 | To use, simply import cmd2.Cmd instead of cmd.Cmd; use precisely as though you 4 | were using the standard library's cmd, while enjoying the extra features. 5 | 6 | Searchable command history (commands: "hi", "li", "run") 7 | Load commands from file, save to file, edit commands in file 8 | Multi-line commands 9 | Case-insensitive commands 10 | Special-character shortcut commands (beyond cmd's "@" and "!") 11 | Settable environment parameters 12 | Optional _onchange_{paramname} called when environment parameter changes 13 | Parsing commands with `optparse` options (flags) 14 | Redirection to file with >, >>; input from file with < 15 | Easy transcript-based testing of applications (see example/example.py) 16 | Bash-style ``select`` available 17 | 18 | Note that redirection with > and | will only work if `self.stdout.write()` 19 | is used in place of `print`. The standard library's `cmd` module is 20 | written to use `self.stdout.write()`, 21 | 22 | - Catherine Devlin, Jan 03 2008 - catherinedevlin.blogspot.com 23 | 24 | mercurial repository at http://www.assembla.com/wiki/show/python-cmd2 25 | """ 26 | import cmd 27 | import re 28 | import os 29 | import sys 30 | import optparse 31 | import subprocess 32 | import tempfile 33 | import doctest 34 | import unittest 35 | import datetime 36 | import urllib 37 | import glob 38 | import traceback 39 | import platform 40 | import copy 41 | from code import InteractiveConsole, InteractiveInterpreter 42 | from optparse import make_option 43 | import pyparsing 44 | 45 | __version__ = '0.6.8' 46 | 47 | if sys.version_info[0] == 2: 48 | pyparsing.ParserElement.enablePackrat() 49 | 50 | """ 51 | Packrat is causing Python3 errors that I don't understand. 52 | 53 | > /usr/local/Cellar/python3/3.2/lib/python3.2/site-packages/pyparsing-1.5.6-py3.2.egg/pyparsing.py(999)scanString() 54 | -> nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) 55 | (Pdb) n 56 | NameError: global name 'exc' is not defined 57 | 58 | (Pdb) parseFn 59 | 60 | 61 | Bug report filed: https://sourceforge.net/tracker/?func=detail&atid=617311&aid=3381439&group_id=97203 62 | """ 63 | 64 | class OptionParser(optparse.OptionParser): 65 | def exit(self, status=0, msg=None): 66 | self.values._exit = True 67 | if msg: 68 | print (msg) 69 | 70 | def print_help(self, *args, **kwargs): 71 | try: 72 | if self._func.__doc__: 73 | print (self._func.__doc__) 74 | except AttributeError: 75 | pass 76 | optparse.OptionParser.print_help(self, *args, **kwargs) 77 | 78 | def error(self, msg): 79 | """error(msg : string) 80 | 81 | Print a usage message incorporating 'msg' to stderr and exit. 82 | If you override this in a subclass, it should not return -- it 83 | should either exit or raise an exception. 84 | """ 85 | raise optparse.OptParseError(msg) 86 | 87 | def remaining_args(oldArgs, newArgList): 88 | ''' 89 | Preserves the spacing originally in the argument after 90 | the removal of options. 91 | 92 | >>> remaining_args('-f bar bar cow', ['bar', 'cow']) 93 | 'bar cow' 94 | ''' 95 | pattern = '\s+'.join(re.escape(a) for a in newArgList) + '\s*$' 96 | matchObj = re.search(pattern, oldArgs) 97 | return oldArgs[matchObj.start():] 98 | 99 | def _attr_get_(obj, attr): 100 | '''Returns an attribute's value, or None (no error) if undefined. 101 | Analagous to .get() for dictionaries. Useful when checking for 102 | value of options that may not have been defined on a given 103 | method.''' 104 | try: 105 | return getattr(obj, attr) 106 | except AttributeError: 107 | return None 108 | 109 | def _which(editor): 110 | try: 111 | return subprocess.Popen(['which', editor], stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] 112 | except OSError: 113 | return None 114 | 115 | optparse.Values.get = _attr_get_ 116 | 117 | options_defined = [] # used to distinguish --options from SQL-style --comments 118 | 119 | def options(option_list, arg_desc="arg"): 120 | '''Used as a decorator and passed a list of optparse-style options, 121 | alters a cmd2 method to populate its ``opts`` argument from its 122 | raw text argument. 123 | 124 | Example: transform 125 | def do_something(self, arg): 126 | 127 | into 128 | @options([make_option('-q', '--quick', action="store_true", 129 | help="Makes things fast")], 130 | "source dest") 131 | def do_something(self, arg, opts): 132 | if opts.quick: 133 | self.fast_button = True 134 | ''' 135 | if not isinstance(option_list, list): 136 | option_list = [option_list] 137 | for opt in option_list: 138 | options_defined.append(pyparsing.Literal(opt.get_opt_string())) 139 | def option_setup(func): 140 | optionParser = OptionParser() 141 | for opt in option_list: 142 | optionParser.add_option(opt) 143 | optionParser.set_usage("%s [options] %s" % (func.__name__[3:], arg_desc)) 144 | optionParser._func = func 145 | def new_func(instance, arg): 146 | try: 147 | opts, newArgList = optionParser.parse_args(arg.split()) 148 | # Must find the remaining args in the original argument list, but 149 | # mustn't include the command itself 150 | #if hasattr(arg, 'parsed') and newArgList[0] == arg.parsed.command: 151 | # newArgList = newArgList[1:] 152 | newArgs = remaining_args(arg, newArgList) 153 | if isinstance(arg, ParsedString): 154 | arg = arg.with_args_replaced(newArgs) 155 | else: 156 | arg = newArgs 157 | except optparse.OptParseError as e: 158 | print (e) 159 | optionParser.print_help() 160 | return 161 | if hasattr(opts, '_exit'): 162 | return None 163 | result = func(instance, arg, opts) 164 | return result 165 | new_func.__doc__ = '%s\n%s' % (func.__doc__, optionParser.format_help()) 166 | return new_func 167 | return option_setup 168 | 169 | class PasteBufferError(EnvironmentError): 170 | if sys.platform[:3] == 'win': 171 | errmsg = """Redirecting to or from paste buffer requires pywin32 172 | to be installed on operating system. 173 | Download from http://sourceforge.net/projects/pywin32/""" 174 | elif sys.platform[:3] == 'dar': 175 | # Use built in pbcopy on Mac OSX 176 | pass 177 | else: 178 | errmsg = """Redirecting to or from paste buffer requires xclip 179 | to be installed on operating system. 180 | On Debian/Ubuntu, 'sudo apt-get install xclip' will install it.""" 181 | def __init__(self): 182 | Exception.__init__(self, self.errmsg) 183 | 184 | pastebufferr = """Redirecting to or from paste buffer requires %s 185 | to be installed on operating system. 186 | %s""" 187 | 188 | if subprocess.mswindows: 189 | try: 190 | import win32clipboard 191 | def get_paste_buffer(): 192 | win32clipboard.OpenClipboard(0) 193 | try: 194 | result = win32clipboard.GetClipboardData() 195 | except TypeError: 196 | result = '' #non-text 197 | win32clipboard.CloseClipboard() 198 | return result 199 | def write_to_paste_buffer(txt): 200 | win32clipboard.OpenClipboard(0) 201 | win32clipboard.EmptyClipboard() 202 | win32clipboard.SetClipboardText(txt) 203 | win32clipboard.CloseClipboard() 204 | except ImportError: 205 | def get_paste_buffer(*args): 206 | raise OSError(pastebufferr % ('pywin32', 'Download from http://sourceforge.net/projects/pywin32/')) 207 | write_to_paste_buffer = get_paste_buffer 208 | elif sys.platform == 'darwin': 209 | can_clip = False 210 | try: 211 | # test for pbcopy - AFAIK, should always be installed on MacOS 212 | subprocess.check_call('pbcopy -help', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) 213 | can_clip = True 214 | except (subprocess.CalledProcessError, OSError, IOError): 215 | pass 216 | if can_clip: 217 | def get_paste_buffer(): 218 | pbcopyproc = subprocess.Popen('pbcopy -help', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) 219 | return pbcopyproc.stdout.read() 220 | def write_to_paste_buffer(txt): 221 | pbcopyproc = subprocess.Popen('pbcopy', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) 222 | pbcopyproc.communicate(txt.encode()) 223 | else: 224 | def get_paste_buffer(*args): 225 | raise OSError(pastebufferr % ('pbcopy', 'On MacOS X - error should not occur - part of the default installation')) 226 | write_to_paste_buffer = get_paste_buffer 227 | else: 228 | can_clip = False 229 | try: 230 | subprocess.check_call('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) 231 | can_clip = True 232 | except AttributeError: # check_call not defined, Python < 2.5 233 | try: 234 | teststring = 'Testing for presence of xclip.' 235 | xclipproc = subprocess.Popen('xclip -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 236 | xclipproc.stdin.write(teststring) 237 | xclipproc.stdin.close() 238 | xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 239 | if xclipproc.stdout.read() == teststring: 240 | can_clip = True 241 | except Exception: # hate a bare Exception call, but exception classes vary too much b/t stdlib versions 242 | pass 243 | except Exception: 244 | pass # something went wrong with xclip and we cannot use it 245 | if can_clip: 246 | def get_paste_buffer(): 247 | xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 248 | return xclipproc.stdout.read() 249 | def write_to_paste_buffer(txt): 250 | xclipproc = subprocess.Popen('xclip -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 251 | xclipproc.stdin.write(txt.encode()) 252 | xclipproc.stdin.close() 253 | # but we want it in both the "primary" and "mouse" clipboards 254 | xclipproc = subprocess.Popen('xclip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 255 | xclipproc.stdin.write(txt.encode()) 256 | xclipproc.stdin.close() 257 | else: 258 | def get_paste_buffer(*args): 259 | raise OSError(pastebufferr % ('xclip', 'On Debian/Ubuntu, install with "sudo apt-get install xclip"')) 260 | write_to_paste_buffer = get_paste_buffer 261 | 262 | pyparsing.ParserElement.setDefaultWhitespaceChars(' \t') 263 | 264 | class ParsedString(str): 265 | def full_parsed_statement(self): 266 | new = ParsedString('%s %s' % (self.parsed.command, self.parsed.args)) 267 | new.parsed = self.parsed 268 | new.parser = self.parser 269 | return new 270 | def with_args_replaced(self, newargs): 271 | new = ParsedString(newargs) 272 | new.parsed = self.parsed 273 | new.parser = self.parser 274 | new.parsed['args'] = newargs 275 | new.parsed.statement['args'] = newargs 276 | return new 277 | 278 | class StubbornDict(dict): 279 | '''Dictionary that tolerates many input formats. 280 | Create it with stubbornDict(arg) factory function. 281 | 282 | >>> d = StubbornDict(large='gross', small='klein') 283 | >>> sorted(d.items()) 284 | [('large', 'gross'), ('small', 'klein')] 285 | >>> d.append(['plain', ' plaid']) 286 | >>> sorted(d.items()) 287 | [('large', 'gross'), ('plaid', ''), ('plain', ''), ('small', 'klein')] 288 | >>> d += ' girl Frauelein, Maedchen\\n\\n shoe schuh' 289 | >>> sorted(d.items()) 290 | [('girl', 'Frauelein, Maedchen'), ('large', 'gross'), ('plaid', ''), ('plain', ''), ('shoe', 'schuh'), ('small', 'klein')] 291 | ''' 292 | def update(self, arg): 293 | dict.update(self, StubbornDict.to_dict(arg)) 294 | append = update 295 | def __iadd__(self, arg): 296 | self.update(arg) 297 | return self 298 | def __add__(self, arg): 299 | selfcopy = copy.copy(self) 300 | selfcopy.update(stubbornDict(arg)) 301 | return selfcopy 302 | def __radd__(self, arg): 303 | selfcopy = copy.copy(self) 304 | selfcopy.update(stubbornDict(arg)) 305 | return selfcopy 306 | 307 | @classmethod 308 | def to_dict(cls, arg): 309 | 'Generates dictionary from string or list of strings' 310 | if hasattr(arg, 'splitlines'): 311 | arg = arg.splitlines() 312 | if hasattr(arg, '__reversed__'): 313 | result = {} 314 | for a in arg: 315 | a = a.strip() 316 | if a: 317 | key_val = a.split(None, 1) 318 | key = key_val[0] 319 | if len(key_val) > 1: 320 | val = key_val[1] 321 | else: 322 | val = '' 323 | result[key] = val 324 | else: 325 | result = arg 326 | return result 327 | 328 | def stubbornDict(*arg, **kwarg): 329 | ''' 330 | >>> sorted(stubbornDict('cow a bovine\\nhorse an equine').items()) 331 | [('cow', 'a bovine'), ('horse', 'an equine')] 332 | >>> sorted(stubbornDict(['badger', 'porcupine a poky creature']).items()) 333 | [('badger', ''), ('porcupine', 'a poky creature')] 334 | >>> sorted(stubbornDict(turtle='has shell', frog='jumpy').items()) 335 | [('frog', 'jumpy'), ('turtle', 'has shell')] 336 | ''' 337 | result = {} 338 | for a in arg: 339 | result.update(StubbornDict.to_dict(a)) 340 | result.update(kwarg) 341 | return StubbornDict(result) 342 | 343 | def replace_with_file_contents(fname): 344 | if fname: 345 | try: 346 | result = open(os.path.expanduser(fname[0])).read() 347 | except IOError: 348 | result = '< %s' % fname[0] # wasn't a file after all 349 | else: 350 | result = get_paste_buffer() 351 | return result 352 | 353 | class EmbeddedConsoleExit(SystemExit): 354 | pass 355 | 356 | class EmptyStatement(Exception): 357 | pass 358 | 359 | def ljust(x, width, fillchar=' '): 360 | 'analogous to str.ljust, but works for lists' 361 | if hasattr(x, 'ljust'): 362 | return x.ljust(width, fillchar) 363 | else: 364 | if len(x) < width: 365 | x = (x + [fillchar] * width)[:width] 366 | return x 367 | 368 | class Cmd(cmd.Cmd): 369 | echo = False 370 | case_insensitive = True # Commands recognized regardless of case 371 | continuation_prompt = '> ' 372 | timing = False # Prints elapsed time for each command 373 | # make sure your terminators are not in legalChars! 374 | legalChars = u'!#$%.:?@_' + pyparsing.alphanums + pyparsing.alphas8bit 375 | shortcuts = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'} 376 | excludeFromHistory = '''run r list l history hi ed edit li eof'''.split() 377 | default_to_shell = False 378 | noSpecialParse = 'set ed edit exit'.split() 379 | defaultExtension = 'txt' # For ``save``, ``load``, etc. 380 | default_file_name = 'command.txt' # For ``save``, ``load``, etc. 381 | abbrev = True # Abbreviated commands recognized 382 | current_script_dir = None 383 | reserved_words = [] 384 | feedback_to_output = False # Do include nonessentials in >, | output 385 | quiet = False # Do not suppress nonessential output 386 | debug = False 387 | locals_in_py = True 388 | kept_state = None 389 | redirector = '>' # for sending output to file 390 | settable = stubbornDict(''' 391 | prompt 392 | colors Colorized output (*nix only) 393 | continuation_prompt On 2nd+ line of input 394 | debug Show full error stack on error 395 | default_file_name for ``save``, ``load``, etc. 396 | editor Program used by ``edit`` 397 | case_insensitive upper- and lower-case both OK 398 | feedback_to_output include nonessentials in `|`, `>` results 399 | quiet Don't print nonessential feedback 400 | echo Echo command issued into output 401 | timing Report execution times 402 | abbrev Accept abbreviated commands 403 | ''') 404 | 405 | def poutput(self, msg): 406 | '''Convenient shortcut for self.stdout.write(); adds newline if necessary.''' 407 | if msg: 408 | self.stdout.write(msg) 409 | if msg[-1] != '\n': 410 | self.stdout.write('\n') 411 | def perror(self, errmsg, statement=None): 412 | if self.debug: 413 | traceback.print_exc() 414 | print (str(errmsg)) 415 | def pfeedback(self, msg): 416 | """For printing nonessential feedback. Can be silenced with `quiet`. 417 | Inclusion in redirected output is controlled by `feedback_to_output`.""" 418 | if not self.quiet: 419 | if self.feedback_to_output: 420 | self.poutput(msg) 421 | else: 422 | print (msg) 423 | _STOP_AND_EXIT = True # distinguish end of script file from actual exit 424 | _STOP_SCRIPT_NO_EXIT = -999 425 | editor = os.environ.get('EDITOR') 426 | if not editor: 427 | if sys.platform[:3] == 'win': 428 | editor = 'notepad' 429 | else: 430 | for editor in ['gedit', 'kate', 'vim', 'vi', 'emacs', 'nano', 'pico']: 431 | if _which(editor): 432 | break 433 | 434 | colorcodes = {'bold':{True:'\x1b[1m',False:'\x1b[22m'}, 435 | 'cyan':{True:'\x1b[36m',False:'\x1b[39m'}, 436 | 'blue':{True:'\x1b[34m',False:'\x1b[39m'}, 437 | 'red':{True:'\x1b[31m',False:'\x1b[39m'}, 438 | 'magenta':{True:'\x1b[35m',False:'\x1b[39m'}, 439 | 'green':{True:'\x1b[32m',False:'\x1b[39m'}, 440 | 'underline':{True:'\x1b[4m',False:'\x1b[24m'}} 441 | colors = (platform.system() != 'Windows') 442 | def colorize(self, val, color): 443 | '''Given a string (``val``), returns that string wrapped in UNIX-style 444 | special characters that turn on (and then off) text color and style. 445 | If the ``colors`` environment paramter is ``False``, or the application 446 | is running on Windows, will return ``val`` unchanged. 447 | ``color`` should be one of the supported strings (or styles): 448 | red/blue/green/cyan/magenta, bold, underline''' 449 | if self.colors and (self.stdout == self.initial_stdout): 450 | return self.colorcodes[color][True] + val + self.colorcodes[color][False] 451 | return val 452 | 453 | def do_cmdenvironment(self, args): 454 | '''Summary report of interactive parameters.''' 455 | self.stdout.write(""" 456 | Commands are %(casesensitive)scase-sensitive. 457 | Commands may be terminated with: %(terminators)s 458 | Settable parameters: %(settable)s\n""" % \ 459 | { 'casesensitive': (self.case_insensitive and 'not ') or '', 460 | 'terminators': str(self.terminators), 461 | 'settable': ' '.join(self.settable) 462 | }) 463 | 464 | def do_help(self, arg): 465 | if arg: 466 | funcname = self.func_named(arg) 467 | if funcname: 468 | fn = getattr(self, funcname) 469 | try: 470 | fn.optionParser.print_help(file=self.stdout) 471 | except AttributeError: 472 | cmd.Cmd.do_help(self, funcname[3:]) 473 | else: 474 | cmd.Cmd.do_help(self, arg) 475 | 476 | def __init__(self, *args, **kwargs): 477 | cmd.Cmd.__init__(self, *args, **kwargs) 478 | self.initial_stdout = sys.stdout 479 | self.history = History() 480 | self.pystate = {} 481 | self.shortcuts = sorted(self.shortcuts.items(), reverse=True) 482 | self.keywords = self.reserved_words + [fname[3:] for fname in dir(self) 483 | if fname.startswith('do_')] 484 | self._init_parser() 485 | 486 | def do_shortcuts(self, args): 487 | """Lists single-key shortcuts available.""" 488 | result = "\n".join('%s: %s' % (sc[0], sc[1]) for sc in sorted(self.shortcuts)) 489 | self.stdout.write("Single-key shortcuts for other commands:\n%s\n" % (result)) 490 | 491 | prefixParser = pyparsing.Empty() 492 | commentGrammars = pyparsing.Or([pyparsing.pythonStyleComment, pyparsing.cStyleComment]) 493 | commentGrammars.addParseAction(lambda x: '') 494 | commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo( 495 | pyparsing.stringEnd ^ '*/') 496 | terminators = [';'] 497 | blankLinesAllowed = False 498 | multilineCommands = [] 499 | 500 | def _init_parser(self): 501 | r''' 502 | >>> c = Cmd() 503 | >>> c.multilineCommands = ['multiline'] 504 | >>> c.case_insensitive = True 505 | >>> c._init_parser() 506 | >>> print (c.parser.parseString('').dump()) 507 | [] 508 | >>> print (c.parser.parseString('').dump()) 509 | [] 510 | >>> print (c.parser.parseString('/* empty command */').dump()) 511 | [] 512 | >>> print (c.parser.parseString('plainword').dump()) 513 | ['plainword', ''] 514 | - command: plainword 515 | - statement: ['plainword', ''] 516 | - command: plainword 517 | >>> print (c.parser.parseString('termbare;').dump()) 518 | ['termbare', '', ';', ''] 519 | - command: termbare 520 | - statement: ['termbare', '', ';'] 521 | - command: termbare 522 | - terminator: ; 523 | - terminator: ; 524 | >>> print (c.parser.parseString('termbare; suffx').dump()) 525 | ['termbare', '', ';', 'suffx'] 526 | - command: termbare 527 | - statement: ['termbare', '', ';'] 528 | - command: termbare 529 | - terminator: ; 530 | - suffix: suffx 531 | - terminator: ; 532 | >>> print (c.parser.parseString('barecommand').dump()) 533 | ['barecommand', ''] 534 | - command: barecommand 535 | - statement: ['barecommand', ''] 536 | - command: barecommand 537 | >>> print (c.parser.parseString('COMmand with args').dump()) 538 | ['command', 'with args'] 539 | - args: with args 540 | - command: command 541 | - statement: ['command', 'with args'] 542 | - args: with args 543 | - command: command 544 | >>> print (c.parser.parseString('command with args and terminator; and suffix').dump()) 545 | ['command', 'with args and terminator', ';', 'and suffix'] 546 | - args: with args and terminator 547 | - command: command 548 | - statement: ['command', 'with args and terminator', ';'] 549 | - args: with args and terminator 550 | - command: command 551 | - terminator: ; 552 | - suffix: and suffix 553 | - terminator: ; 554 | >>> print (c.parser.parseString('simple | piped').dump()) 555 | ['simple', '', '|', ' piped'] 556 | - command: simple 557 | - pipeTo: piped 558 | - statement: ['simple', ''] 559 | - command: simple 560 | >>> print (c.parser.parseString('double-pipe || is not a pipe').dump()) 561 | ['double', '-pipe || is not a pipe'] 562 | - args: -pipe || is not a pipe 563 | - command: double 564 | - statement: ['double', '-pipe || is not a pipe'] 565 | - args: -pipe || is not a pipe 566 | - command: double 567 | >>> print (c.parser.parseString('command with args, terminator;sufx | piped').dump()) 568 | ['command', 'with args, terminator', ';', 'sufx', '|', ' piped'] 569 | - args: with args, terminator 570 | - command: command 571 | - pipeTo: piped 572 | - statement: ['command', 'with args, terminator', ';'] 573 | - args: with args, terminator 574 | - command: command 575 | - terminator: ; 576 | - suffix: sufx 577 | - terminator: ; 578 | >>> print (c.parser.parseString('output into > afile.txt').dump()) 579 | ['output', 'into', '>', 'afile.txt'] 580 | - args: into 581 | - command: output 582 | - output: > 583 | - outputTo: afile.txt 584 | - statement: ['output', 'into'] 585 | - args: into 586 | - command: output 587 | >>> print (c.parser.parseString('output into;sufx | pipethrume plz > afile.txt').dump()) 588 | ['output', 'into', ';', 'sufx', '|', ' pipethrume plz', '>', 'afile.txt'] 589 | - args: into 590 | - command: output 591 | - output: > 592 | - outputTo: afile.txt 593 | - pipeTo: pipethrume plz 594 | - statement: ['output', 'into', ';'] 595 | - args: into 596 | - command: output 597 | - terminator: ; 598 | - suffix: sufx 599 | - terminator: ; 600 | >>> print (c.parser.parseString('output to paste buffer >> ').dump()) 601 | ['output', 'to paste buffer', '>>', ''] 602 | - args: to paste buffer 603 | - command: output 604 | - output: >> 605 | - statement: ['output', 'to paste buffer'] 606 | - args: to paste buffer 607 | - command: output 608 | >>> print (c.parser.parseString('ignore the /* commented | > */ stuff;').dump()) 609 | ['ignore', 'the /* commented | > */ stuff', ';', ''] 610 | - args: the /* commented | > */ stuff 611 | - command: ignore 612 | - statement: ['ignore', 'the /* commented | > */ stuff', ';'] 613 | - args: the /* commented | > */ stuff 614 | - command: ignore 615 | - terminator: ; 616 | - terminator: ; 617 | >>> print (c.parser.parseString('has > inside;').dump()) 618 | ['has', '> inside', ';', ''] 619 | - args: > inside 620 | - command: has 621 | - statement: ['has', '> inside', ';'] 622 | - args: > inside 623 | - command: has 624 | - terminator: ; 625 | - terminator: ; 626 | >>> print (c.parser.parseString('multiline has > inside an unfinished command').dump()) 627 | ['multiline', ' has > inside an unfinished command'] 628 | - multilineCommand: multiline 629 | >>> print (c.parser.parseString('multiline has > inside;').dump()) 630 | ['multiline', 'has > inside', ';', ''] 631 | - args: has > inside 632 | - multilineCommand: multiline 633 | - statement: ['multiline', 'has > inside', ';'] 634 | - args: has > inside 635 | - multilineCommand: multiline 636 | - terminator: ; 637 | - terminator: ; 638 | >>> print (c.parser.parseString('multiline command /* with comment in progress;').dump()) 639 | ['multiline', ' command /* with comment in progress;'] 640 | - multilineCommand: multiline 641 | >>> print (c.parser.parseString('multiline command /* with comment complete */ is done;').dump()) 642 | ['multiline', 'command /* with comment complete */ is done', ';', ''] 643 | - args: command /* with comment complete */ is done 644 | - multilineCommand: multiline 645 | - statement: ['multiline', 'command /* with comment complete */ is done', ';'] 646 | - args: command /* with comment complete */ is done 647 | - multilineCommand: multiline 648 | - terminator: ; 649 | - terminator: ; 650 | >>> print (c.parser.parseString('multiline command ends\n\n').dump()) 651 | ['multiline', 'command ends', '\n', '\n'] 652 | - args: command ends 653 | - multilineCommand: multiline 654 | - statement: ['multiline', 'command ends', '\n', '\n'] 655 | - args: command ends 656 | - multilineCommand: multiline 657 | - terminator: ['\n', '\n'] 658 | - terminator: ['\n', '\n'] 659 | >>> print (c.parser.parseString('multiline command "with term; ends" now\n\n').dump()) 660 | ['multiline', 'command "with term; ends" now', '\n', '\n'] 661 | - args: command "with term; ends" now 662 | - multilineCommand: multiline 663 | - statement: ['multiline', 'command "with term; ends" now', '\n', '\n'] 664 | - args: command "with term; ends" now 665 | - multilineCommand: multiline 666 | - terminator: ['\n', '\n'] 667 | - terminator: ['\n', '\n'] 668 | >>> print (c.parser.parseString('what if "quoted strings /* seem to " start comments?').dump()) 669 | ['what', 'if "quoted strings /* seem to " start comments?'] 670 | - args: if "quoted strings /* seem to " start comments? 671 | - command: what 672 | - statement: ['what', 'if "quoted strings /* seem to " start comments?'] 673 | - args: if "quoted strings /* seem to " start comments? 674 | - command: what 675 | ''' 676 | #outputParser = (pyparsing.Literal('>>') | (pyparsing.WordStart() + '>') | pyparsing.Regex('[^=]>'))('output') 677 | outputParser = (pyparsing.Literal(self.redirector *2) | \ 678 | (pyparsing.WordStart() + self.redirector) | \ 679 | pyparsing.Regex('[^=]' + self.redirector))('output') 680 | 681 | terminatorParser = pyparsing.Or([(hasattr(t, 'parseString') and t) or pyparsing.Literal(t) for t in self.terminators])('terminator') 682 | stringEnd = pyparsing.stringEnd ^ '\nEOF' 683 | self.multilineCommand = pyparsing.Or([pyparsing.Keyword(c, caseless=self.case_insensitive) for c in self.multilineCommands])('multilineCommand') 684 | oneLineCommand = (~self.multilineCommand + pyparsing.Word(self.legalChars))('command') 685 | pipe = pyparsing.Keyword('|', identChars='|') 686 | self.commentGrammars.ignore(pyparsing.quotedString).setParseAction(lambda x: '') 687 | doNotParse = self.commentGrammars | self.commentInProgress | pyparsing.quotedString 688 | afterElements = \ 689 | pyparsing.Optional(pipe + pyparsing.SkipTo(outputParser ^ stringEnd, ignore=doNotParse)('pipeTo')) + \ 690 | pyparsing.Optional(outputParser + pyparsing.SkipTo(stringEnd, ignore=doNotParse).setParseAction(lambda x: x[0].strip())('outputTo')) 691 | if self.case_insensitive: 692 | self.multilineCommand.setParseAction(lambda x: x[0].lower()) 693 | oneLineCommand.setParseAction(lambda x: x[0].lower()) 694 | if self.blankLinesAllowed: 695 | self.blankLineTerminationParser = pyparsing.NoMatch 696 | else: 697 | self.blankLineTerminator = (pyparsing.lineEnd + pyparsing.lineEnd)('terminator') 698 | self.blankLineTerminator.setResultsName('terminator') 699 | self.blankLineTerminationParser = ((self.multilineCommand ^ oneLineCommand) + pyparsing.SkipTo(self.blankLineTerminator, ignore=doNotParse).setParseAction(lambda x: x[0].strip())('args') + self.blankLineTerminator)('statement') 700 | self.multilineParser = (((self.multilineCommand ^ oneLineCommand) + pyparsing.SkipTo(terminatorParser, ignore=doNotParse).setParseAction(lambda x: x[0].strip())('args') + terminatorParser)('statement') + 701 | pyparsing.SkipTo(outputParser ^ pipe ^ stringEnd, ignore=doNotParse).setParseAction(lambda x: x[0].strip())('suffix') + afterElements) 702 | self.multilineParser.ignore(self.commentInProgress) 703 | self.singleLineParser = ((oneLineCommand + pyparsing.SkipTo(terminatorParser ^ stringEnd ^ pipe ^ outputParser, ignore=doNotParse).setParseAction(lambda x:x[0].strip())('args'))('statement') + 704 | pyparsing.Optional(terminatorParser) + afterElements) 705 | #self.multilineParser = self.multilineParser.setResultsName('multilineParser') 706 | #self.singleLineParser = self.singleLineParser.setResultsName('singleLineParser') 707 | self.blankLineTerminationParser = self.blankLineTerminationParser.setResultsName('statement') 708 | self.parser = self.prefixParser + ( 709 | stringEnd | 710 | self.multilineParser | 711 | self.singleLineParser | 712 | self.blankLineTerminationParser | 713 | self.multilineCommand + pyparsing.SkipTo(stringEnd, ignore=doNotParse) 714 | ) 715 | self.parser.ignore(self.commentGrammars) 716 | 717 | inputMark = pyparsing.Literal('<') 718 | inputMark.setParseAction(lambda x: '') 719 | fileName = pyparsing.Word(self.legalChars + '/\\') 720 | inputFrom = fileName('inputFrom') 721 | inputFrom.setParseAction(replace_with_file_contents) 722 | # a not-entirely-satisfactory way of distinguishing < as in "import from" from < 723 | # as in "lesser than" 724 | self.inputParser = inputMark + pyparsing.Optional(inputFrom) + pyparsing.Optional('>') + \ 725 | pyparsing.Optional(fileName) + (pyparsing.stringEnd | '|') 726 | self.inputParser.ignore(self.commentInProgress) 727 | 728 | def preparse(self, raw, **kwargs): 729 | return raw 730 | def postparse(self, parseResult): 731 | return parseResult 732 | 733 | def parsed(self, raw, **kwargs): 734 | if isinstance(raw, ParsedString): 735 | p = raw 736 | else: 737 | # preparse is an overridable hook; default makes no changes 738 | s = self.preparse(raw, **kwargs) 739 | s = self.inputParser.transformString(s.lstrip()) 740 | s = self.commentGrammars.transformString(s) 741 | for (shortcut, expansion) in self.shortcuts: 742 | if s.lower().startswith(shortcut): 743 | s = s.replace(shortcut, expansion + ' ', 1) 744 | break 745 | result = self.parser.parseString(s) 746 | result['raw'] = raw 747 | result['command'] = result.multilineCommand or result.command 748 | result = self.postparse(result) 749 | p = ParsedString(result.args) 750 | p.parsed = result 751 | p.parser = self.parsed 752 | for (key, val) in kwargs.items(): 753 | p.parsed[key] = val 754 | return p 755 | 756 | def postparsing_precmd(self, statement): 757 | stop = 0 758 | return stop, statement 759 | def postparsing_postcmd(self, stop): 760 | return stop 761 | 762 | def func_named(self, arg): 763 | result = None 764 | target = 'do_' + arg 765 | if target in dir(self): 766 | result = target 767 | else: 768 | if self.abbrev: # accept shortened versions of commands 769 | funcs = [fname for fname in self.keywords if fname.startswith(arg)] 770 | if len(funcs) == 1: 771 | result = 'do_' + funcs[0] 772 | return result 773 | def onecmd_plus_hooks(self, line): 774 | # The outermost level of try/finally nesting can be condensed once 775 | # Python 2.4 support can be dropped. 776 | stop = 0 777 | try: 778 | try: 779 | statement = self.complete_statement(line) 780 | (stop, statement) = self.postparsing_precmd(statement) 781 | if stop: 782 | return self.postparsing_postcmd(stop) 783 | if statement.parsed.command not in self.excludeFromHistory: 784 | self.history.append(statement.parsed.raw) 785 | try: 786 | self.redirect_output(statement) 787 | timestart = datetime.datetime.now() 788 | statement = self.precmd(statement) 789 | stop = self.onecmd(statement) 790 | stop = self.postcmd(stop, statement) 791 | if self.timing: 792 | self.pfeedback('\nElapsed: %s\n' % str(datetime.datetime.now() - timestart)) 793 | finally: 794 | self.restore_output(statement) 795 | except EmptyStatement: 796 | return 0 797 | except Exception as e: 798 | self.perror(str(e), statement) 799 | finally: 800 | return self.postparsing_postcmd(stop) 801 | def complete_statement(self, line): 802 | """Keep accepting lines of input until the command is complete.""" 803 | if (not line) or ( 804 | not pyparsing.Or(self.commentGrammars). 805 | setParseAction(lambda x: '').transformString(line)): 806 | raise EmptyStatement() 807 | statement = self.parsed(line) 808 | while statement.parsed.multilineCommand and (statement.parsed.terminator == ''): 809 | statement = '%s\n%s' % (statement.parsed.raw, 810 | self.pseudo_raw_input(self.continuation_prompt)) 811 | statement = self.parsed(statement) 812 | if not statement.parsed.command: 813 | raise EmptyStatement() 814 | return statement 815 | 816 | def redirect_output(self, statement): 817 | if statement.parsed.pipeTo: 818 | self.kept_state = Statekeeper(self, ('stdout',)) 819 | self.kept_sys = Statekeeper(sys, ('stdout',)) 820 | self.redirect = subprocess.Popen(statement.parsed.pipeTo, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 821 | sys.stdout = self.stdout = self.redirect.stdin 822 | elif statement.parsed.output: 823 | if (not statement.parsed.outputTo) and (not can_clip): 824 | raise EnvironmentError('Cannot redirect to paste buffer; install ``xclip`` and re-run to enable') 825 | self.kept_state = Statekeeper(self, ('stdout',)) 826 | self.kept_sys = Statekeeper(sys, ('stdout',)) 827 | if statement.parsed.outputTo: 828 | mode = 'w' 829 | if statement.parsed.output == 2 * self.redirector: 830 | mode = 'a' 831 | sys.stdout = self.stdout = open(os.path.expanduser(statement.parsed.outputTo), mode) 832 | else: 833 | sys.stdout = self.stdout = tempfile.TemporaryFile(mode="w+") 834 | if statement.parsed.output == '>>': 835 | self.stdout.write(get_paste_buffer()) 836 | 837 | def restore_output(self, statement): 838 | if self.kept_state: 839 | if statement.parsed.output: 840 | if not statement.parsed.outputTo: 841 | self.stdout.seek(0) 842 | write_to_paste_buffer(self.stdout.read()) 843 | elif statement.parsed.pipeTo: 844 | for result in self.redirect.communicate(): 845 | self.kept_state.stdout.write(result or '') 846 | self.stdout.close() 847 | self.kept_state.restore() 848 | self.kept_sys.restore() 849 | self.kept_state = None 850 | 851 | def onecmd(self, line): 852 | """Interpret the argument as though it had been typed in response 853 | to the prompt. 854 | 855 | This may be overridden, but should not normally need to be; 856 | see the precmd() and postcmd() methods for useful execution hooks. 857 | The return value is a flag indicating whether interpretation of 858 | commands by the interpreter should stop. 859 | 860 | This (`cmd2`) version of `onecmd` already override's `cmd`'s `onecmd`. 861 | 862 | """ 863 | statement = self.parsed(line) 864 | self.lastcmd = statement.parsed.raw 865 | funcname = self.func_named(statement.parsed.command) 866 | if not funcname: 867 | return self._default(statement) 868 | try: 869 | func = getattr(self, funcname) 870 | except AttributeError: 871 | return self._default(statement) 872 | stop = func(statement) 873 | return stop 874 | 875 | def _default(self, statement): 876 | arg = statement.full_parsed_statement() 877 | if self.default_to_shell: 878 | result = os.system(arg) 879 | if not result: 880 | return self.postparsing_postcmd(None) 881 | return self.postparsing_postcmd(self.default(arg)) 882 | 883 | def pseudo_raw_input(self, prompt): 884 | """copied from cmd's cmdloop; like raw_input, but accounts for changed stdin, stdout""" 885 | 886 | if self.use_rawinput: 887 | try: 888 | line = raw_input(prompt) 889 | except EOFError: 890 | line = 'EOF' 891 | else: 892 | self.stdout.write(prompt) 893 | self.stdout.flush() 894 | line = self.stdin.readline() 895 | if not len(line): 896 | line = 'EOF' 897 | else: 898 | if line[-1] == '\n': # this was always true in Cmd 899 | line = line[:-1] 900 | return line 901 | 902 | def _cmdloop(self, intro=None): 903 | """Repeatedly issue a prompt, accept input, parse an initial prefix 904 | off the received input, and dispatch to action methods, passing them 905 | the remainder of the line as argument. 906 | """ 907 | 908 | # An almost perfect copy from Cmd; however, the pseudo_raw_input portion 909 | # has been split out so that it can be called separately 910 | 911 | self.preloop() 912 | if self.use_rawinput and self.completekey: 913 | try: 914 | import readline 915 | self.old_completer = readline.get_completer() 916 | readline.set_completer(self.complete) 917 | readline.parse_and_bind(self.completekey+": complete") 918 | except ImportError: 919 | pass 920 | try: 921 | if intro is not None: 922 | self.intro = intro 923 | if self.intro: 924 | self.stdout.write(str(self.intro)+"\n") 925 | stop = None 926 | while not stop: 927 | if self.cmdqueue: 928 | line = self.cmdqueue.pop(0) 929 | else: 930 | line = self.pseudo_raw_input(self.prompt) 931 | if (self.echo) and (isinstance(self.stdin, file)): 932 | self.stdout.write(line + '\n') 933 | stop = self.onecmd_plus_hooks(line) 934 | self.postloop() 935 | finally: 936 | if self.use_rawinput and self.completekey: 937 | try: 938 | import readline 939 | readline.set_completer(self.old_completer) 940 | except ImportError: 941 | pass 942 | return stop 943 | 944 | def do_EOF(self, arg): 945 | return self._STOP_SCRIPT_NO_EXIT # End of script; should not exit app 946 | do_eof = do_EOF 947 | 948 | def do_quit(self, arg): 949 | return self._STOP_AND_EXIT 950 | do_exit = do_quit 951 | do_q = do_quit 952 | 953 | def select(self, options, prompt='Your choice? '): 954 | '''Presents a numbered menu to the user. Modelled after 955 | the bash shell's SELECT. Returns the item chosen. 956 | 957 | Argument ``options`` can be: 958 | 959 | | a single string -> will be split into one-word options 960 | | a list of strings -> will be offered as options 961 | | a list of tuples -> interpreted as (value, text), so 962 | that the return value can differ from 963 | the text advertised to the user ''' 964 | if isinstance(options, basestring): 965 | options = zip(options.split(), options.split()) 966 | fulloptions = [] 967 | for opt in options: 968 | if isinstance(opt, basestring): 969 | fulloptions.append((opt, opt)) 970 | else: 971 | try: 972 | fulloptions.append((opt[0], opt[1])) 973 | except IndexError: 974 | fulloptions.append((opt[0], opt[0])) 975 | for (idx, (value, text)) in enumerate(fulloptions): 976 | self.poutput(' %2d. %s\n' % (idx+1, text)) 977 | while True: 978 | response = raw_input(prompt) 979 | try: 980 | response = int(response) 981 | result = fulloptions[response - 1][0] 982 | break 983 | except ValueError: 984 | pass # loop and ask again 985 | return result 986 | 987 | @options([make_option('-l', '--long', action="store_true", 988 | help="describe function of parameter")]) 989 | def do_show(self, arg, opts): 990 | '''Shows value of a parameter.''' 991 | param = arg.strip().lower() 992 | result = {} 993 | maxlen = 0 994 | for p in self.settable: 995 | if (not param) or p.startswith(param): 996 | result[p] = '%s: %s' % (p, str(getattr(self, p))) 997 | maxlen = max(maxlen, len(result[p])) 998 | if result: 999 | for p in sorted(result): 1000 | if opts.long: 1001 | self.poutput('%s # %s' % (result[p].ljust(maxlen), self.settable[p])) 1002 | else: 1003 | self.poutput(result[p]) 1004 | else: 1005 | raise NotImplementedError("Parameter '%s' not supported (type 'show' for list of parameters)." % param) 1006 | 1007 | def do_set(self, arg): 1008 | ''' 1009 | Sets a cmd2 parameter. Accepts abbreviated parameter names so long 1010 | as there is no ambiguity. Call without arguments for a list of 1011 | settable parameters with their values.''' 1012 | try: 1013 | statement, paramName, val = arg.parsed.raw.split(None, 2) 1014 | val = val.strip() 1015 | paramName = paramName.strip().lower() 1016 | if paramName not in self.settable: 1017 | hits = [p for p in self.settable if p.startswith(paramName)] 1018 | if len(hits) == 1: 1019 | paramName = hits[0] 1020 | else: 1021 | return self.do_show(paramName) 1022 | currentVal = getattr(self, paramName) 1023 | if (val[0] == val[-1]) and val[0] in ("'", '"'): 1024 | val = val[1:-1] 1025 | else: 1026 | val = cast(currentVal, val) 1027 | setattr(self, paramName, val) 1028 | self.stdout.write('%s - was: %s\nnow: %s\n' % (paramName, currentVal, val)) 1029 | if currentVal != val: 1030 | try: 1031 | onchange_hook = getattr(self, '_onchange_%s' % paramName) 1032 | onchange_hook(old=currentVal, new=val) 1033 | except AttributeError: 1034 | pass 1035 | except (ValueError, AttributeError, NotSettableError) as e: 1036 | self.do_show(arg) 1037 | 1038 | def do_pause(self, arg): 1039 | 'Displays the specified text then waits for the user to press RETURN.' 1040 | raw_input(arg + '\n') 1041 | 1042 | def do_shell(self, arg): 1043 | 'execute a command as if at the OS prompt.' 1044 | os.system(arg) 1045 | 1046 | def do_py(self, arg): 1047 | ''' 1048 | py : Executes a Python command. 1049 | py: Enters interactive Python mode. 1050 | End with ``Ctrl-D`` (Unix) / ``Ctrl-Z`` (Windows), ``quit()``, '`exit()``. 1051 | Non-python commands can be issued with ``cmd("your command")``. 1052 | Run python code from external files with ``run("filename.py")`` 1053 | ''' 1054 | self.pystate['self'] = self 1055 | arg = arg.parsed.raw[2:].strip() 1056 | localvars = (self.locals_in_py and self.pystate) or {} 1057 | interp = InteractiveConsole(locals=localvars) 1058 | interp.runcode('import sys, os;sys.path.insert(0, os.getcwd())') 1059 | if arg.strip(): 1060 | interp.runcode(arg) 1061 | else: 1062 | def quit(): 1063 | raise EmbeddedConsoleExit 1064 | def onecmd_plus_hooks(arg): 1065 | return self.onecmd_plus_hooks(arg + '\n') 1066 | def run(arg): 1067 | try: 1068 | file = open(arg) 1069 | interp.runcode(file.read()) 1070 | file.close() 1071 | except IOError as e: 1072 | self.perror(e) 1073 | self.pystate['quit'] = quit 1074 | self.pystate['exit'] = quit 1075 | self.pystate['cmd'] = onecmd_plus_hooks 1076 | self.pystate['run'] = run 1077 | try: 1078 | cprt = 'Type "help", "copyright", "credits" or "license" for more information.' 1079 | keepstate = Statekeeper(sys, ('stdin','stdout')) 1080 | sys.stdout = self.stdout 1081 | sys.stdin = self.stdin 1082 | interp.interact(banner= "Python %s on %s\n%s\n(%s)\n%s" % 1083 | (sys.version, sys.platform, cprt, self.__class__.__name__, self.do_py.__doc__)) 1084 | except EmbeddedConsoleExit: 1085 | pass 1086 | keepstate.restore() 1087 | 1088 | @options([make_option('-s', '--script', action="store_true", help="Script format; no separation lines"), 1089 | ], arg_desc = '(limit on which commands to include)') 1090 | def do_history(self, arg, opts): 1091 | """history [arg]: lists past commands issued 1092 | 1093 | | no arg: list all 1094 | | arg is integer: list one history item, by index 1095 | | arg is string: string search 1096 | | arg is /enclosed in forward-slashes/: regular expression search 1097 | """ 1098 | if arg: 1099 | history = self.history.get(arg) 1100 | else: 1101 | history = self.history 1102 | for hi in history: 1103 | if opts.script: 1104 | self.poutput(hi) 1105 | else: 1106 | self.stdout.write(hi.pr()) 1107 | def last_matching(self, arg): 1108 | try: 1109 | if arg: 1110 | return self.history.get(arg)[-1] 1111 | else: 1112 | return self.history[-1] 1113 | except IndexError: 1114 | return None 1115 | def do_list(self, arg): 1116 | """list [arg]: lists last command issued 1117 | 1118 | no arg -> list most recent command 1119 | arg is integer -> list one history item, by index 1120 | a..b, a:b, a:, ..b -> list spans from a (or start) to b (or end) 1121 | arg is string -> list all commands matching string search 1122 | arg is /enclosed in forward-slashes/ -> regular expression search 1123 | """ 1124 | try: 1125 | history = self.history.span(arg or '-1') 1126 | except IndexError: 1127 | history = self.history.search(arg) 1128 | for hi in history: 1129 | self.poutput(hi.pr()) 1130 | 1131 | do_hi = do_history 1132 | do_l = do_list 1133 | do_li = do_list 1134 | 1135 | def do_ed(self, arg): 1136 | """ed: edit most recent command in text editor 1137 | ed [N]: edit numbered command from history 1138 | ed [filename]: edit specified file name 1139 | 1140 | commands are run after editor is closed. 1141 | "set edit (program-name)" or set EDITOR environment variable 1142 | to control which editing program is used.""" 1143 | if not self.editor: 1144 | raise EnvironmentError("Please use 'set editor' to specify your text editing program of choice.") 1145 | filename = self.default_file_name 1146 | if arg: 1147 | try: 1148 | buffer = self.last_matching(int(arg)) 1149 | except ValueError: 1150 | filename = arg 1151 | buffer = '' 1152 | else: 1153 | buffer = self.history[-1] 1154 | 1155 | if buffer: 1156 | f = open(os.path.expanduser(filename), 'w') 1157 | f.write(buffer or '') 1158 | f.close() 1159 | 1160 | os.system('%s %s' % (self.editor, filename)) 1161 | self.do__load(filename) 1162 | do_edit = do_ed 1163 | 1164 | saveparser = (pyparsing.Optional(pyparsing.Word(pyparsing.nums)^'*')("idx") + 1165 | pyparsing.Optional(pyparsing.Word(legalChars + '/\\'))("fname") + 1166 | pyparsing.stringEnd) 1167 | def do_save(self, arg): 1168 | """`save [N] [filename.ext]` 1169 | 1170 | Saves command from history to file. 1171 | 1172 | | N => Number of command (from history), or `*`; 1173 | | most recent command if omitted""" 1174 | 1175 | try: 1176 | args = self.saveparser.parseString(arg) 1177 | except pyparsing.ParseException: 1178 | self.perror('Could not understand save target %s' % arg) 1179 | raise SyntaxError(self.do_save.__doc__) 1180 | fname = args.fname or self.default_file_name 1181 | if args.idx == '*': 1182 | saveme = '\n\n'.join(self.history[:]) 1183 | elif args.idx: 1184 | saveme = self.history[int(args.idx)-1] 1185 | else: 1186 | saveme = self.history[-1] 1187 | try: 1188 | f = open(os.path.expanduser(fname), 'w') 1189 | f.write(saveme) 1190 | f.close() 1191 | self.pfeedback('Saved to %s' % (fname)) 1192 | except Exception as e: 1193 | self.perror('Error saving %s' % (fname)) 1194 | raise 1195 | 1196 | def read_file_or_url(self, fname): 1197 | # TODO: not working on localhost 1198 | if isinstance(fname, file): 1199 | result = open(fname, 'r') 1200 | else: 1201 | match = self.urlre.match(fname) 1202 | if match: 1203 | result = urllib.urlopen(match.group(1)) 1204 | else: 1205 | fname = os.path.expanduser(fname) 1206 | try: 1207 | result = open(os.path.expanduser(fname), 'r') 1208 | except IOError: 1209 | result = open('%s.%s' % (os.path.expanduser(fname), 1210 | self.defaultExtension), 'r') 1211 | return result 1212 | 1213 | def do__relative_load(self, arg=None): 1214 | ''' 1215 | Runs commands in script at file or URL; if this is called from within an 1216 | already-running script, the filename will be interpreted relative to the 1217 | already-running script's directory.''' 1218 | if arg: 1219 | arg = arg.split(None, 1) 1220 | targetname, args = arg[0], (arg[1:] or [''])[0] 1221 | targetname = os.path.join(self.current_script_dir or '', targetname) 1222 | self.do__load('%s %s' % (targetname, args)) 1223 | 1224 | urlre = re.compile('(https?://[-\\w\\./]+)') 1225 | def do_load(self, arg=None): 1226 | """Runs script of command(s) from a file or URL.""" 1227 | if arg is None: 1228 | targetname = self.default_file_name 1229 | else: 1230 | arg = arg.split(None, 1) 1231 | targetname, args = arg[0], (arg[1:] or [''])[0].strip() 1232 | try: 1233 | target = self.read_file_or_url(targetname) 1234 | except IOError as e: 1235 | self.perror('Problem accessing script from %s: \n%s' % (targetname, e)) 1236 | return 1237 | keepstate = Statekeeper(self, ('stdin','use_rawinput','prompt', 1238 | 'continuation_prompt','current_script_dir')) 1239 | self.stdin = target 1240 | self.use_rawinput = False 1241 | self.prompt = self.continuation_prompt = '' 1242 | self.current_script_dir = os.path.split(targetname)[0] 1243 | stop = self._cmdloop() 1244 | self.stdin.close() 1245 | keepstate.restore() 1246 | self.lastcmd = '' 1247 | return stop and (stop != self._STOP_SCRIPT_NO_EXIT) 1248 | do__load = do_load # avoid an unfortunate legacy use of do_load from sqlpython 1249 | 1250 | def do_run(self, arg): 1251 | """run [arg]: re-runs an earlier command 1252 | 1253 | no arg -> run most recent command 1254 | arg is integer -> run one history item, by index 1255 | arg is string -> run most recent command by string search 1256 | arg is /enclosed in forward-slashes/ -> run most recent by regex 1257 | """ 1258 | 'run [N]: runs the SQL that was run N commands ago' 1259 | runme = self.last_matching(arg) 1260 | self.pfeedback(runme) 1261 | if runme: 1262 | stop = self.onecmd_plus_hooks(runme) 1263 | do_r = do_run 1264 | 1265 | def fileimport(self, statement, source): 1266 | try: 1267 | f = open(os.path.expanduser(source)) 1268 | except IOError: 1269 | self.stdout.write("Couldn't read from file %s\n" % source) 1270 | return '' 1271 | data = f.read() 1272 | f.close() 1273 | return data 1274 | 1275 | def runTranscriptTests(self, callargs): 1276 | class TestMyAppCase(Cmd2TestCase): 1277 | CmdApp = self.__class__ 1278 | self.__class__.testfiles = callargs 1279 | sys.argv = [sys.argv[0]] # the --test argument upsets unittest.main() 1280 | testcase = TestMyAppCase() 1281 | runner = unittest.TextTestRunner() 1282 | result = runner.run(testcase) 1283 | result.printErrors() 1284 | 1285 | def run_commands_at_invocation(self, callargs): 1286 | for initial_command in callargs: 1287 | if self.onecmd_plus_hooks(initial_command + '\n'): 1288 | return self._STOP_AND_EXIT 1289 | 1290 | def cmdloop(self): 1291 | parser = optparse.OptionParser() 1292 | parser.add_option('-t', '--test', dest='test', 1293 | action="store_true", 1294 | help='Test against transcript(s) in FILE (wildcards OK)') 1295 | (callopts, callargs) = parser.parse_args() 1296 | if callopts.test: 1297 | self.runTranscriptTests(callargs) 1298 | else: 1299 | if not self.run_commands_at_invocation(callargs): 1300 | self._cmdloop() 1301 | 1302 | class HistoryItem(str): 1303 | listformat = '-------------------------[%d]\n%s\n' 1304 | def __init__(self, instr): 1305 | str.__init__(self) 1306 | self.lowercase = self.lower() 1307 | self.idx = None 1308 | def pr(self): 1309 | return self.listformat % (self.idx, str(self)) 1310 | 1311 | class History(list): 1312 | '''A list of HistoryItems that knows how to respond to user requests. 1313 | >>> h = History([HistoryItem('first'), HistoryItem('second'), HistoryItem('third'), HistoryItem('fourth')]) 1314 | >>> h.span('-2..') 1315 | ['third', 'fourth'] 1316 | >>> h.span('2..3') 1317 | ['second', 'third'] 1318 | >>> h.span('3') 1319 | ['third'] 1320 | >>> h.span(':') 1321 | ['first', 'second', 'third', 'fourth'] 1322 | >>> h.span('2..') 1323 | ['second', 'third', 'fourth'] 1324 | >>> h.span('-1') 1325 | ['fourth'] 1326 | >>> h.span('-2..-3') 1327 | ['third', 'second'] 1328 | >>> h.search('o') 1329 | ['second', 'fourth'] 1330 | >>> h.search('/IR/') 1331 | ['first', 'third'] 1332 | ''' 1333 | def zero_based_index(self, onebased): 1334 | result = onebased 1335 | if result > 0: 1336 | result -= 1 1337 | return result 1338 | def to_index(self, raw): 1339 | if raw: 1340 | result = self.zero_based_index(int(raw)) 1341 | else: 1342 | result = None 1343 | return result 1344 | def search(self, target): 1345 | target = target.strip() 1346 | if target[0] == target[-1] == '/' and len(target) > 1: 1347 | target = target[1:-1] 1348 | else: 1349 | target = re.escape(target) 1350 | pattern = re.compile(target, re.IGNORECASE) 1351 | return [s for s in self if pattern.search(s)] 1352 | spanpattern = re.compile(r'^\s*(?P\-?\d+)?\s*(?P:|(\.{2,}))?\s*(?P\-?\d+)?\s*$') 1353 | def span(self, raw): 1354 | if raw.lower() in ('*', '-', 'all'): 1355 | raw = ':' 1356 | results = self.spanpattern.search(raw) 1357 | if not results: 1358 | raise IndexError 1359 | if not results.group('separator'): 1360 | return [self[self.to_index(results.group('start'))]] 1361 | start = self.to_index(results.group('start')) 1362 | end = self.to_index(results.group('end')) 1363 | reverse = False 1364 | if end is not None: 1365 | if end < start: 1366 | (start, end) = (end, start) 1367 | reverse = True 1368 | end += 1 1369 | result = self[start:end] 1370 | if reverse: 1371 | result.reverse() 1372 | return result 1373 | 1374 | rangePattern = re.compile(r'^\s*(?P[\d]+)?\s*\-\s*(?P[\d]+)?\s*$') 1375 | def append(self, new): 1376 | new = HistoryItem(new) 1377 | list.append(self, new) 1378 | new.idx = len(self) 1379 | def extend(self, new): 1380 | for n in new: 1381 | self.append(n) 1382 | 1383 | def get(self, getme=None, fromEnd=False): 1384 | if not getme: 1385 | return self 1386 | try: 1387 | getme = int(getme) 1388 | if getme < 0: 1389 | return self[:(-1 * getme)] 1390 | else: 1391 | return [self[getme-1]] 1392 | except IndexError: 1393 | return [] 1394 | except ValueError: 1395 | rangeResult = self.rangePattern.search(getme) 1396 | if rangeResult: 1397 | start = rangeResult.group('start') or None 1398 | end = rangeResult.group('start') or None 1399 | if start: 1400 | start = int(start) - 1 1401 | if end: 1402 | end = int(end) 1403 | return self[start:end] 1404 | 1405 | getme = getme.strip() 1406 | 1407 | if getme.startswith(r'/') and getme.endswith(r'/'): 1408 | finder = re.compile(getme[1:-1], re.DOTALL | re.MULTILINE | re.IGNORECASE) 1409 | def isin(hi): 1410 | return finder.search(hi) 1411 | else: 1412 | def isin(hi): 1413 | return (getme.lower() in hi.lowercase) 1414 | return [itm for itm in self if isin(itm)] 1415 | 1416 | class NotSettableError(Exception): 1417 | pass 1418 | 1419 | def cast(current, new): 1420 | """Tries to force a new value into the same type as the current.""" 1421 | typ = type(current) 1422 | if typ == bool: 1423 | try: 1424 | return bool(int(new)) 1425 | except (ValueError, TypeError): 1426 | pass 1427 | try: 1428 | new = new.lower() 1429 | except: 1430 | pass 1431 | if (new=='on') or (new[0] in ('y','t')): 1432 | return True 1433 | if (new=='off') or (new[0] in ('n','f')): 1434 | return False 1435 | else: 1436 | try: 1437 | return typ(new) 1438 | except: 1439 | pass 1440 | print ("Problem setting parameter (now %s) to %s; incorrect type?" % (current, new)) 1441 | return current 1442 | 1443 | class Statekeeper(object): 1444 | def __init__(self, obj, attribs): 1445 | self.obj = obj 1446 | self.attribs = attribs 1447 | if self.obj: 1448 | self.save() 1449 | def save(self): 1450 | for attrib in self.attribs: 1451 | setattr(self, attrib, getattr(self.obj, attrib)) 1452 | def restore(self): 1453 | if self.obj: 1454 | for attrib in self.attribs: 1455 | setattr(self.obj, attrib, getattr(self, attrib)) 1456 | 1457 | class Borg(object): 1458 | '''All instances of any Borg subclass will share state. 1459 | from Python Cookbook, 2nd Ed., recipe 6.16''' 1460 | _shared_state = {} 1461 | def __new__(cls, *a, **k): 1462 | obj = object.__new__(cls, *a, **k) 1463 | obj.__dict__ = cls._shared_state 1464 | return obj 1465 | 1466 | class OutputTrap(Borg): 1467 | '''Instantiate an OutputTrap to divert/capture ALL stdout output. For use in unit testing. 1468 | Call `tearDown()` to return to normal output.''' 1469 | def __init__(self): 1470 | self.contents = '' 1471 | self.old_stdout = sys.stdout 1472 | sys.stdout = self 1473 | def write(self, txt): 1474 | self.contents += txt 1475 | def read(self): 1476 | result = self.contents 1477 | self.contents = '' 1478 | return result 1479 | def tearDown(self): 1480 | sys.stdout = self.old_stdout 1481 | self.contents = '' 1482 | 1483 | class Cmd2TestCase(unittest.TestCase): 1484 | '''Subclass this, setting CmdApp, to make a unittest.TestCase class 1485 | that will execute the commands in a transcript file and expect the results shown. 1486 | See example.py''' 1487 | CmdApp = None 1488 | def fetchTranscripts(self): 1489 | self.transcripts = {} 1490 | for fileset in self.CmdApp.testfiles: 1491 | for fname in glob.glob(fileset): 1492 | tfile = open(fname) 1493 | self.transcripts[fname] = iter(tfile.readlines()) 1494 | tfile.close() 1495 | if not len(self.transcripts): 1496 | raise StandardError("No test files found - nothing to test.") 1497 | def setUp(self): 1498 | if self.CmdApp: 1499 | self.outputTrap = OutputTrap() 1500 | self.cmdapp = self.CmdApp() 1501 | self.fetchTranscripts() 1502 | def runTest(self): # was testall 1503 | if self.CmdApp: 1504 | its = sorted(self.transcripts.items()) 1505 | for (fname, transcript) in its: 1506 | self._test_transcript(fname, transcript) 1507 | regexPattern = pyparsing.QuotedString(quoteChar=r'/', escChar='\\', multiline=True, unquoteResults=True) 1508 | regexPattern.ignore(pyparsing.cStyleComment) 1509 | notRegexPattern = pyparsing.Word(pyparsing.printables) 1510 | notRegexPattern.setParseAction(lambda t: re.escape(t[0])) 1511 | expectationParser = regexPattern | notRegexPattern 1512 | anyWhitespace = re.compile(r'\s', re.DOTALL | re.MULTILINE) 1513 | def _test_transcript(self, fname, transcript): 1514 | lineNum = 0 1515 | finished = False 1516 | line = transcript.next() 1517 | lineNum += 1 1518 | tests_run = 0 1519 | while not finished: 1520 | # Scroll forward to where actual commands begin 1521 | while not line.startswith(self.cmdapp.prompt): 1522 | try: 1523 | line = transcript.next() 1524 | except StopIteration: 1525 | finished = True 1526 | break 1527 | lineNum += 1 1528 | command = [line[len(self.cmdapp.prompt):]] 1529 | line = transcript.next() 1530 | # Read the entirety of a multi-line command 1531 | while line.startswith(self.cmdapp.continuation_prompt): 1532 | command.append(line[len(self.cmdapp.continuation_prompt):]) 1533 | try: 1534 | line = transcript.next() 1535 | except StopIteration: 1536 | raise (StopIteration, 1537 | 'Transcript broke off while reading command beginning at line %d with\n%s' 1538 | % (command[0])) 1539 | lineNum += 1 1540 | command = ''.join(command) 1541 | # Send the command into the application and capture the resulting output 1542 | stop = self.cmdapp.onecmd_plus_hooks(command) 1543 | #TODO: should act on ``stop`` 1544 | result = self.outputTrap.read() 1545 | # Read the expected result from transcript 1546 | if line.startswith(self.cmdapp.prompt): 1547 | message = '\nFile %s, line %d\nCommand was:\n%r\nExpected: (nothing)\nGot:\n%r\n'%\ 1548 | (fname, lineNum, command, result) 1549 | self.assert_(not(result.strip()), message) 1550 | continue 1551 | expected = [] 1552 | while not line.startswith(self.cmdapp.prompt): 1553 | expected.append(line) 1554 | try: 1555 | line = transcript.next() 1556 | except StopIteration: 1557 | finished = True 1558 | break 1559 | lineNum += 1 1560 | expected = ''.join(expected) 1561 | # Compare actual result to expected 1562 | message = '\nFile %s, line %d\nCommand was:\n%s\nExpected:\n%s\nGot:\n%s\n'%\ 1563 | (fname, lineNum, command, expected, result) 1564 | expected = self.expectationParser.transformString(expected) 1565 | # checking whitespace is a pain - let's skip it 1566 | expected = self.anyWhitespace.sub('', expected) 1567 | result = self.anyWhitespace.sub('', result) 1568 | self.assert_(re.match(expected, result, re.MULTILINE | re.DOTALL), message) 1569 | 1570 | def tearDown(self): 1571 | if self.CmdApp: 1572 | self.outputTrap.tearDown() 1573 | 1574 | if __name__ == '__main__': 1575 | doctest.testmod(optionflags = doctest.NORMALIZE_WHITESPACE) 1576 | 1577 | ''' 1578 | To make your application transcript-testable, replace 1579 | 1580 | :: 1581 | 1582 | app = MyApp() 1583 | app.cmdloop() 1584 | 1585 | with 1586 | 1587 | :: 1588 | 1589 | app = MyApp() 1590 | cmd2.run(app) 1591 | 1592 | Then run a session of your application and paste the entire screen contents 1593 | into a file, ``transcript.test``, and invoke the test like:: 1594 | 1595 | python myapp.py --test transcript.test 1596 | 1597 | Wildcards can be used to test against multiple transcript files. 1598 | ''' 1599 | 1600 | 1601 | -------------------------------------------------------------------------------- /lib/db.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # site : beebeeto.com 4 | # team : n0tr00t security 5 | 6 | import os 7 | import sys 8 | import sqlite3 as sq3 9 | import collections as clt 10 | 11 | try: 12 | import simplejson as json 13 | except ImportError: 14 | import json 15 | 16 | from poc import Poc 17 | 18 | 19 | class Database(object): 20 | tables = { 21 | 'poc': [ 22 | 'id', 23 | 'name', 24 | 'rank', 25 | 'level', 26 | 'author', 27 | 'create_date', 28 | 'protocol', 29 | 'port', 30 | 'layer4_protocol', 31 | 'app_name', 32 | 'vul_type', 33 | 'tag', 34 | 'desc', 35 | 'batchable', 36 | 'path', 37 | ], 38 | } 39 | 40 | def __init__(self, dbFilePath='./hive.db', pocDir='./pocs'): 41 | self.dbConn = sq3.connect(dbFilePath) 42 | self.pocDir = pocDir 43 | self.cursor = self.dbConn.cursor() 44 | 45 | def updtDbFromPocs(self, pocDir='../pocs/'): 46 | ''' 47 | Update local sqlite database according to the 48 | poc_info in the pocs' source code online database. 49 | ''' 50 | updatedNum = 0 51 | insertedNum = 0 52 | errNum = 0 53 | errPocs = [] 54 | for pocFileName in os.listdir(pocDir): 55 | if pocFileName.startswith('poc') and pocFileName.endswith('py'): 56 | try: 57 | pocInfo = Poc(os.path.join(pocDir, pocFileName), 58 | batchable=False).module.MyPoc.poc_info 59 | status = self.__updtFromPocInfo(pocInfo) 60 | if status == 'inserted': 61 | insertedNum += 1 62 | elif status == 'updated': 63 | updatedNum += 1 64 | except Exception, err: 65 | errNum += 1 66 | errPocs.append('%s:%s...' % (pocFileName, str(err)[:50])) 67 | self.dbConn.commit() 68 | return (insertedNum, updatedNum, errNum, errPocs) 69 | 70 | def __updtFromPocInfo(self, pocInfo): 71 | self.cursor.execute('SELECT * FROM poc WHERE id=?', 72 | (pocInfo['poc']['id'],)) 73 | pocPath = os.path.join(self.pocDir, 74 | '%s.py' % pocInfo['poc']['id'].replace('-', '_')) 75 | if not self.cursor.fetchone(): 76 | args = [ 77 | pocInfo['poc']['id'], 78 | pocInfo['poc']['name'].decode('utf-8', 'ignore'), 79 | None, 80 | None, 81 | pocInfo['poc']['author'].decode('utf-8', 'ignore'), 82 | pocInfo['poc']['create_date'], 83 | pocInfo['protocol']['name'], 84 | '%s' % ','.join([str(i) for i in pocInfo['protocol']['port']]), 85 | '%s' % ','.join(pocInfo['protocol']['layer4_protocol']), 86 | pocInfo['vul']['app_name'].decode('utf-8', 'ignore'), 87 | pocInfo['vul']['type'], 88 | '%s' % ','.join([i.decode('utf-8', 'ignore') for i in pocInfo['vul']['tag']]), 89 | pocInfo['vul']['desc'].decode('utf-8', 'ignore'), 90 | None, 91 | pocPath if os.path.exists(pocPath) else None, 92 | ] 93 | sql = 'INSERT INTO poc VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)' 94 | self.cursor.execute(sql, args) 95 | return 'inserted' 96 | else: 97 | args = [ 98 | pocInfo['poc']['name'].decode('utf-8', 'ignore'), 99 | pocInfo['poc']['author'].decode('utf-8', 'ignore'), 100 | pocInfo['poc']['create_date'], 101 | pocInfo['protocol']['name'], 102 | '%s' % ','.join([str(i) for i in pocInfo['protocol']['port']]), 103 | '%s' % ','.join(pocInfo['protocol']['layer4_protocol']), 104 | pocInfo['vul']['app_name'].decode('utf-8', 'ignore'), 105 | pocInfo['vul']['type'], 106 | '%s' % ','.join([i.decode('utf-8', 'ignore') for i in pocInfo['vul']['tag']]), 107 | pocInfo['vul']['desc'].decode('utf-8', 'ignore'), 108 | pocPath if os.path.exists(pocPath) else None, 109 | pocInfo['poc']['id'], 110 | ] 111 | sql = 'UPDATE poc SET name=?, author=?, create_date=?, '\ 112 | 'protocol=?, port=?, layer4_protocol=?, app_name=?, '\ 113 | 'vul_type=?, tag=?, desc=?, path=? WHERE id=?' 114 | self.cursor.execute(sql, args) 115 | return 'updated' 116 | 117 | def updtDbFromJson(self, jsonFile): 118 | ''' 119 | Update local sqlite database according to the 120 | dumped json file export from beebeeto.com. 121 | ''' 122 | updatedNum = 0 123 | insertedNum = 0 124 | errNum = 0 125 | errPocs = [] 126 | f = open(jsonFile, 'rbU') 127 | for row in f: 128 | try: 129 | status = self.__updtFromJsonRow( 130 | dictRow=json.loads(row.strip()) 131 | ) 132 | if status == 'inserted': 133 | insertedNum += 1 134 | elif status == 'updated': 135 | updatedNum += 1 136 | except Exception, err: 137 | errNum += 1 138 | errPocs.append('%s:%s...' % (row.strip(), str(err)[:50])) 139 | self.dbConn.commit() 140 | f.close() 141 | return (insertedNum, updatedNum, errNum, errPocs) 142 | 143 | def __updtFromJsonRow(self, dictRow): 144 | pocId = dictRow.get('id') 145 | if not pocId: 146 | return 147 | pocPath = os.path.join(self.pocDir, 148 | '%s.py' % pocId.replace('-', '_')) 149 | pocFile = open(pocPath, 'wb') 150 | pocFile.write(dictRow.get('source_code').encode('utf-8', 'ignore')) 151 | pocFile.close() 152 | dbMapping = [ 153 | ('id', pocId, ), 154 | ('name', dictRow.get('name'), ), 155 | ('rank', dictRow.get('rank'), ) , 156 | ('level', dictRow.get('level'), ), 157 | ('author', dictRow.get('author'),), 158 | ('create_date', dictRow.get('create_date'), ), 159 | ('protocol', None, ), 160 | ('port', None, ), 161 | ('layer4_protocol', None, ), 162 | ('app_name', dictRow.get('app_name'), ), 163 | ('vul_type', dictRow.get('vul_type'), ), 164 | ('tag', dictRow.get('tag'), ), 165 | ('desc', dictRow.get('desc'), ), 166 | ('batchable', dictRow.get('batchable'), ), 167 | ('path', pocPath if os.path.exists(pocPath) else None, ), 168 | ] 169 | self.cursor.execute('SELECT * FROM poc WHERE id=?',(pocId,)) 170 | if not self.cursor.fetchone(): 171 | sql = ' '.join([ 172 | 'INSERT INTO poc VALUES', 173 | '(%s)' % ','.join('?' * len(dbMapping)), 174 | ]) 175 | self.cursor.execute(sql, map(lambda i: i[1], dbMapping)) 176 | return 'inserted' 177 | else: 178 | sql1 = 'UPDATE poc SET' 179 | sql2 = ', '.join(['%s=?' % i[0] for i in dbMapping[1:] if i[1] is not None]) 180 | sql3 = 'WHERE id=?' 181 | sql = ' '.join([sql1, sql2, sql3]) 182 | args = [i[1] for i in dbMapping[1:] if i[1] is not None] 183 | args.append(pocId) 184 | self.cursor.execute(sql, args) 185 | return 'updated' 186 | 187 | def searchStr(self, item): 188 | columns = [ 189 | 'name', 190 | 'app_name', 191 | 'tag', 192 | 'desc', 193 | ] 194 | sql = 'SELECT * FROM poc WHERE ' + ' or '.join( 195 | ['LOWER(%s) like "%%%s%%"' % (col, item.lower()) for col in columns] 196 | ) 197 | self.cursor.execute(sql) 198 | return self.cursor.fetchall() 199 | 200 | def searchPoc(self, pocId): 201 | sql = 'SELECT * FROM poc WHERE id=?' 202 | self.cursor.execute(sql, (pocId,)) 203 | return self.cursor.fetchone() 204 | 205 | def getBatchable(self): 206 | sql = 'SELECT * FROM poc WHERE batchable=1' 207 | self.cursor.execute(sql) 208 | return self.cursor.fetchall() 209 | 210 | def countAll(self): 211 | sql = 'SELECT count(*) FROM poc' 212 | self.cursor.execute(sql) 213 | return self.cursor.fetchone() 214 | 215 | 216 | if __name__ == '__main__': 217 | # testing code 218 | sys.path.append('../') 219 | from SETTINGS import FRAMEWORK_DIR 220 | sys.path.append(FRAMEWORK_DIR) 221 | 222 | from pprint import pprint as pr 223 | 224 | db = Database(dbFilePath='../hive.db', 225 | pocDir='../pocs/') 226 | 227 | #print db.updtDbFromBB2Db(bb2DbFile='../pocdb.json') 228 | #print db.updtDbFromPocs(pocDir='../pocs') 229 | 230 | #pr(db.searchStr(item='discuz')) 231 | #pr(db.countAll()) 232 | #pr(db.searchPoc(pocId='poc-2014-0019')) 233 | 234 | #pr(db.getBatchable()) 235 | pr(db.updtDbFromJson(jsonFile='../pocdb.json')) 236 | -------------------------------------------------------------------------------- /lib/exception.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # site : beebeeto.com 4 | # team : n0tr00t security 5 | 6 | 7 | class BeeScanBaseException(Exception): 8 | pass 9 | 10 | 11 | class BeeScanLaunchException(BeeScanBaseException): 12 | pass 13 | 14 | 15 | class LoadDefaultArgsException(BeeScanBaseException): 16 | pass 17 | -------------------------------------------------------------------------------- /lib/io.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import sys 4 | import platform 5 | import colorama 6 | 7 | msgTypeColor = { 8 | None: ('', ''), 9 | 'info': (colorama.Fore.BLUE, '[*] '), 10 | 'warning': (colorama.Fore.YELLOW, '[!] '), 11 | 'error': (colorama.Fore.RED, '[-] '), 12 | 'ok': (colorama.Fore.GREEN, '[+] '), 13 | } 14 | 15 | def bprint(msgStr, msgType=None, vbCur=1, vbTs=1): 16 | ''' 17 | print function with: 18 | 1. diffrent message type with different color: 19 | info - default color 20 | warning - yellow 21 | error - red 22 | ok - green 23 | 2. verbose level control. 24 | args: 25 | msgStr - the message string. 26 | msgType - message type(info/warning/error/ok). 27 | vbCur - current verbose level. 28 | vbTs - verbose threshold to print the message. 29 | ''' 30 | if vbCur >= vbTs: 31 | # deprecated, because colorama.init() disables the auto-completion 32 | # of cmd2, although it works very well in platform auto-detection. 33 | #colorama.init(autoreset=True) 34 | 35 | if platform.system().lower() == 'windows': 36 | stream = colorama.AnsiToWin32(sys.stdout) 37 | else: 38 | stream = sys.stdout 39 | 40 | print(msgTypeColor[msgType][0] + \ 41 | colorama.Style.BRIGHT + \ 42 | msgStr + \ 43 | colorama.Fore.RESET + \ 44 | colorama.Style.RESET_ALL, 45 | file=stream) 46 | 47 | def bprintPrefix(msgStr, msgType=None, vbCur=1, vbTs=1): 48 | ''' 49 | print function with: 50 | 1. diffrent message type with different color: 51 | info - default color 52 | warning - yellow 53 | error - red 54 | ok - green 55 | 2. verbose level control. 56 | args: 57 | msgStr - the message string. 58 | msgType - message type(info/warning/error/ok). 59 | vbCur - current verbose level. 60 | vbTs - verbose threshold to print the message. 61 | ''' 62 | if vbCur >= vbTs: 63 | # deprecated, because colorama.init() disables the auto-completion 64 | # of cmd2, although it works very well in platform auto-detection. 65 | #colorama.init(autoreset=True) 66 | 67 | if platform.system().lower() == 'windows': 68 | stream = colorama.AnsiToWin32(sys.stdout) 69 | else: 70 | stream = sys.stdout 71 | 72 | print(msgTypeColor[msgType][0] + \ 73 | colorama.Style.BRIGHT + \ 74 | msgTypeColor[msgType][1] + \ 75 | colorama.Fore.RESET + \ 76 | colorama.Style.RESET_ALL, 77 | end='', 78 | file=stream) 79 | print(msgStr, 80 | file=stream) 81 | 82 | 83 | if __name__ == '__main__': 84 | for msgType, color in msgTypeColor.iteritems(): 85 | bprint('233333', msgType) 86 | 87 | for msgType, color in msgTypeColor.iteritems(): 88 | bprintPrefix('666666', msgType) 89 | -------------------------------------------------------------------------------- /lib/poc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # site : beebeeto.com 4 | # team : n0tr00t security 5 | 6 | import os 7 | import re 8 | import imp 9 | 10 | from pprint import pprint 11 | from optparse import Option 12 | from cStringIO import StringIO 13 | 14 | 15 | from exception import LoadDefaultArgsException 16 | 17 | 18 | class Poc(object): 19 | def __init__(self, path, batchable=None): 20 | self.path = path 21 | self.batchable = batchable 22 | self.name = os.path.split(path)[1].rstrip('.py') 23 | self.module = imp.load_source(self.name, path) 24 | 25 | def showInfo(self): 26 | '''print poc_info''' 27 | from pprint import pprint 28 | pprint(self.module.MyPoc.poc_info) 29 | 30 | def _getDefaultOpts(self): 31 | # get the code segment of _init_user_parser() 32 | with open(self.path, 'rbU') as f: 33 | code = f.read() 34 | try: 35 | funcStart = code.index('_init_user_parser(self):') 36 | f.seek(0) 37 | f.read(funcStart) 38 | f.readline() # escape line "def _init_user_parser(self):" 39 | userParserCode = '' 40 | nextLine = f.readline() 41 | while not re.findall('(?:^ \S)|(?:^\S\S)', nextLine): 42 | userParserCode += nextLine 43 | nextLine = f.readline() 44 | pass 45 | except ValueError: # no user defined parser 46 | return {} 47 | 48 | # strip whitespace characters 49 | codeStream = StringIO(userParserCode) 50 | options = [] 51 | argsStr = '' 52 | for eachLine in codeStream: 53 | if eachLine.strip().startswith('self.user_parser.add_option'): 54 | options.append(argsStr) 55 | argsStr = eachLine.strip() 56 | else: 57 | argsStr += eachLine.strip() 58 | options.append(argsStr) 59 | 60 | # convert string args into variable instance 61 | reStr = r"([dest|default|type|help|action]+)\s?=\s?(.*?)[,|\)]" 62 | pattern = re.compile(reStr) 63 | optDict = {} 64 | for argsStr in options[1:]: 65 | args = pattern.findall(argsStr) 66 | dictStr = '{%s}' % ','.join("'%s':%s" % \ 67 | (i[0], i[1]) for i in args if i[0] != 'help') 68 | argsDict = eval(dictStr) 69 | opt = Option('--arbitrary', **argsDict) 70 | optDict.setdefault(argsDict['dest'], opt.default) 71 | return optDict 72 | 73 | def run(self, target, verify=True): 74 | try: 75 | options = self._getDefaultOpts() 76 | except Exception, err: 77 | raise LoadDefaultArgsException(str(err)) 78 | options.update({ 79 | 'target': self.module.MyPoc.normalize_target(target), 80 | 'verify': verify, 81 | 'verbose': False, 82 | }) 83 | args = { 84 | 'options': options, 85 | 'success': None, 86 | 'poc_ret': {}, 87 | } 88 | result = {} 89 | if options['verify']: 90 | ret = self.module.MyPoc.verify(args) 91 | else: 92 | ret = self.module.MyPoc.exploit(args) 93 | result.update(ret) 94 | return result 95 | 96 | if __name__ == '__main__': 97 | # testing code 98 | import os 99 | import sys 100 | BASE_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), 101 | '..', 102 | '..', 103 | '..')) 104 | FRAMEWORK_DIR = os.path.abspath(os.path.join(BASE_DIR, 105 | 'beebeeto-framework')) 106 | sys.path.extend([BASE_DIR, FRAMEWORK_DIR]) 107 | 108 | p = Poc('../pocs/poc_2014_0014.py') 109 | 110 | def test_GetDefaultOpts(): 111 | print p._getDefaultOpts() 112 | 113 | def test_run(): 114 | print p.run(target='www.baidu.com') 115 | 116 | #test_GetDefaultOpts() 117 | test_run() 118 | -------------------------------------------------------------------------------- /lib/pprint2.py: -------------------------------------------------------------------------------- 1 | # Author: Fred L. Drake, Jr. 2 | # fdrake@acm.org 3 | # 4 | # This is a simple little module I wrote to make life easier. I didn't 5 | # see anything quite like it in the library, though I may have overlooked 6 | # something. I wrote this when I was trying to read some heavily nested 7 | # tuples with fairly non-descriptive content. This is modeled very much 8 | # after Lisp/Scheme - style pretty-printing of lists. If you find it 9 | # useful, thank small children who sleep at night. 10 | 11 | """Support to pretty-print lists, tuples, & dictionaries recursively. 12 | 13 | Very simple, but useful, especially in debugging data structures. 14 | 15 | Classes 16 | ------- 17 | 18 | PrettyPrinter() 19 | Handle pretty-printing operations onto a stream using a configured 20 | set of formatting parameters. 21 | 22 | Functions 23 | --------- 24 | 25 | pformat() 26 | Format a Python object into a pretty-printed representation. 27 | 28 | pprint() 29 | Pretty-print a Python object to a stream [default is sys.stdout]. 30 | 31 | saferepr() 32 | Generate a 'standard' repr()-like value, but protect against recursive 33 | data structures. 34 | 35 | """ 36 | 37 | import sys as _sys 38 | import warnings 39 | import locale 40 | 41 | from cStringIO import StringIO as _StringIO 42 | 43 | __all__ = ["pprint","pformat","isreadable","isrecursive","saferepr", 44 | "PrettyPrinter"] 45 | 46 | # cache these for faster access: 47 | _commajoin = ", ".join 48 | _id = id 49 | _len = len 50 | _type = type 51 | 52 | 53 | def pprint(object, stream=None, indent=1, width=80, depth=None): 54 | """Pretty-print a Python object to a stream [default is sys.stdout].""" 55 | printer = PrettyPrinter( 56 | stream=stream, indent=indent, width=width, depth=depth) 57 | printer.pprint(object) 58 | 59 | def pformat(object, indent=1, width=80, depth=None): 60 | """Format a Python object into a pretty-printed representation.""" 61 | return PrettyPrinter(indent=indent, width=width, depth=depth).pformat(object) 62 | 63 | def saferepr(object): 64 | """Version of repr() which can handle recursive data structures.""" 65 | return _safe_repr(object, {}, None, 0)[0] 66 | 67 | def isreadable(object): 68 | """Determine if saferepr(object) is readable by eval().""" 69 | return _safe_repr(object, {}, None, 0)[1] 70 | 71 | def isrecursive(object): 72 | """Determine if object requires a recursive representation.""" 73 | return _safe_repr(object, {}, None, 0)[2] 74 | 75 | def _sorted(iterable): 76 | with warnings.catch_warnings(): 77 | if _sys.py3kwarning: 78 | warnings.filterwarnings("ignore", "comparing unequal types " 79 | "not supported", DeprecationWarning) 80 | return sorted(iterable) 81 | 82 | class PrettyPrinter: 83 | def __init__(self, indent=1, width=80, depth=None, stream=None): 84 | """Handle pretty printing operations onto a stream using a set of 85 | configured parameters. 86 | 87 | indent 88 | Number of spaces to indent for each level of nesting. 89 | 90 | width 91 | Attempted maximum number of columns in the output. 92 | 93 | depth 94 | The maximum depth to print out nested structures. 95 | 96 | stream 97 | The desired output stream. If omitted (or false), the standard 98 | output stream available at construction will be used. 99 | 100 | """ 101 | indent = int(indent) 102 | width = int(width) 103 | assert indent >= 0, "indent must be >= 0" 104 | assert depth is None or depth > 0, "depth must be > 0" 105 | assert width, "width must be != 0" 106 | self._depth = depth 107 | self._indent_per_level = indent 108 | self._width = width 109 | if stream is not None: 110 | self._stream = stream 111 | else: 112 | self._stream = _sys.stdout 113 | 114 | def pprint(self, object): 115 | self._format(object, self._stream, 0, 0, {}, 0) 116 | self._stream.write("\n") 117 | 118 | def pformat(self, object): 119 | sio = _StringIO() 120 | self._format(object, sio, 0, 0, {}, 0) 121 | return sio.getvalue() 122 | 123 | def isrecursive(self, object): 124 | return self.format(object, {}, 0, 0)[2] 125 | 126 | def isreadable(self, object): 127 | s, readable, recursive = self.format(object, {}, 0, 0) 128 | return readable and not recursive 129 | 130 | def _format(self, object, stream, indent, allowance, context, level): 131 | level = level + 1 132 | objid = _id(object) 133 | if objid in context: 134 | stream.write(_recursion(object)) 135 | self._recursive = True 136 | self._readable = False 137 | return 138 | rep = self._repr(object, context, level - 1) 139 | typ = _type(object) 140 | sepLines = _len(rep) > (self._width - 1 - indent - allowance) 141 | write = stream.write 142 | 143 | if self._depth and level > self._depth: 144 | write(rep) 145 | return 146 | 147 | r = getattr(typ, "__repr__", None) 148 | if issubclass(typ, dict) and r is dict.__repr__: 149 | write('{') 150 | if self._indent_per_level > 1: 151 | write((self._indent_per_level - 1) * ' ') 152 | length = _len(object) 153 | if length: 154 | context[objid] = 1 155 | indent = indent + self._indent_per_level 156 | items = _sorted(object.items()) 157 | key, ent = items[0] 158 | rep = self._repr(key, context, level) 159 | write(rep) 160 | write(': ') 161 | self._format(ent, stream, indent + _len(rep) + 2, 162 | allowance + 1, context, level) 163 | if length > 1: 164 | for key, ent in items[1:]: 165 | rep = self._repr(key, context, level) 166 | if sepLines: 167 | write(',\n%s%s: ' % (' '*indent, rep)) 168 | else: 169 | write(', %s: ' % rep) 170 | self._format(ent, stream, indent + _len(rep) + 2, 171 | allowance + 1, context, level) 172 | indent = indent - self._indent_per_level 173 | del context[objid] 174 | write('}') 175 | return 176 | 177 | if ((issubclass(typ, list) and r is list.__repr__) or 178 | (issubclass(typ, tuple) and r is tuple.__repr__) or 179 | (issubclass(typ, set) and r is set.__repr__) or 180 | (issubclass(typ, frozenset) and r is frozenset.__repr__) 181 | ): 182 | length = _len(object) 183 | if issubclass(typ, list): 184 | write('[') 185 | endchar = ']' 186 | elif issubclass(typ, set): 187 | if not length: 188 | write('set()') 189 | return 190 | write('set([') 191 | endchar = '])' 192 | object = _sorted(object) 193 | indent += 4 194 | elif issubclass(typ, frozenset): 195 | if not length: 196 | write('frozenset()') 197 | return 198 | write('frozenset([') 199 | endchar = '])' 200 | object = _sorted(object) 201 | indent += 10 202 | else: 203 | write('(') 204 | endchar = ')' 205 | if self._indent_per_level > 1 and sepLines: 206 | write((self._indent_per_level - 1) * ' ') 207 | if length: 208 | context[objid] = 1 209 | indent = indent + self._indent_per_level 210 | self._format(object[0], stream, indent, allowance + 1, 211 | context, level) 212 | if length > 1: 213 | for ent in object[1:]: 214 | if sepLines: 215 | write(',\n' + ' '*indent) 216 | else: 217 | write(', ') 218 | self._format(ent, stream, indent, 219 | allowance + 1, context, level) 220 | indent = indent - self._indent_per_level 221 | del context[objid] 222 | if issubclass(typ, tuple) and length == 1: 223 | write(',') 224 | write(endchar) 225 | return 226 | 227 | write(rep) 228 | 229 | def _repr(self, object, context, level): 230 | repr, readable, recursive = self.format(object, context.copy(), 231 | self._depth, level) 232 | if not readable: 233 | self._readable = False 234 | if recursive: 235 | self._recursive = True 236 | return repr 237 | 238 | def format(self, object, context, maxlevels, level): 239 | """Format object for a specific context, returning a string 240 | and flags indicating whether the representation is 'readable' 241 | and whether the object represents a recursive construct. 242 | """ 243 | return _safe_repr(object, context, maxlevels, level) 244 | 245 | 246 | # Return triple (repr_string, isreadable, isrecursive). 247 | 248 | def _safe_repr(object, context, maxlevels, level): 249 | typ = _type(object) 250 | if typ is str: 251 | string = object 252 | string = string.replace('\n', '\\n').replace('\r','\\r').replace('\t','\\t') 253 | if 'locale' not in _sys.modules: 254 | return repr(object), True, False 255 | if "'" in object and '"' not in object: 256 | closure = '"' 257 | quotes = {'"': '\\"'} 258 | string = string.replace('"','\\"') 259 | else: 260 | closure = "'" 261 | quotes = {"'": "\\'"} 262 | string = string.replace("'", "\\'") 263 | try: 264 | string.decode('utf8').encode('gbk') 265 | return ("%s%s%s" % (closure, string, closure)), True, False 266 | except: 267 | pass 268 | qget = quotes.get 269 | sio = _StringIO() 270 | write = sio.write 271 | for char in object: 272 | if char.isalpha(): 273 | write(char) 274 | else: 275 | write(qget(char, repr(char)[1:-1])) 276 | return ("%s%s%s" % (closure, sio.getvalue(), closure)), True, False 277 | 278 | if typ is unicode: 279 | string = object.encode("utf8") 280 | string = string.replace('\n', '\\n').replace('\r','\\r').replace('\t','\\t') 281 | if "'" in object and '"' not in object: 282 | closure = '"' 283 | quotes = {'"': '\\"'} 284 | string = string.replace('"','\\"') 285 | else: 286 | closure = "'" 287 | quotes = {"'": "\\'"} 288 | string = string.replace("'", "\\'") 289 | return ("u%s%s%s" % (closure, string, closure)), True, False 290 | 291 | r = getattr(typ, "__repr__", None) 292 | if issubclass(typ, dict) and r is dict.__repr__: 293 | if not object: 294 | return "{}", True, False 295 | objid = _id(object) 296 | if maxlevels and level >= maxlevels: 297 | return "{...}", False, objid in context 298 | if objid in context: 299 | return _recursion(object), False, True 300 | context[objid] = 1 301 | readable = True 302 | recursive = False 303 | components = [] 304 | append = components.append 305 | level += 1 306 | saferepr = _safe_repr 307 | for k, v in _sorted(object.items()): 308 | krepr, kreadable, krecur = saferepr(k, context, maxlevels, level) 309 | vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level) 310 | append("%s: %s" % (krepr, vrepr)) 311 | readable = readable and kreadable and vreadable 312 | if krecur or vrecur: 313 | recursive = True 314 | del context[objid] 315 | return "{%s}" % _commajoin(components), readable, recursive 316 | 317 | if (issubclass(typ, list) and r is list.__repr__) or \ 318 | (issubclass(typ, tuple) and r is tuple.__repr__): 319 | if issubclass(typ, list): 320 | if not object: 321 | return "[]", True, False 322 | format = "[%s]" 323 | elif _len(object) == 1: 324 | format = "(%s,)" 325 | else: 326 | if not object: 327 | return "()", True, False 328 | format = "(%s)" 329 | objid = _id(object) 330 | if maxlevels and level >= maxlevels: 331 | return format % "...", False, objid in context 332 | if objid in context: 333 | return _recursion(object), False, True 334 | context[objid] = 1 335 | readable = True 336 | recursive = False 337 | components = [] 338 | append = components.append 339 | level += 1 340 | for o in object: 341 | orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level) 342 | append(orepr) 343 | if not oreadable: 344 | readable = False 345 | if orecur: 346 | recursive = True 347 | del context[objid] 348 | return format % _commajoin(components), readable, recursive 349 | 350 | rep = repr(object) 351 | return rep, (rep and not rep.startswith('<')), False 352 | 353 | 354 | def _recursion(object): 355 | return ("" 356 | % (_type(object).__name__, _id(object))) 357 | 358 | 359 | def _perfcheck(object=None): 360 | import time 361 | if object is None: 362 | object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000 363 | p = PrettyPrinter() 364 | t1 = time.time() 365 | _safe_repr(object, {}, None, 0) 366 | t2 = time.time() 367 | p.pformat(object) 368 | t3 = time.time() 369 | print "_safe_repr:", t2 - t1 370 | print "pformat:", t3 - t2 371 | 372 | if __name__ == "__main__": 373 | _perfcheck() 374 | -------------------------------------------------------------------------------- /lib/scanner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # site : www.beebeeto.com 4 | # team : n0tr00t security 5 | 6 | import os 7 | import sys 8 | sys.path.append('../') 9 | import lib 10 | import time 11 | import socket 12 | import random 13 | import string 14 | import platform 15 | 16 | from lib.poc import Poc 17 | 18 | try: 19 | from lib import beecoroutine as bc, beethread as bt 20 | except: 21 | from lib import beethread as bt 22 | 23 | 24 | socket.setdefaulttimeout(10) 25 | 26 | 27 | def TestPlatform(): 28 | plat = platform.platform() 29 | if 'windows' in str(plat).lower(): 30 | platstr = 'beethread' 31 | else: 32 | platstr = 'beecoroutine' 33 | return platstr 34 | 35 | class Storm(object): 36 | '''scanner scanning a particular target with a list of pocs.''' 37 | def __init__(self, target, listPocPaths, poolModule='beethread', 38 | concurrency=20, verify=True): 39 | ''' 40 | target - a target to scan. 41 | listPocPaths - a list containing man pocs' paths, the pocs must be 42 | batchable. 43 | poolModule - can be 'beethread' or 'beecoroutine', meaning using 44 | thread pool or coroutine pool to manage concurrency. 45 | concurrency - how many work to run in concurrency. 46 | verify - run the pocs' verfiy mode or exploit mode. 47 | ''' 48 | self.target = target 49 | self.verify = verify 50 | self._pocObjs = self._loadPocs(listPocPaths) 51 | self._poolModule = poolModule 52 | self._workerPool = self._initWorkerPool(poolModule)(concurrency=concurrency) 53 | self.r_num = random.randint(10000, 99999) 54 | 55 | def _view_bar(self, num=1, sum=0, bar_word=':'): 56 | rate = float(num) / float(sum) 57 | rate_num = int(rate * 100) 58 | str_lsit = ['-', '|', '/', '\\'] 59 | print '\r[%s] Scanning... (%d%%)' % (random.choice(str_lsit), rate_num), 60 | sys.stdout.flush() 61 | 62 | def _loadPocs(self, listPocPaths): 63 | pocObjs = [] 64 | for pocPath in listPocPaths: 65 | pocObjs.append(Poc(path=pocPath)) 66 | return pocObjs 67 | 68 | def _initWorkerPool(self, poolModule='beethread'): 69 | if poolModule is 'beethread': 70 | return bt.WorkerPool 71 | elif poolModule is 'beecoroutine': 72 | return bc.WorkerPool 73 | 74 | def _runPoc(self, pocName, pocObj, verbose=True): 75 | if verbose: 76 | tmp_schedule = open('./tmp/t_%d.txt' % self.r_num, 'a+') 77 | tmp_schedule.write(pocName+'\n') 78 | tmp_count = len(open('./tmp/t_%d.txt' % self.r_num ,'rU').readlines()) 79 | self._view_bar(num=tmp_count, sum=len(self._pocObjs)) 80 | tmp_schedule.close() 81 | return pocObj.run(target=self.target, verify=self.verify) 82 | 83 | def scan(self, timeout=None, verbose=True): 84 | print 85 | self._workerPool.work(iterJobFuncArgs=[[pocObj.name, pocObj, True] \ 86 | for pocObj in self._pocObjs], 87 | jobFunc=self._runPoc, 88 | timeout=timeout) 89 | try: 90 | os.remove('./tmp/t_%d.txt' % self.r_num) 91 | except Exception, err: 92 | pass 93 | return self._workerPool.results 94 | 95 | 96 | class Hunter(object): 97 | '''scanner scanning a list of targets with a particular poc.''' 98 | def __init__(self, iterTarget, pocPath, poolModule='beethread', 99 | concurrency=20, verify=True): 100 | self.iterTarget = iterTarget 101 | self.verify = verify 102 | self._pocObj = Poc(path=pocPath) 103 | self._poolModule = poolModule 104 | self._workerPool = self._initWorkerPool(poolModule)(concurrency=concurrency) 105 | 106 | def _initWorkerPool(self, poolModule='beethread'): 107 | if poolModule is 'beethread': 108 | return bt.WorkerPool 109 | elif poolModule is 'beecoroutine': 110 | return bc.WorkerPool 111 | 112 | def _runPoc(self, target): 113 | # TODO: need to normalize target in Poc 114 | target = target.strip() 115 | return self._pocObj.run(target=target, verify=self.verify) 116 | 117 | def scan(self, timeout=None): 118 | self._workerPool.work(iterJobFuncArgs=self.iterTarget, 119 | jobFunc=self._runPoc, 120 | timeout=timeout) 121 | return self._workerPool.results 122 | 123 | 124 | 125 | if __name__ == '__main__': 126 | from pprint import pprint 127 | 128 | # testing Storm 129 | ''' 130 | pocPaths = [ 131 | './pocs/poc_2014_0014.py', 132 | './pocs/poc_2014_0010.py', 133 | ] 134 | s = Storm(listPocPaths=pocPaths, target='baidu.com', poolModule='beecoroutine') 135 | pprint(s.scan()) 136 | ''' 137 | 138 | # testing Hunter 139 | ''' 140 | targets = ['baidu.com', 141 | 'https://8.8.8.8'] 142 | h = Hunter(iterTarget=targets, 143 | pocPath='../pocs/poc_2014_0011.py', 144 | poolModule='beethread') 145 | pprint(h.scan()) 146 | ''' 147 | -------------------------------------------------------------------------------- /menu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # site : www.beebeeto.com 4 | # team : n0tr00t security 5 | 6 | import os 7 | import sys 8 | import optparse 9 | import pyparsing 10 | import pprint as pr 11 | import textwrap as tw 12 | import traceback as tb 13 | 14 | from lib.scanner import Storm, Hunter, TestPlatform 15 | from prettytable import PrettyTable 16 | from lib.io import bprint, bprintPrefix 17 | from lib import db, poc, cmd2, banners, pprint2 18 | from SETTINGS import BEESCAN_DIR, POC_DIR, VERSION 19 | 20 | 21 | # decorater to record last command return value 22 | def recordCmdRet(valName): 23 | def _recordCmdRet(func): 24 | def __recordCmdRet(self, *args, **kwargs): 25 | ret = func(self, *args, **kwargs) 26 | setattr(self, valName, ret) 27 | return 28 | return __recordCmdRet 29 | return _recordCmdRet 30 | 31 | 32 | 33 | class BaseMenu(cmd2.Cmd): 34 | default_to_shell = True 35 | timing = False 36 | nonWhiteMsg = 'Please enter a non white space string.' 37 | 38 | def __init__(self): 39 | super(BaseMenu, self).__init__() 40 | self.database = db.Database(dbFilePath='./hive.db') 41 | self.retLastSearch = None # the result of last search 42 | 43 | def postloop(self): 44 | self.database.dbConn.close() 45 | 46 | def postcmd(self, stop, line): 47 | return stop 48 | 49 | def do_status(self, arg): 50 | '''print status information.''' 51 | bprintPrefix('BeeHive Version: %s' % VERSION, msgType='ok') 52 | bprintPrefix('Exploits & PoCs: %d\n' % self.database.countAll()[0], 'ok') 53 | 54 | @recordCmdRet(valName='retLastSearch') 55 | def do_search(self, arg): 56 | # the func doc below cannot be automatically used as help doc 57 | # because this func is wrapped by a decorator. 58 | if not arg.strip(): 59 | bprintPrefix(self.nonWhiteMsg, 'warning') 60 | return 61 | try: 62 | results = self.database.searchStr(arg.strip()) 63 | except Exception, err: 64 | print '[-] ', 65 | print err 66 | return 67 | res_tb = PrettyTable(['Name', 'Datetime', 'Level', 68 | 'Author', 'Batch', 'Pid',]) 69 | res_tb.align['Name'] = 'l' 70 | for r in results: 71 | res_tb.add_row(((r[1][:35]+'...').strip(), 72 | r[5][:10],r[3],r[4][:10], 73 | r[-2],r[0][4:])) 74 | print res_tb.get_string(sortby='Pid', reversesort=False) 75 | return results 76 | 77 | def help_search(self): 78 | print '''[*] Search for pocs/exps.''' 79 | 80 | def do_run(self, arg): 81 | bprintPrefix("Can't run this command under the root menu.", 'error') 82 | return 83 | 84 | def help_run(self): 85 | print '''[-] UNKOWN''' 86 | 87 | @cmd2.options([cmd2.make_option('-m', '--mode', action='store', help='Update database. (json / pocs)'),]) 88 | def do_updatedb(self, arg, opts=None): 89 | '''''' 90 | if opts.mode == 'pocs': 91 | try: 92 | num_insert, num_all, num_err, err_list = self.database.updtDbFromPocs(pocDir=POC_DIR) 93 | print '[*] Scan local mode\n%s\nTotal: %s' % ('--'*10, num_all) 94 | except Exception, err: 95 | bprintPrefix(err, 'error') 96 | elif opts.mode == 'json': 97 | try: 98 | num_insert, num_all, num_err, err_list = self.database.updtDbFromJson('./pocdb.json') 99 | print '[*] JSON import mode\n%s\nTotal: %s' % ('--'*10, num_all) 100 | except Exception, err: 101 | bprintPrefix(err, 'error') 102 | else: 103 | bprintPrefix('WTF!?', 'warning') 104 | return 105 | bprint('Insert number: %s' % num_insert, 'ok') 106 | bprint('Error number: %s' % num_err, 'error') 107 | for i in err_list: 108 | print ' %s' % i 109 | 110 | def do_showloaded(self, arg): 111 | '''[*] Show current loaded poc(s)''' 112 | if hasattr(self, 'loadedPocs') and self.loadedPocs: 113 | if isinstance(self, (ShooterMenu, HunterMenu)): 114 | bprintPrefix('loaded poc: %s' % \ 115 | self.loadedPocs.poc_info.get('poc').get('id'), 'ok') 116 | elif isinstance(self, StormMenu): 117 | bprintPrefix('loaded pocs: ', 'ok') 118 | for pocPath in self.loadedPocs: 119 | print ' %s' % os.path.basename(pocPath) 120 | else: 121 | bprint('[-] no poc has been loaded.', 'error') 122 | 123 | def do_lastret(self, arg): 124 | '''[*] Show the result of last scan.''' 125 | if hasattr(self, 'retLastScan') and self.retLastScan: 126 | try: 127 | print 128 | res_tb, ret = self.retLastScan 129 | print res_tb.get_string(sortby='Status', reversesort=False) 130 | print 131 | except Exception, e: 132 | bprintPrefix('%s\n'%str(e), 'warning') 133 | else: 134 | bprintPrefix('No scan result.\n', 'warning') 135 | 136 | def do_export(self, arg): 137 | '''[*] Save the result as a file.''' 138 | if not arg.strip(): 139 | bprintPrefix(self.nonWhiteMsg, 'error') 140 | return 141 | if hasattr(self, 'retLastScan') and self.retLastScan: 142 | try: 143 | res_tb, ret = self.retLastScan 144 | _output = open(arg.strip(), 'a+') 145 | _output.write(str(ret)) 146 | _output.close() 147 | bprintPrefix('Write file success: %s' % arg.strip(), 'ok') 148 | except Exception, e: 149 | bprintPrefix('%s\n'%str(e), 'warning') 150 | else: 151 | bprintPrefix('No scan result.\n', 'warning') 152 | 153 | def do_info(self, arg): 154 | '''[*] View code information and usage.''' 155 | if not arg.strip(): 156 | bprintPrefix(self.nonWhiteMsg, 'error') 157 | return 158 | if not arg.strip().startswith('poc'): 159 | pocName = 'poc-' + arg.strip() 160 | if pocName.strip()[8] != '-': 161 | pocName = 'poc-' + pocName[-8:-4] + '-' + pocName[-4:] 162 | else: 163 | pocName = arg.strip() 164 | pocInfo = self.database.searchPoc( 165 | pocId=pocName.strip().replace('_', '-')) 166 | if pocInfo is None: 167 | bprintPrefix('Cannot find poc %s in database.' % arg, 'error') 168 | return 169 | pocId, name, rank, level, author, createDate, protocol, port, \ 170 | layer4Protocol, appName, vulType, desc, tag, batchable, \ 171 | path = pocInfo 172 | if not path or not os.path.exists(path): 173 | bprintPrefix('Poc file %s not exists, perhaps you have\'t bought '\ 174 | 'it.\n' % path, 'error') 175 | return 176 | try: 177 | p = poc.Poc(path=os.path.join(POC_DIR, '%s.py' % \ 178 | pocName.strip().replace('-', '_')), 179 | batchable=batchable) 180 | mp = p.module.MyPoc(run_in_shell=False) 181 | mp._init_parser(do_parse=False) 182 | bprint('%s information:' % path, 'ok') 183 | pprint2.pprint(mp.poc_info) 184 | print 185 | bprint('%s help:' % path, 'ok') 186 | mp.base_parser.print_help() 187 | #return mp 188 | except Exception, err: 189 | bprintPrefix(err, 'error') 190 | 191 | @staticmethod 192 | def extRunPocOpt(option_list, arg_desc="arg"): 193 | ''' 194 | Used as a decorator and passed a list of optparse-style options, 195 | alters a cmd2 method to populate its ``opts`` argument from its 196 | raw text argument. 197 | 198 | Example: transform 199 | def do_something(self, arg): 200 | 201 | into 202 | @extPocOpt([make_option('-q', '--quick', action="store_true", 203 | help="Makes things fast")], 204 | "source dest") 205 | def do_something(self, arg, opts): 206 | if opts.quick: 207 | self.fast_button = True 208 | ''' 209 | 210 | if not isinstance(option_list, list): 211 | option_list = [option_list] 212 | for opt in option_list: 213 | cmd2.options_defined.append( 214 | pyparsing.Literal(opt.get_opt_string()) 215 | ) 216 | 217 | def option_setup(func): 218 | optionParser = cmd2.OptionParser() 219 | for opt in option_list: 220 | optionParser.add_option(opt) 221 | optionParser.set_usage("%s [options] %s" % (func.__name__[3:], arg_desc)) 222 | optionParser._func = func 223 | def new_func(instance, arg): 224 | try: 225 | for opt in instance.loadedPocs.base_parser._get_all_options(): 226 | try: 227 | optionParser._check_conflict(opt) 228 | optionParser.add_option(opt) 229 | except optparse.OptionConflictError as e: 230 | pass 231 | instance.runParser = optionParser 232 | opts, newArgList = optionParser.parse_args(arg.split()) 233 | # Must find the remaining args in the original argument list, but 234 | # mustn't include the command itself 235 | #if hasattr(arg, 'parsed') and newArgList[0] == arg.parsed.command: 236 | # newArgList = newArgList[1:] 237 | newArgs = cmd2.remaining_args(arg, newArgList) 238 | if isinstance(arg, cmd2.ParsedString): 239 | arg = arg.with_args_replaced(newArgs) 240 | else: 241 | arg = newArgs 242 | except optparse.OptParseError as e: 243 | print (e) 244 | optionParser.print_help() 245 | return 246 | except AttributeError as e: 247 | bprintPrefix('Please load a poc first.', 'warning') 248 | return 249 | if hasattr(opts, '_exit'): 250 | return None 251 | result = func(instance, arg, opts) 252 | return result 253 | new_func.__doc__ = '%s\n%s' % (func.__doc__, optionParser.format_help()) 254 | return new_func 255 | return option_setup 256 | 257 | 258 | 259 | class MainMenu(BaseMenu): 260 | prompt = 'beehive > ' 261 | 262 | def preloop(self): 263 | num_count = str(self.database.countAll()[0]) 264 | print banners.getBanner() 265 | bprint('%sn0tr00t security team\n' % (' '*20), 'warning') 266 | sys.stdout.write('Beehive Version: ') 267 | bprint(VERSION, 'ok') 268 | sys.stdout.write('Exploits & Pocs: ') 269 | bprint(num_count, 'ok') 270 | sys.stdout.write('Contact: ') 271 | bprint('root@beebeeto.com', 'ok') 272 | sys.stdout.write('Forum: ') 273 | bprint(' http://buzz.beebeeto.com', 'ok') 274 | print 275 | 276 | def do_shooter(self, arg): 277 | '''[*] Go to the shooter(1 poc --> 1 target) sub menu.''' 278 | sm = ShooterMenu() 279 | sm.cmdloop() 280 | 281 | def do_storm(self, arg): 282 | '''[*] Go to the storm(N poc --> 1 target) sub menu.''' 283 | sm = StormMenu() 284 | sm.cmdloop() 285 | 286 | def do_hunter(self, arg): 287 | '''[*] Go to the hunter(1 poc --> N targets) sub menu.''' 288 | hm = HunterMenu() 289 | hm.cmdloop() 290 | 291 | 292 | 293 | class StormMenu(BaseMenu): 294 | prompt = 'beehive.storm > ' 295 | 296 | @recordCmdRet(valName='loadedPocs') 297 | def do_loadsearched(self, arg): 298 | if not self.retLastSearch: 299 | bprint('[-] please make a search first.', 'error') 300 | return 301 | batchablePocPaths = [] 302 | unbatchablePocPaths = [] 303 | for pocInfo in self.retLastSearch: 304 | pocId, name, rank, level, author, createDate, protocol, port, \ 305 | layer4Protocol, appName, vulType, desc, tag, batchable, \ 306 | path = pocInfo 307 | if batchable: 308 | batchablePocPaths.append(path) 309 | else: 310 | unbatchablePocPaths.append(path) 311 | if unbatchablePocPaths: 312 | bprintPrefix('These pocs in last search results are not batchable:', 'warning') 313 | bprintPrefix('They cannot be loaded in Storm mode, please load them '\ 314 | 'singlely in the Shooter mode.', 'warning') 315 | for pocPath in unbatchablePocPaths: 316 | print ' %s' % os.path.basename(pocPath) 317 | if unbatchablePocPaths and batchablePocPaths: 318 | print 319 | if batchablePocPaths: 320 | bprintPrefix('These pocs in last search results are batchable:', 'ok') 321 | bprintPrefix('They are going to be used to load Storm mode scan.', 'ok') 322 | for pocPath in batchablePocPaths: 323 | print ' %s' % os.path.basename(pocPath) 324 | return batchablePocPaths 325 | else: 326 | bprintPrefix('None of the poc in last search result is batchable.', 'warning') 327 | return None 328 | 329 | def help_loadsearched(self): 330 | bprintPrefix('load last searched result(s) to test a target.', 'info') 331 | 332 | @recordCmdRet(valName='retLastScan') 333 | def do_run(self, arg): 334 | if not hasattr(self, 'loadedPocs') or not self.loadedPocs: 335 | bprintPrefix('Please load a poc first.', 'warning') 336 | return 337 | if not arg.strip(): 338 | bprintPrefix('Please enter the target.', 'error') 339 | return 340 | s = Storm(target=arg, 341 | listPocPaths=self.loadedPocs, 342 | poolModule=TestPlatform(), 343 | concurrency=20, verify=True) 344 | ret = s.scan() 345 | JOB_UNSTART = 0 # poc not run 346 | JOB_RUNNING = 1 347 | JOB_FINISHED = 2 # poc run ok 348 | JOB_ERROR = -1 # error encountered when run poc 349 | JOB_ABORT = -2 # running poc is abort, viz unfinished 350 | print 351 | bprintPrefix('Scan end, Results:\n', 'ok') 352 | res_tb = PrettyTable(['Vulnerability', 'Pid', 'Status', 'Result',]) 353 | res_tb.align['Vulnerability'] = 'l' 354 | for r in ret.values(): 355 | pid = r['args'][0].replace('_', '-') 356 | poc_info = self.database.searchPoc(pid) 357 | state = r['state'] 358 | if state == JOB_FINISHED: 359 | status = str(r['jobRet']['success']) 360 | result = str(r['jobRet']['poc_ret']) 361 | if status == 'None': 362 | status = 'False' 363 | result = 'N/A' 364 | elif status == 'False': 365 | result = 'Not Vulnerable' 366 | elif state == JOB_ERROR: 367 | status = 'Error' 368 | result = r['exception'] 369 | else: 370 | status = 'Error' 371 | res_tb.add_row([poc_info[1][:25]+'...', pid, 372 | status, result[:25]]) 373 | print res_tb.get_string(sortby='Status', reversesort=False) 374 | print 375 | return res_tb, ret 376 | 377 | def help_run(self): 378 | bprintPrefix('Run loaded poc(s)', 'info') 379 | 380 | @recordCmdRet(valName='loadedPocs') 381 | def do_loadall(self, arg): 382 | try: 383 | batchablePocs = self.database.getBatchable() 384 | pocPaths = [] 385 | [pocPaths.append(i[-1]) for i in batchablePocs] 386 | bprintPrefix('%d batchable pocs (%d total pocs) loaded.' % ( 387 | len(pocPaths), 388 | self.database.countAll()[0], 389 | ), 'ok') 390 | return pocPaths 391 | except Exception, err: 392 | print '[-] ', 393 | print err 394 | return 395 | 396 | def help_loadall(self): 397 | bprintPrefix('Load all poc to storm a target.', 'info') 398 | 399 | 400 | 401 | class ShooterMenu(BaseMenu): 402 | prompt = 'beehive.shooter > ' 403 | 404 | @recordCmdRet(valName='loadedPocs') 405 | def do_loadpoc(self, arg): 406 | if not arg.strip().startswith('poc'): 407 | pocName = 'poc-' + arg.strip() 408 | if pocName.strip()[8] != '-': 409 | pocName = 'poc-' + pocName[-8:-4] + '-' + pocName[-4:] 410 | else: 411 | pocName = arg.strip() 412 | pocInfo = self.database.searchPoc( 413 | pocId=pocName.strip().replace('_', '-')) 414 | if pocInfo is None: 415 | bprintPrefix('Cannot find poc %s in database.' % arg, 'error') 416 | return 417 | pocId, name, rank, level, author, createDate, protocol, port, \ 418 | layer4Protocol, appName, vulType, desc, tag, batchable, \ 419 | path = pocInfo 420 | if not path or not os.path.exists(path): 421 | bprintPrefix('Poc file %s not exists, perhaps you have\'t bought '\ 422 | 'it.\n' % path, 'error') 423 | return 424 | try: 425 | p = poc.Poc(path=os.path.join(POC_DIR, '%s.py' % \ 426 | pocName.strip().replace('-', '_')), 427 | batchable=batchable) 428 | mp = p.module.MyPoc(run_in_shell=False) 429 | mp._init_parser(do_parse=False) 430 | bprintPrefix('load %s success!' % path, 'ok') 431 | return mp 432 | except Exception, err: 433 | bprintPrefix(err, 'error') 434 | 435 | def help_loadpoc(self): 436 | bprintPrefix('Load a poc to test a target.', 'info') 437 | 438 | @recordCmdRet(valName='retLastScan') 439 | @BaseMenu.extRunPocOpt([ 440 | cmd2.make_option('-d', '--debug', action="store_true", help="debug mode",) 441 | ]) 442 | def do_run(self, arg, opts=None): 443 | if not hasattr(self, 'loadedPocs') or not self.loadedPocs: 444 | bprintPrefix('Please load a poc first.', 'warning') 445 | return 446 | if not opts.target: 447 | bprintPrefix('No target input!\n', 'warning') 448 | self.runParser.print_help() 449 | return 450 | print 451 | ret = self.loadedPocs.run(options=opts.__dict__, debug=opts.debug) 452 | bprintPrefix('%s:\n' % self.loadedPocs.poc_info['poc']['id'], 'info') 453 | # results view 454 | if ret['options']: 455 | print '%starget: %s' % (' '*4, ret['options']['target']) 456 | try: 457 | if ret['exception']: 458 | print '%sexception: %s' % (' '*4, ret['exception']) 459 | except Exception, err: 460 | pass 461 | if ret['success'] == True: 462 | print ' '*4, 463 | bprintPrefix('success: %s' % ret['success'], 'ok') 464 | print ' '*3, 465 | bprintPrefix('poc_ret: %s' % ret['poc_ret'], 'ok') 466 | else: 467 | print '%ssuccess: %s' % (' '*4, ret['success']) 468 | print 469 | return ret 470 | 471 | def help_run(self): 472 | bprintPrefix('Run poc to shoot a target.', 'info') 473 | 474 | 475 | 476 | class HunterMenu(BaseMenu): 477 | prompt = 'beehive.hunter > ' 478 | 479 | @recordCmdRet(valName='loadedPocs') 480 | def do_loadpoc(self, arg): 481 | if not arg.strip().startswith('poc'): 482 | pocName = 'poc-' + arg.strip() 483 | if pocName.strip()[8] != '-': 484 | pocName = 'poc-' + pocName[-8:-4] + '-' + pocName[-4:] 485 | else: 486 | pocName = arg.strip() 487 | pocInfo = self.database.searchPoc( 488 | pocId=pocName.strip().replace('_', '-')) 489 | if pocInfo is None: 490 | bprintPrefix('Cannot find poc %s in database.' % arg, 'error') 491 | return 492 | pocId, name, rank, level, author, createDate, protocol, port, \ 493 | layer4Protocol, appName, vulType, desc, tag, batchable, \ 494 | path = pocInfo 495 | if not path or not os.path.exists(path): 496 | bprintPrefix('Poc file %s not exists, perhaps you have\'t bought '\ 497 | 'it.\n' % path, 'error') 498 | return 499 | try: 500 | p = poc.Poc(path=os.path.join(POC_DIR, '%s.py' % \ 501 | pocName.strip().replace('-', '_')), 502 | batchable=batchable) 503 | mp = p.module.MyPoc(run_in_shell=False) 504 | mp._init_parser(do_parse=False) 505 | bprintPrefix('load %s success!' % path, 'ok') 506 | return mp 507 | except Exception, err: 508 | bprintPrefix(err, 'error') 509 | 510 | def help_loadpoc(self): 511 | bprintPrefix('Load a poc to test a target.', 'info') 512 | 513 | @recordCmdRet(valName='retLastScan') 514 | @BaseMenu.extRunPocOpt([ 515 | cmd2.make_option('-f', '--file', action="store", help="debug mode"), 516 | ]) 517 | def do_run(self, arg, opts=None): 518 | if not hasattr(self, 'loadedPocs') or not self.loadedPocs: 519 | bprintPrefix('Please load a poc first.', 'warning') 520 | return 521 | file_alert = 'Need to load a targets file. (domains)' 522 | if not opts.file: 523 | bprintPrefix(file_alert, 'warning') 524 | return 525 | if opts.file: 526 | filename = opts.file 527 | if filename[0] == "'": 528 | filename = filename.strip("'") 529 | elif filename[0] == '"': 530 | filename = filename.strip('"') 531 | try: 532 | f_req = open(filename, 'r') 533 | if os.stat(filename).st_size == 0: 534 | bprintPrefix('File content is empty?', 'warning') 535 | return 536 | except Exception, err: 537 | bprintPrefix(str(err), 'error') 538 | return 539 | 540 | # scan main 541 | pocid = self.loadedPocs.poc_info.get('poc').get('id') 542 | (options, args) = self.loadedPocs.base_parser.parse_args( 543 | arg.strip().split()) 544 | h = Hunter(iterTarget=f_req, 545 | pocPath=('./pocs/%s.py' % pocid.replace('-', '_')), 546 | poolModule=TestPlatform()) 547 | ret = h.scan() 548 | 549 | # view table 550 | JOB_UNSTART = 0 # poc not run 551 | JOB_RUNNING = 1 552 | JOB_FINISHED = 2 # poc run ok 553 | JOB_ERROR = -1 # error encountered when run poc 554 | JOB_ABORT = -2 # running poc is abort, viz unfinished 555 | print 556 | res_tb = PrettyTable(['Target', 'Status', 'Result',]) 557 | res_tb.align['Target'] = 'l' 558 | try: 559 | for r in ret.values(): 560 | target = r['args'] 561 | pid = r['args'][0].replace('_', '-') 562 | poc_info = self.database.searchPoc(pid) 563 | state = r['state'] 564 | if state == JOB_FINISHED: 565 | status = str(r['jobRet']['success']) 566 | result = str(r['jobRet']['poc_ret']) 567 | if status == 'None': 568 | status = 'False' 569 | result = 'N/A' 570 | elif status == 'False': 571 | result = 'Not Vulnerable' 572 | elif state == JOB_ERROR: 573 | status = 'Error' 574 | result = r['exception'] 575 | else: 576 | status = 'Error' 577 | res_tb.add_row([target, status, result[:25]]) 578 | except Exception, err: 579 | import traceback 580 | traceback.print_exc() 581 | print res_tb.get_string(sortby='Status', reversesort=False) 582 | print 583 | return res_tb, ret 584 | 585 | def help_run(self): 586 | bprintPrefix('Run loaded poc(s)', 'info') 587 | 588 | 589 | if __name__ == '__main__': 590 | mm = MainMenu() 591 | mm.cmdloop() 592 | -------------------------------------------------------------------------------- /pocs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n0tr00t/Beehive/24227d00f2db5299ffd426bc662deac8469badf3/pocs/__init__.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | gevent 2 | colorama 3 | pyparsing 4 | threadpool 5 | pyreadline 6 | prettytable 7 | requests 8 | redis 9 | paramiko 10 | pymongo 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # site : www.beebeeto.com 4 | # team : n0tr00t security 5 | 6 | import os 7 | import sys 8 | import platform 9 | try: 10 | from lib.io import bprintPrefix 11 | except ImportError as err: 12 | def bprintPrefix(msg, key): 13 | print("%s %s" % (msg, key)) 14 | 15 | def TestPlatform(): 16 | plat = platform.platform() 17 | platstr = 'other' 18 | if 'windows' in str(plat).lower(): 19 | platstr = 'windows' 20 | return plat, platstr 21 | 22 | def Framework_check(): 23 | plat, platstr = TestPlatform() 24 | if platstr == 'windows': 25 | framework_path = '%s\\Beebeeto-framework\\' % ('\\'.join(os.getcwd().split('\\')[:-1])) 26 | else: 27 | framework_path = '%s/Beebeeto-framework/' % ('/'.join(os.getcwd().split('/')[:-1])) 28 | if os.path.exists(framework_path) == True: 29 | return True 30 | else: 31 | return False 32 | 33 | def Requirements(): 34 | requirements = [] 35 | [requirements.append(r.strip()) for r in open('requirements.txt', 'r').readlines()] 36 | return requirements 37 | 38 | def Install(name, pip_proxy=False): 39 | print 40 | pip_proxy_address = 'http://mirrors.aliyun.com/pypi/simple/' 41 | pip_proxy_host = 'mirrors.aliyun.com' 42 | bprintPrefix('%s installing...' % name, 'ok') 43 | if pip_proxy == True: 44 | os.system('pip install %s -i %s --trusted-host %s' % (name, pip_proxy_address, pip_proxy_host)) 45 | else: 46 | os.system('pip install %s' % name) 47 | 48 | def Main(pip_proxy): 49 | try: 50 | bprintPrefix('pip version: %s' % (os.popen('pip --version').readlines()[0].strip()), 'info') 51 | except Exception as err: 52 | bprintPrefix(str(err), 'error') 53 | sys.exit() 54 | requirements = Requirements() 55 | plat, platstr = TestPlatform() 56 | if platstr == 'windows': 57 | requirements.remove('gevent') 58 | else: 59 | requirements.append('readline') 60 | bprintPrefix('Platform: %s' % plat, 'info') 61 | if Framework_check() == True: 62 | bprintPrefix('Beebeeto-framework check: ok', 'ok') 63 | else: 64 | bprintPrefix('Beebeeto-framework check: false', 'error') 65 | bprintPrefix('Installing...', 'info') 66 | if platstr == 'windows': 67 | os.system('cd .. & git clone https://github.com/n0tr00t/Beebeeto-framework') 68 | else: 69 | os.system('cd ../ ; git clone https://github.com/n0tr00t/Beebeeto-framework') 70 | [Install(r, pip_proxy) for r in requirements] 71 | bprintPrefix('Finish :)', 'ok') 72 | 73 | if __name__ == '__main__': 74 | pip_proxy= False 75 | if len(sys.argv) > 1 and sys.argv[1] == 'proxy': 76 | pip_proxy = True 77 | Main(pip_proxy) 78 | -------------------------------------------------------------------------------- /tmp/tmp.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n0tr00t/Beehive/24227d00f2db5299ffd426bc662deac8469badf3/tmp/tmp.txt --------------------------------------------------------------------------------