├── Dockerfile
├── LICENSE
├── README.md
├── man_spider
├── __init__.py
├── lib
│ ├── __init__.py
│ ├── errors.py
│ ├── file.py
│ ├── logger.py
│ ├── parser
│ │ ├── __init__.py
│ │ └── parser.py
│ ├── processpool.py
│ ├── smb.py
│ ├── spider.py
│ ├── spiderling.py
│ └── util.py
└── manspider.py
├── manspider.sh
├── poetry.lock
├── pyproject.toml
└── requirements.txt
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.10
2 |
3 | RUN apt-get update && apt-get install -y krb5-user tesseract-ocr antiword
4 |
5 | COPY . /manspider
6 | WORKDIR /manspider
7 |
8 | RUN pip install .
9 |
10 | ENTRYPOINT ["manspider"]
--------------------------------------------------------------------------------
/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 | An SMB share crawler written in python
635 | Copyright (C) 2018 BLS OPS LLC
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 | MANSPIDER Copyright (C) 2018 BLS OPS LLC
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MANSPIDER
2 | ### Crawl SMB shares for juicy information. File content searching + regex is supported!
3 |
4 | ### UPDATE 2025-05-26
5 |
6 | **Manspider has been updated to support kerberos!** Also, the textract library has been replaced with [Extractous](https://github.com/yobix-ai/extractous), so you can now feel free to run it outside Docker. 🎉
7 |
8 | 
9 |
10 | ### File types supported:
11 | - `PDF`
12 | - `DOCX`
13 | - `XLSX`
14 | - `PPTX`
15 | - any text-based format
16 | - and many more!!
17 |
18 | ### MANSPIDER will crawl every share on every target system. If provided creds don't work, it will fall back to "guest", then to a null session.
19 | 
20 |
21 | ### Installation:
22 | (Optional) Install these dependencies to add additional file parsing capability:
23 | ~~~
24 | # for images (png, jpeg)
25 | $ sudo apt install tesseract-ocr
26 |
27 | # for legacy document support (.doc)
28 | $ sudo apt install antiword
29 | ~~~
30 | Install manspider (please be patient, this can take a while):
31 | ~~~
32 | $ pip install pipx
33 | $ pipx install git+https://github.com/blacklanternsecurity/MANSPIDER
34 | ~~~
35 |
36 | ## Installation (Docker)
37 |
38 | ```bash
39 | docker run --rm -v ./manspider:/root/.manspider blacklanternsecurity/manspider --help
40 | ```
41 |
42 | Note there is also a helper script `manspider.sh` which will automatically mount volumes for manspider's `loot` and `logs` directories, making it a bit more convenient to run:
43 |
44 | ```bash
45 | ./manspider.sh --help
46 | ```
47 |
48 | ### Example #1: Search the network for filenames that may contain creds
49 | NOTE: matching files are automatically downloaded into `$HOME/.manspider/loot`! (`-n` to disable)
50 | ~~~
51 | $ manspider 192.168.0.0/24 -f passw user admin account network login logon cred -d evilcorp -u bob -p Passw0rd
52 | ~~~
53 |
54 | ### Example #2: Search for spreadsheets with "password" in the filename
55 | ~~~
56 | $ manspider share.evilcorp.local -f passw -e xlsx csv -d evilcorp -u bob -p Passw0rd
57 | ~~~
58 |
59 | ### Example #3: Search for documents containing passwords
60 | ~~~
61 | $ manspider share.evilcorp.local -c passw -e xlsx csv docx pdf -d evilcorp -u bob -p Passw0rd
62 | ~~~
63 |
64 | ### Example #4: Search for interesting file extensions
65 | ~~~
66 | $ manspider share.evilcorp.local -e bat com vbs ps1 psd1 psm1 pem key rsa pub reg pfx cfg conf config vmdk vhd vdi dit -d evilcorp -u bob -p Passw0rd
67 | ~~~
68 |
69 | ### Example #5: Search for finance-related files
70 | This example searches financy-sounding directories for filenames containing 5 or more consecutive numbers (e.g. `000202006.EFT`)
71 | ~~~
72 | $ manspider share.evilcorp.local --dirnames bank financ payable payment reconcil remit voucher vendor eft swift -f '[0-9]{5,}' -d evilcorp -u bob -p Passw0rd
73 | ~~~
74 |
75 | ### Example #6: Search for SSH keys by filename
76 | ~~~
77 | $ manspider share.evilcorp.local -e ppk rsa pem ssh rsa -o -f id_rsa id_dsa id_ed25519 -d evilcorp -u bob -p Passw0rd
78 | ~~~
79 |
80 | ### Example #7: Search for SSH keys by content
81 | ~~~
82 | $ manspider share.evilcorp.local -e '' -c 'BEGIN .{1,10} PRIVATE KEY' -d evilcorp -u bob -p Passw0rd
83 | ~~~
84 |
85 | ### Example #8: Search for password manager files
86 | ~~~bash
87 | # .kdbx - KeePass Password Database (KeePass, KeePassXC)
88 | # .kdb - KeePass Classic Database (KeePass 1.x)
89 | # .1pif - 1Password Interchange Format (1Password)
90 | # .agilekeychain - Agile Keychain Format (1Password, deprecated)
91 | # .opvault - OPVault Format (1Password)
92 | # .lpd - LastPass Data File (LastPass)
93 | # .dashlane - Dashlane Data File (Dashlane)
94 | # .psafe3 - Password Safe Database (Password Safe)
95 | # .enpass - Enpass Password Manager Data File (Enpass)
96 | # .bwdb - Bitwarden Database (Bitwarden)
97 | # .msecure - mSecure Password Manager Data File (mSecure)
98 | # .stickypass - Sticky Password Data File (Sticky Password)
99 | # .pwm - Password Memory Data File (Password Memory)
100 | # .rdb - RoboForm Data File (RoboForm)
101 | # .safe - SafeInCloud Password Manager Data File (SafeInCloud)
102 | # .zps - Zoho Vault Encrypted Data File (Zoho Vault)
103 | # .pmvault - SplashID Safe Data File (SplashID Safe)
104 | # .mywallet - MyWallet Password Manager Data File (MyWallet)
105 | # .jpass - JPass Password Manager Data File (JPass)
106 | # .pwmdb - Universal Password Manager Database (Universal Password Manager)
107 | $ manspider share.evilcorp.local -e kdbx kdb 1pif agilekeychain opvault lpd dashlane psafe3 enpass bwdb msecure stickypass pwm rdb safe zps pmvault mywallet jpass pwmdb -d evilcorp -u bob -p Passw0rd
108 | ~~~
109 |
110 | ### Example #9: Search for certificates
111 | ~~~
112 | $ manspider share.evilcorp.local -e pfx p12 pkcs12 pem key crt cer csr jks keystore key keys der -d evilcorp -u bob -p Passw0rd
113 | ~~~
114 |
115 | ### Usage Tip #1:
116 | You can run multiple instances of manspider at one time. This is useful when one instance is already running, and you want to search what it's downloaded (similar to `grep -R`). To do this, specify the keyword `loot` as the target, which will search the downloaded files in `$HOME/.manspider/loot`.
117 |
118 | ### Usage Tip #2:
119 | Reasonable defaults help prevent unwanted scenarios like getting stuck on a single target. All of these can be overridden:
120 | - **default spider depth: 10** (override with `-m`)
121 | - **default max filesize: 10MB** (override with `-s`)
122 | - **default threads: 5** (override with `-t`)
123 | - **shares excluded: `C$`, `IPC$`, `ADMIN$`, `PRINT$`** (override with `--exclude-sharenames`)
124 |
125 | ### Usage Tip #3:
126 | Manspider accepts any combination of the following as targets:
127 | - IPs
128 | - hostnames
129 | - subnets (CIDR format)
130 | - files containing any of the above
131 | - local folders containing files
132 |
133 | For example, you could specify any or all of these:
134 | - **`192.168.1.250`**
135 | - **`share.evilcorp.local`**
136 | - **`192.168.1.0/24`**
137 | - **`smb_hosts.txt`**
138 | - **`loot`** (to search already-downloaded files)
139 | - **`/mnt/share`** (to recursively search a directory)
140 | - NOTE: when searching local files, you must specify a directory, not an individual file
141 |
142 | ## Usage:
143 | ~~~
144 | usage: manspider [-h] [-u USERNAME] [-p PASSWORD] [-d DOMAIN] [-l LOOT_DIR] [-m MAXDEPTH] [-H HASH] [-k] [-aesKey HEX] [-dc-ip IP] [-t THREADS] [-f REGEX [REGEX ...]] [-e EXT [EXT ...]]
145 | [--exclude-extensions EXT [EXT ...]] [-c REGEX [REGEX ...]] [--sharenames SHARE [SHARE ...]] [--exclude-sharenames [SHARE ...]] [--dirnames DIR [DIR ...]]
146 | [--exclude-dirnames DIR [DIR ...]] [-q] [-n] [-mfail INT] [-o] [-s SIZE] [-v]
147 | targets [targets ...]
148 |
149 | Scan for juicy data on SMB shares. Matching files and logs are stored in $HOME/.manspider. All filters are case-insensitive.
150 |
151 | positional arguments:
152 | targets IPs, Hostnames, CIDR ranges, or files containing targets to spider (NOTE: local searching also supported, specify directory name or keyword "loot" to search
153 | downloaded files)
154 |
155 | options:
156 | -h, --help show this help message and exit
157 | -u, --username USERNAME
158 | username for authentication
159 | -p, --password PASSWORD
160 | password for authentication
161 | -d, --domain DOMAIN domain for authentication
162 | -l, --loot-dir LOOT_DIR
163 | loot directory (default ~/.manspider/)
164 | -m, --maxdepth MAXDEPTH
165 | maximum depth to spider (default: 10)
166 | -H, --hash HASH NTLM hash for authentication
167 | -k, --kerberos Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters
168 | -aesKey, --aes-key HEX
169 | AES key to use for Kerberos Authentication (128 or 256 bits)
170 | -dc-ip, --dc-ip IP IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter
171 | -t, --threads THREADS
172 | concurrent threads (default: 5)
173 | -f, --filenames REGEX [REGEX ...]
174 | filter filenames using regex (space-separated)
175 | -e, --extensions EXT [EXT ...]
176 | only show filenames with these extensions (space-separated, e.g. `docx xlsx` for only word & excel docs)
177 | --exclude-extensions EXT [EXT ...]
178 | ignore files with these extensions
179 | -c, --content REGEX [REGEX ...]
180 | search for file content using regex (multiple supported)
181 | --sharenames SHARE [SHARE ...]
182 | only search shares with these names (multiple supported)
183 | --exclude-sharenames [SHARE ...]
184 | don't search shares with these names (multiple supported)
185 | --dirnames DIR [DIR ...]
186 | only search directories containing these strings (multiple supported)
187 | --exclude-dirnames DIR [DIR ...]
188 | don't search directories containing these strings (multiple supported)
189 | -q, --quiet don't display matching file content
190 | -n, --no-download don't download matching files
191 | -mfail, --max-failed-logons INT
192 | limit failed logons
193 | -o, --or-logic use OR logic instead of AND (files are downloaded if filename OR extension OR content match)
194 | -s, --max-filesize SIZE
195 | don't retrieve files over this size, e.g. "500K" or ".5M" (default: 10M)
196 | -v, --verbose show debugging messages
197 | ~~~
198 |
--------------------------------------------------------------------------------
/man_spider/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blacklanternsecurity/MANSPIDER/37cc069211afc2cbb42765bcd98afe1d448b1aac/man_spider/__init__.py
--------------------------------------------------------------------------------
/man_spider/lib/__init__.py:
--------------------------------------------------------------------------------
1 | from .parser import *
2 | from .util import *
3 | from .logger import *
4 | from .spider import *
5 |
--------------------------------------------------------------------------------
/man_spider/lib/errors.py:
--------------------------------------------------------------------------------
1 | import struct
2 | import logging
3 | from impacket.nmb import NetBIOSError, NetBIOSTimeout
4 | from impacket.smb import SessionError, UnsupportedFeature
5 | from impacket.smbconnection import SessionError as CSessionError
6 |
7 | # set up logging
8 | log = logging.getLogger('manspider')
9 |
10 |
11 | class MANSPIDERError(Exception):
12 | pass
13 |
14 | class FileRetrievalError(MANSPIDERError):
15 | pass
16 |
17 | class ShareListError(MANSPIDERError):
18 | pass
19 |
20 | class FileListError(MANSPIDERError):
21 | pass
22 |
23 | class LogonFailure(MANSPIDERError):
24 | pass
25 |
26 |
27 | native_impacket_errors = (
28 | struct.error,
29 | NetBIOSError,
30 | NetBIOSTimeout,
31 | SessionError,
32 | CSessionError,
33 | UnsupportedFeature,
34 | )
35 |
36 |
37 | impacket_errors = (
38 | OSError,
39 | BrokenPipeError,
40 | ) + native_impacket_errors
41 |
42 |
43 | def impacket_error(e):
44 | '''
45 | Tries to format impacket exceptions nicely
46 | '''
47 | if type(e) in (SessionError, CSessionError):
48 | try:
49 | error_str = e.getErrorString()[0]
50 | e.args = (error_str,)
51 | except (IndexError,):
52 | pass
53 | if not e.args:
54 | e.args = ('',)
55 | return e
56 |
--------------------------------------------------------------------------------
/man_spider/lib/file.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from man_spider.lib.util import *
4 | from man_spider.lib.errors import *
5 |
6 |
7 | class RemoteFile():
8 | '''
9 | Represents a file on an SMB share
10 | Passed from a spiderling up to its parent spide
11 | r '''
12 |
13 | def __init__(self, name, share, target, size=0):
14 |
15 | self.share = share
16 | self.target = target
17 | self.name = name
18 | self.size = size
19 | self.smb_client = None
20 |
21 | file_suffix = Path(name).suffix.lower()
22 | self.tmp_filename = Path('/tmp/.manspider') / (random_string(15) + file_suffix)
23 |
24 |
25 | def get(self, smb_client=None):
26 | '''
27 | Downloads file to self.tmp_filename
28 |
29 | NOTE: SMBConnection() can't be passed through a multiprocessing queue
30 | This means that smb_client must be set after the file arrives at Spider()
31 | '''
32 |
33 | if smb_client is None and self.smb_client is None:
34 | raise FileRetrievalError('Please specify smb_client')
35 |
36 | #memfile = io.BytesIO()
37 | with open(str(self.tmp_filename), 'wb') as f:
38 |
39 | try:
40 | smb_client.conn.getFile(self.share, self.name, f.write)
41 | except Exception as e:
42 | smb_client.handle_impacket_error(e, self.share, self.name)
43 | raise FileRetrievalError(f'Error retrieving file "{str(self)}": {str(e)[:150]}')
44 |
45 | # reset cursor back to zero so .read() will return the whole file
46 | #memfile.seek(0)
47 |
48 |
49 | def __str__(self):
50 |
51 | return f'{self.target}\\{self.share}\\{self.name}'
52 |
--------------------------------------------------------------------------------
/man_spider/lib/logger.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from copy import copy
3 | from sys import stdout
4 | from pathlib import Path
5 | from datetime import datetime
6 | from multiprocessing import Queue
7 | from logging.handlers import QueueHandler, QueueListener
8 |
9 |
10 | ### PRETTY COLORS ###
11 |
12 |
13 | class ColoredFormatter(logging.Formatter):
14 |
15 | color_mapping = {
16 | 'DEBUG': 69, # blue
17 | 'INFO': 118, # green
18 | 'WARNING': 208, # orange
19 | 'ERROR': 196, # red
20 | 'CRITICAL': 196, # red
21 | }
22 |
23 | char_mapping = {
24 | 'DEBUG': '*',
25 | 'INFO': '+',
26 | 'WARNING': '-',
27 | 'ERROR': '!',
28 | 'CRITICAL': '!!!',
29 | }
30 |
31 | prefix = '\033[1;38;5;'
32 | suffix = '\033[0m'
33 |
34 | def __init__(self, pattern):
35 |
36 | super().__init__(pattern)
37 |
38 |
39 | def format(self, record):
40 |
41 | colored_record = copy(record)
42 | levelname = colored_record.levelname
43 | levelchar = self.char_mapping.get(levelname, '+')
44 | seq = self.color_mapping.get(levelname, 15) # default white
45 | colored_levelname = f'{self.prefix}{seq}m[{levelchar}]{self.suffix}'
46 | colored_record.levelname = colored_levelname
47 |
48 | return logging.Formatter.format(self, colored_record)
49 |
50 |
51 | @classmethod
52 | def green(cls, s):
53 |
54 | return cls.color(s)
55 |
56 |
57 | @classmethod
58 | def red(cls, s):
59 |
60 | return cls.color(s, level='ERROR')
61 |
62 |
63 | @classmethod
64 | def color(cls, s, level='INFO'):
65 |
66 | color = cls.color_mapping.get(level)
67 | return f'{cls.prefix}{color}m{s}{cls.suffix}'
68 |
69 |
70 |
71 | class CustomQueueListener(QueueListener):
72 | '''
73 | Ignore errors in the monitor thread that result from a race condition when the program exits
74 | '''
75 | def _monitor(self):
76 | try:
77 | super()._monitor()
78 | except Exception:
79 | pass
80 |
81 |
82 | ### LOG TO STDERR ###
83 |
84 | console = logging.StreamHandler(stdout)
85 | # tell the handler to use this format
86 | console.setFormatter(ColoredFormatter('%(levelname)s %(message)s'))
87 |
88 | ### LOG TO FILE ###
89 |
90 | log_queue = Queue()
91 | listener = CustomQueueListener(log_queue, console)
92 | sender = QueueHandler(log_queue)
93 | logging.getLogger('manspider').handlers = [sender]
94 |
95 | logdir = Path.home() / '.manspider' / 'logs'
96 | logdir.mkdir(parents=True, exist_ok=True)
97 | logfile = f'manspider_{datetime.now().strftime("%m-%d-%Y")}.log'
98 | handler = logging.FileHandler(str(logdir / logfile))
99 | handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
100 | logging.getLogger('manspider').addHandler(handler)
--------------------------------------------------------------------------------
/man_spider/lib/parser/__init__.py:
--------------------------------------------------------------------------------
1 | from .parser import *
--------------------------------------------------------------------------------
/man_spider/lib/parser/parser.py:
--------------------------------------------------------------------------------
1 | import re
2 | import magic
3 | import logging
4 | from time import sleep
5 | import subprocess as sp
6 | from extractous import Extractor
7 |
8 | from man_spider.lib.util import *
9 | from man_spider.lib.logger import *
10 |
11 | log = logging.getLogger('manspider.parser')
12 |
13 |
14 | class FileParser:
15 |
16 | # don't parse files with these magic types
17 | magic_blacklist = [
18 | # PNG, JPEG, etc.
19 | # 'image data',
20 | # ZIP, GZ, etc.
21 | 'archive data',
22 | # encrypted data
23 | 'encrypted'
24 | ]
25 |
26 |
27 | def __init__(self, filters, quiet=False):
28 | self.init_content_filters(filters)
29 | self.extractor = Extractor()
30 | self.quiet = quiet
31 |
32 |
33 | def init_content_filters(self, file_content):
34 | '''
35 | Get ready to search by file content
36 | '''
37 |
38 | # strings to look for in file content
39 | # if empty, content is ignored
40 | self.content_filters = []
41 | for f in file_content:
42 | try:
43 | self.content_filters.append(re.compile(f, re.I))
44 | except re.error as e:
45 | log.error(f'Unsupported file content regex "{f}": {e}')
46 | sleep(1)
47 | if self.content_filters:
48 | content_filter_str = '"' + '", "'.join([f.pattern for f in self.content_filters]) + '"'
49 | log.info(f'Searching by file content: {content_filter_str}')
50 |
51 |
52 |
53 | def match(self, file_content):
54 | '''
55 | Finds all regex matches in file content
56 | '''
57 |
58 | for _filter in self.content_filters:
59 | for match in _filter.finditer(file_content):
60 | # ( filter, (match_start_index, match_end_index) )
61 | yield (_filter, match.span())
62 |
63 |
64 | def match_magic(self, file):
65 | '''
66 | Returns True if the file isn't of a blacklisted file type
67 | '''
68 |
69 | # get magic type
70 | magic_type = magic.from_file(str(file)).lower()
71 | for keyword in self.magic_blacklist:
72 | if keyword.lower() in magic_type:
73 | log.debug(f'Not parsing {file}: blacklisted magic type: "{keyword}"')
74 | return False
75 |
76 | return True
77 |
78 |
79 | def grep(self, content, pattern):
80 |
81 | if not self.quiet:
82 | try:
83 | '''
84 | GREP(1)
85 | -E, --extended-regexp
86 | Interpret PATTERN as an extended regular expression
87 | -i, --ignore-case
88 | Ignore case distinctions
89 | -a, --text
90 | Process a binary file as if it were text
91 | -m NUM, --max-count=NUM
92 | Stop reading a file after NUM matching lines
93 | '''
94 | grep_process = sp.Popen(
95 | ['grep', '-Eiam', '5', '--color=always', pattern],
96 | stdin=sp.PIPE,
97 | stdout=sp.PIPE
98 | )
99 | grep_output = grep_process.communicate(content)[0]
100 | for line in grep_output.splitlines():
101 | log.info(better_decode(line[:500]))
102 | except (sp.SubprocessError, OSError, IndexError):
103 | pass
104 |
105 |
106 | def parse_file(self, file, pretty_filename=None):
107 | '''
108 | Parse a file on the local filesystem
109 | '''
110 |
111 | if pretty_filename is None:
112 | pretty_filename = str(file)
113 |
114 | log.debug(f'Parsing file: {pretty_filename}')
115 |
116 | matches = dict()
117 |
118 | try:
119 |
120 | matches = self.extractous(file, pretty_filename=pretty_filename)
121 |
122 | except Exception as e:
123 | if log.level <= logging.DEBUG:
124 | log.warning(f'Error extracting text from {pretty_filename}: {e}')
125 | else:
126 | log.warning(f'Error extracting text from {pretty_filename} (-v to debug)')
127 |
128 | return matches
129 |
130 |
131 | def extractous(self, file, pretty_filename):
132 | '''
133 | Extracts text from a file using the extractous library
134 | '''
135 |
136 | matches = dict()
137 |
138 | suffix = Path(str(file)).suffix.lower()
139 |
140 | # blacklist certain mime types
141 | if not self.match_magic(file):
142 | return matches
143 |
144 | text_content, metadata = self.extractor.extract_file_to_string(str(file))
145 |
146 | # try to convert to UTF-8 for grep-friendliness
147 | try:
148 | binary_content = text_content.encode('utf-8', errors='ignore')
149 | except Exception:
150 | pass
151 |
152 | # count the matches
153 | for _filter, match in self.match(text_content):
154 | try:
155 | matches[_filter] += 1
156 | except KeyError:
157 | matches[_filter] = 1
158 |
159 | for _filter, match_count in matches.items():
160 | log.info(ColoredFormatter.green(f'{pretty_filename}: matched "{_filter.pattern}" {match_count:,} times'))
161 | # run grep for pretty output
162 | if not self.quiet:
163 | self.grep(binary_content, _filter.pattern)
164 |
165 | return matches
166 |
--------------------------------------------------------------------------------
/man_spider/lib/processpool.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import traceback
3 | from time import sleep
4 | import multiprocessing as mp
5 | from queue import Empty, Full
6 |
7 |
8 | # set up logging
9 | log = logging.getLogger('manspider.processpool')
10 |
11 |
12 |
13 | class ProcessPool:
14 | '''
15 | usage:
16 | with ProcessPool(2) as pool:
17 | for i in pool.map(target, iterable):
18 | yield i
19 | '''
20 |
21 | def __init__(self, processes=None, daemon=False, name=''):
22 |
23 | self.name = f'ProcessPool'
24 | if name:
25 | self.name += f'-{name}'
26 |
27 | if processes is None:
28 | processes = mp.cpu_count()
29 |
30 | self.processes = processes
31 | self.pool = [None] * self.processes
32 |
33 | self.started_counter = 0
34 | self.finished_counter = 0
35 |
36 | self.daemon = daemon
37 |
38 | # make the result queue
39 | self.result_queue = mp.Manager().Queue()
40 |
41 |
42 | def map(self, func, iterable, args=(), kwargs={}):
43 |
44 | # loop until we're out of work
45 | for entry in iterable:
46 |
47 | try:
48 |
49 | while 1:
50 |
51 | for result in self.results:
52 | yield result
53 |
54 | # start processes
55 | for i in range(len(self.pool)):
56 | process = self.pool[i]
57 | if process is None or not process.is_alive():
58 | self.pool[i] = mp.Process(target=self.execute, args=(func, self.result_queue, (entry,)+args), \
59 | kwargs=kwargs, daemon=self.daemon)
60 | self.pool[i].start()
61 | self.started_counter += 1
62 | log.debug(f'{self.name}: {self.started_counter:,} processes started')
63 | # success, move on to next
64 | assert False
65 |
66 | for result in self.results:
67 | yield result
68 |
69 | # prevent unnecessary CPU usage
70 | sleep(.1)
71 |
72 | except AssertionError:
73 | continue
74 |
75 | # wait for processes to finish
76 | while 1:
77 |
78 | finished_threads = [p is None or not p.is_alive() for p in self.pool]
79 | if all(finished_threads):
80 | self.finished_counter += len([p for p in self.pool if p is not None and not p.is_alive()])
81 | break
82 | else:
83 | log.debug(f'{self.name}: Waiting for {finished_threads.count(False):,} threads to finish')
84 | sleep(1)
85 |
86 | for result in self.results:
87 | yield result
88 |
89 |
90 | @property
91 | def results(self):
92 |
93 | while 1:
94 | try:
95 | result = self.result_queue.get_nowait()
96 | self.finished_counter += 1
97 | log.debug(f'{self.name}: {self.finished_counter:,} processes finished')
98 | yield result
99 | except Empty:
100 | sleep(.1)
101 | break
102 |
103 |
104 | @staticmethod
105 | def execute(func, result_queue, args=(), kwargs={}):
106 | '''
107 | Executes given function and places return value in result queue
108 | '''
109 |
110 | try:
111 | result_queue.put(func(*args, **kwargs))
112 | except Exception as e:
113 | if type(e) not in [FileNotFoundError]:
114 | log.critical(format_exc())
115 | except KeyboardInterrupt as e:
116 | log.critical('ProcessPool Interrupted')
117 |
118 |
119 | @staticmethod
120 | def _close_queue(q):
121 |
122 | while 1:
123 | try:
124 | q.get_nowait()
125 | except Empty:
126 | break
127 | q.close()
128 |
129 |
130 | def __enter__(self):
131 |
132 | return self
133 |
134 |
135 | def __exit__(self, exception_type, exception_value, traceback):
136 |
137 | try:
138 | self._close_queue(self.result_queue)
139 | except Exception:
140 | pass
--------------------------------------------------------------------------------
/man_spider/lib/smb.py:
--------------------------------------------------------------------------------
1 | import ntpath
2 | import logging
3 | from contextlib import suppress
4 | from impacket.nmb import NetBIOSError, NetBIOSTimeout
5 | from impacket.smbconnection import SessionError, SMBConnection
6 |
7 | from man_spider.lib.errors import *
8 |
9 | # set up logging
10 | log = logging.getLogger('manspider.smb')
11 |
12 |
13 | class SMBClient:
14 | '''
15 | Wrapper around impacket's SMBConnection() object
16 | '''
17 |
18 | def __init__(self, server, username, password, domain, nthash, use_kerberos=False, aes_key="", dc_ip=None):
19 |
20 | self.server = server
21 |
22 | self.conn = None
23 |
24 | self.username = username
25 | self.password = password
26 | self.domain = domain
27 | self.nthash = nthash
28 | self.use_kerberos = use_kerberos
29 | self.aes_key = aes_key
30 | self.dc_ip = dc_ip
31 | self.hostname = None
32 | self.dns_domain = None
33 | if self.nthash:
34 | self.lmhash = 'aad3b435b51404eeaad3b435b51404ee'
35 | else:
36 | self.lmhash = ''
37 | self._shares = None
38 |
39 |
40 | def list_shares(self):
41 | '''
42 | List shares on the SMB server
43 | '''
44 | resp = self.conn.listShares()
45 | for i in range(len(resp)):
46 | sharename = resp[i]['shi1_netname'][:-1]
47 | log.debug(f'{self.server}: Found share: {sharename}')
48 | yield sharename
49 |
50 |
51 | @property
52 | def shares(self):
53 | if self._shares is None:
54 | try:
55 | self._shares = list(self.list_shares())
56 | except Exception as e:
57 | e = self.handle_impacket_error(e)
58 | log.debug(f'{self.server}: Error listing shares: {e}, retrying...')
59 | self.rebuild(e)
60 | try:
61 | self._shares = list(self.list_shares())
62 | except Exception as e:
63 | e = self.handle_impacket_error(e)
64 | log.warning(f'{self.server}: Error listing shares: {e}')
65 | self.rebuild(e)
66 | return self._shares or []
67 |
68 |
69 | def get_hostname(self):
70 | '''
71 | Get the hostname from the SMB connection
72 | '''
73 | try:
74 | conn = SMBConnection(
75 | self.server,
76 | self.server,
77 | None,
78 | 445,
79 | timeout=10,
80 | )
81 | with suppress(Exception):
82 | conn.login("", "")
83 |
84 | if self.hostname is None:
85 | try:
86 | # Get the server name from SMB
87 | self.hostname = str(conn.getServerName()).strip().replace("\x00", "").lower()
88 | if self.hostname:
89 | log.debug(f'{self.server}: Got hostname: {self.hostname}')
90 | else:
91 | log.debug(f'{self.server}: No hostname found')
92 | except Exception as e:
93 | log.debug(f'{self.server}: Error getting hostname from SMB: {e}')
94 | self.hostname = ""
95 |
96 | if self.dns_domain is None:
97 | try:
98 | self.dns_domain = str(conn.getServerDNSDomainName()).strip().replace("\x00", "").lower()
99 | if self.dns_domain:
100 | log.debug(f'{self.server}: Got DNS domain: {self.dns_domain}')
101 | else:
102 | log.debug(f'{self.server}: No DNS domain found')
103 | except Exception as e:
104 | log.debug(f'{self.server}: Error getting DNS domain: {e}')
105 | self.dns_domain = (self.domain if self.domain else "")
106 |
107 | except Exception as e:
108 | log.debug(f'{self.server}: Error getting hostname: {e}')
109 |
110 | return self.hostname, self.domain
111 |
112 | def login(self, refresh=False, first_try=True):
113 | '''
114 | Create a new SMBConnection object (if there isn't one already or if refresh is True)
115 | Attempt to log in, and switch to null session if logon fails
116 | Return True if logon succeeded
117 | Return False if logon failed
118 | '''
119 |
120 | target_server = self.server
121 | if self.use_kerberos:
122 | hostname, domain = self.get_hostname()
123 | if hostname:
124 | target_server = hostname
125 | if domain:
126 | target_server = f"{hostname}.{domain}"
127 |
128 | if self.conn is None or refresh:
129 | try:
130 | self.conn = SMBConnection(target_server, target_server, sess_port=445, timeout=20)
131 | except Exception as e:
132 | log.debug(impacket_error(e))
133 | return None
134 |
135 | try:
136 |
137 | if self.username in [None, '', 'Guest'] and first_try:
138 | # skip to guest / null session
139 | assert False
140 |
141 | user_str = self.username
142 | if self.domain:
143 | user_str = f'{self.domain}\\{self.username}'
144 | log.debug(f'{target_server} ({self.server}): Authenticating as "{user_str}"')
145 |
146 | if self.use_kerberos:
147 | self.conn.kerberosLogin(
148 | self.username,
149 | self.password,
150 | self.domain,
151 | self.lmhash,
152 | self.nthash,
153 | self.aes_key,
154 | kdcHost=self.dc_ip,
155 | )
156 | # pass the hash if requested
157 | elif self.nthash and not self.password:
158 | self.conn.login(
159 | self.username,
160 | '',
161 | lmhash=self.lmhash,
162 | nthash=self.nthash,
163 | domain=self.domain,
164 | )
165 | # otherwise, normal login
166 | else:
167 | self.conn.login(
168 | self.username,
169 | self.password,
170 | domain=self.domain,
171 | )
172 |
173 | log.info(f'{self.server}: Successful login as "{self.username}"')
174 | return True
175 |
176 | except Exception as e:
177 |
178 | if type(e) != AssertionError:
179 | e = self.handle_impacket_error(e, display=True)
180 |
181 | # try guest account, then null session if logon failed
182 | if first_try:
183 |
184 | bad_statuses = ['LOGON_FAIL', 'PASSWORD_EXPIRED', 'LOCKED_OUT', 'SESSION_DELETED']
185 | if any([s in str(e) for s in bad_statuses]):
186 | for s in bad_statuses:
187 | if s in str(e):
188 | log.warning(f'{self.server}: {s}: {self.username}')
189 |
190 | log.debug(f'{self.server}: Trying guest session')
191 | self.username = 'Guest'
192 | self.password = ''
193 | self.domain = ''
194 | self.nthash = ''
195 | guest_success = self.login(refresh=True, first_try=False)
196 | if not guest_success:
197 | log.debug(f'{self.server}: Switching to null session')
198 | self.username = ''
199 | self.login(refresh=True, first_try=False)
200 |
201 | return False
202 |
203 | else:
204 | return True
205 |
206 |
207 | def ls(self, share, path):
208 | '''
209 | List files in share/path
210 | Raise FileListError if there's a problem
211 | @byt3bl33d3r it's really not that bad
212 | '''
213 |
214 | nt_path = ntpath.normpath(f'{path}\\*')
215 |
216 | # for every file/dir in "path"
217 | try:
218 | for f in self.conn.listPath(share, nt_path):
219 | # exclude current and parent directory
220 | if f.get_longname() not in ['', '.', '..']:
221 | yield f
222 | except Exception as e:
223 | e = self.handle_impacket_error(e)
224 | raise FileListError(f'{e.args}: Error listing files at "{share}{nt_path}"')
225 |
226 |
227 | def rebuild(self, error=''):
228 | '''
229 | Rebuild our SMBConnection() if it gets borked
230 | '''
231 | log.debug(f'Rebuilding connection to {self.server} after error: {error}')
232 | self.login(refresh=True)
233 |
234 |
235 | def handle_impacket_error(self, e, share='', filename='', display=False):
236 | '''
237 | Handle arbitrary Impacket errors
238 | this is needed because the library doesn't implement proper inheritance for its exceptions
239 | '''
240 | resource_str = '/'.join([self.server, share, filename]).rstrip('/')
241 |
242 | if type(e) == KeyboardInterrupt:
243 | raise
244 | elif type(e) in (NetBIOSError, NetBIOSTimeout, BrokenPipeError, SessionError, CSessionError):
245 | # the connection may need to be rebuilt
246 | if type(e) in (SessionError, CSessionError):
247 | if any([x in str(e) for x in ('PASSWORD_EXPIRED',)]):
248 | self.rebuild(e)
249 | else:
250 | self.rebuild(e)
251 | if type(e) in native_impacket_errors:
252 | e = impacket_error(e)
253 | if display:
254 | log.debug(f'{resource_str}: {str(e)[:150]}')
255 |
256 | return e
257 |
--------------------------------------------------------------------------------
/man_spider/lib/spider.py:
--------------------------------------------------------------------------------
1 | import re
2 | import queue
3 | from time import sleep
4 | import multiprocessing
5 | from pathlib import Path
6 |
7 | from man_spider.lib.spiderling import *
8 | from man_spider.lib.parser import FileParser
9 |
10 | # set up logging
11 | log = logging.getLogger('manspider')
12 |
13 |
14 | class MANSPIDER:
15 |
16 | def __init__(self, options):
17 |
18 | self.targets = options.targets
19 | self.threads = options.threads
20 | self.maxdepth = options.maxdepth
21 | self.quiet = options.quiet
22 |
23 | self.username = options.username
24 | self.password = options.password
25 | self.domain = options.domain
26 | self.nthash = options.hash
27 | self.use_kerberos = options.kerberos
28 | self.aes_key = options.aes_key
29 | self.dc_ip = options.dc_ip
30 | self.max_failed_logons = options.max_failed_logons
31 | self.max_filesize = options.max_filesize
32 |
33 | self.share_whitelist = options.sharenames
34 | self.share_blacklist = options.exclude_sharenames
35 |
36 | self.dir_whitelist = options.dirnames
37 | self.dir_blacklist = options.exclude_dirnames
38 |
39 | self.no_download = options.no_download
40 |
41 | # applies "or" logic instead of "and"
42 | # e.g. file is downloaded if filename OR extension OR content match
43 | self.or_logic = options.or_logic
44 |
45 | self.extension_blacklist= options.exclude_extensions
46 | self.file_extensions = options.extensions
47 | if self.file_extensions:
48 | extensions_str = '"' + '", "'.join(list(self.file_extensions)) + '"'
49 | log.info(f'Searching by file extension: {extensions_str}')
50 |
51 | self.init_filename_filters(options.filenames)
52 | self.parser = FileParser(options.content, quiet=self.quiet)
53 |
54 | self.failed_logons = 0
55 |
56 | self.spiderling_pool = [None] * self.threads
57 | self.spiderling_queue = multiprocessing.Manager().Queue()
58 |
59 | # prevents needing to continually instantiate new SMBClients
60 | # {target: SMBClient() ...}
61 | self.smb_client_cache = dict()
62 |
63 | # directory to store documents when searching contents
64 | self.tmp_dir = Path('/tmp/.manspider')
65 | self.tmp_dir.mkdir(exist_ok=True)
66 |
67 | # directory to store matching documents
68 | self.loot_dir = Path.home() / '.manspider' / 'loot'
69 |
70 | if(options.loot_dir):
71 | self.loot_dir=Path(options.loot_dir)
72 |
73 | self.loot_dir.mkdir(parents=True, exist_ok=True)
74 |
75 | if not options.no_download:
76 | log.info(f'Matching files will be downloaded to {self.loot_dir}')
77 |
78 |
79 | def start(self):
80 |
81 | for target in self.targets:
82 | try:
83 | while 1:
84 | for i, process in enumerate(self.spiderling_pool):
85 | # if there's room in the pool
86 | if process is None or not process.is_alive():
87 | # start spiderling
88 | self.spiderling_pool[i] = multiprocessing.Process(
89 | target=Spiderling, args=(target, self), daemon=False
90 | )
91 | self.spiderling_pool[i].start()
92 | # success, break out of infinite loop
93 | assert False
94 | else:
95 | # otherwise, clear the queue
96 | self.check_spiderling_queue()
97 |
98 | except AssertionError:
99 | continue
100 |
101 | # save on CPU
102 | sleep(.1)
103 |
104 | while 1:
105 | self.check_spiderling_queue()
106 | dead_spiderlings = [s is None or not s.is_alive() for s in self.spiderling_pool]
107 | if all(dead_spiderlings):
108 | break
109 |
110 | # make sure the queue is empty
111 | self.check_spiderling_queue()
112 |
113 |
114 |
115 | def init_file_extensions(self, file_extensions):
116 | '''
117 | Get ready to search by file extension
118 | '''
119 |
120 | self.file_extensions = FileExtensions()
121 | if file_extensions:
122 | self.file_extensions.update(file_extensions)
123 |
124 |
125 |
126 | def init_filename_filters(self, filename_filters):
127 | '''
128 | Get ready to search by filename
129 | '''
130 |
131 | # strings to look for in filenames
132 | # if empty, all filenames are matched
133 | self.filename_filters = []
134 | for f in filename_filters:
135 | regex_str = str(f)
136 | try:
137 | if not any([f.startswith(x) for x in ['^', '.*']]):
138 | regex_str = rf'.*{regex_str}'
139 | if not any([f.endswith(x) for x in ['$', '.*']]):
140 | regex_str = rf'{regex_str}.*'
141 | self.filename_filters.append(re.compile(regex_str, re.I))
142 | except re.error as e:
143 | log.error(f'Unsupported filename regex "{f}": {e}')
144 | sleep(1)
145 | if self.filename_filters:
146 | filename_filter_str = '"' + '", "'.join([f.pattern for f in self.filename_filters]) + '"'
147 | log.info(f'Searching by filename: {filename_filter_str}')
148 |
149 |
150 | def check_spiderling_queue(self):
151 | '''
152 | Empty the spiderling queue
153 | '''
154 |
155 | while 1:
156 | try:
157 | message = self.spiderling_queue.get_nowait()
158 | self.process_message(message)
159 |
160 | except queue.Empty:
161 | break
162 |
163 |
164 | def process_message(self, message):
165 | '''
166 | Process messages from spiderlings
167 | Log messages, errors, files, etc.
168 | '''
169 | if message.type == 'a':
170 | if message.content == False:
171 | self.failed_logons += 1
172 | if self.lockout_threshold():
173 | log.error(f'REACHED MAXIMUM FAILED LOGONS OF {self.max_failed_logons:,}')
174 | log.error('KILLING EXISTING SPIDERLINGS AND CONTINUING WITH GUEST/NULL SESSIONS')
175 | #for spiderling in self.spiderling_pool:
176 | # spiderling.kill()
177 | self.username = ''
178 | self.password = ''
179 | self.nthash = ''
180 | self.domain = ''
181 |
182 |
183 | def lockout_threshold(self):
184 | '''
185 | Return True if we've reached max failed logons
186 | '''
187 |
188 | if self.max_failed_logons is not None:
189 | if self.failed_logons >= self.max_failed_logons and self.domain:
190 | return True
191 | return False
192 |
193 |
194 | def get_smb_client(self, target):
195 | '''
196 | Check if we already have an smb_client cached
197 | If not, then create it
198 | '''
199 |
200 | smb_client = self.smb_client_cache.get(target, None)
201 |
202 | if smb_client is None:
203 | smb_client = SMBClient(
204 | target,
205 | self.username,
206 | self.password,
207 | self.domain,
208 | self.nthash,
209 | self.use_kerberos,
210 | self.aes_key,
211 | self.dc_ip
212 | )
213 | logon_result = smb_client.login()
214 | if logon_result == False:
215 | self.failed_logons += 1
216 | self.smb_client_cache[target] = smb_client
217 |
218 | return smb_client
219 |
--------------------------------------------------------------------------------
/man_spider/lib/spiderling.py:
--------------------------------------------------------------------------------
1 | import string
2 | import logging
3 | import pathlib
4 | import multiprocessing
5 | from shutil import move
6 | from traceback import format_exc
7 |
8 | from man_spider.lib.smb import *
9 | from man_spider.lib.file import *
10 | from man_spider.lib.util import *
11 | from man_spider.lib.errors import *
12 | from man_spider.lib.processpool import *
13 |
14 |
15 | log = logging.getLogger('manspider.spiderling')
16 |
17 |
18 | class SpiderlingMessage:
19 | '''
20 | Message which gets sent back to the parent through parent_queue
21 | '''
22 |
23 | def __init__(self, message_type, target, content):
24 | '''
25 | "message_type" is a string, and can be:
26 | "e" - error
27 | "a" - authentication failure
28 | '''
29 | self.type = message_type
30 | self.target = target
31 | self.content = content
32 |
33 |
34 |
35 | class Spiderling:
36 | '''
37 | Enumerates SMB shares and spiders all possible directories/filenames up to maxdepth
38 | Designed to be threadable
39 | '''
40 |
41 | # these extensions don't get parsed for content, unless explicitly specified
42 | dont_parse = [
43 | '.png',
44 | '.gif',
45 | '.tiff',
46 | '.msi',
47 | '.bmp',
48 | '.jpg',
49 | '.jpeg',
50 | '.zip',
51 | '.gz',
52 | '.bz2',
53 | '.7z',
54 | '.xz',
55 | ]
56 |
57 | def __init__(self, target, parent):
58 |
59 | try:
60 |
61 | self.parent = parent
62 | self.target = target
63 |
64 | # unless we're only searching local files, connect to target
65 | if type(self.target) == pathlib.PosixPath:
66 | self.local = True
67 | self.go()
68 |
69 | else:
70 | self.local = False
71 |
72 | self.smb_client = SMBClient(
73 | target,
74 | parent.username,
75 | parent.password,
76 | parent.domain,
77 | parent.nthash,
78 | parent.use_kerberos,
79 | parent.aes_key,
80 | parent.dc_ip
81 | )
82 |
83 | logon_result = self.smb_client.login()
84 | if logon_result not in [True, None]:
85 | self.message_parent('a', logon_result)
86 |
87 | if logon_result is not None:
88 | self.go()
89 |
90 | # file parsing parallelized one process at a time
91 | # allows file to be parsed while next one is being fetched
92 | self.parser_process = None
93 |
94 | except KeyboardInterrupt:
95 | log.critical('Spiderling Interrupted')
96 |
97 | # log all exceptions
98 | except Exception as e:
99 | if log.level <= logging.DEBUG:
100 | log.error(format_exc())
101 | else:
102 | log.error(f'Error in spiderling: {e}')
103 |
104 |
105 | def go(self):
106 | '''
107 | go spider go spider go
108 | '''
109 |
110 | # local files
111 | if self.local:
112 | if self.parent.parser.content_filters:
113 | self.parse_local_files(self.files)
114 | else:
115 | # just list the files
116 | list(self.files)
117 |
118 | else:
119 | # remote files
120 | for file in self.files:
121 |
122 | # if content searching is enabled, parse the file
123 | if self.parent.parser.content_filters:
124 | try:
125 | self.parser_process.join()
126 | except AttributeError:
127 | pass
128 | self.parser_process = multiprocessing.Process(target=self.parse_file, args=(file,))
129 | self.parser_process.start()
130 |
131 | # otherwise, just save it
132 | elif not self.local:
133 | log.info(f'{self.target}: {file.share}\\{file.name} ({bytes_to_human(file.size)})')
134 | if not self.parent.no_download:
135 | self.save_file(file)
136 |
137 | log.info(f'Finished spidering {self.target}')
138 |
139 |
140 |
141 | @property
142 | def files(self):
143 | '''
144 | Yields all files on the target to be parsed/downloaded
145 | Premptively download matching files into temp directory
146 | '''
147 |
148 | if self.local:
149 | for file in list(list_files(self.target)):
150 | if self.extension_blacklisted(file):
151 | log.debug(f'{self.target}: Skipping {file}: extension is blacklisted')
152 | continue
153 | if self.path_match(file) or (self.parent.or_logic and self.parent.parser.content_filters):
154 | if self.path_match(file):
155 | log.debug(pathlib.Path(file).relative_to(self.target))
156 | if not self.is_binary_file(file):
157 | yield file
158 | else:
159 | log.debug(f'Skipping {file}: does not match filename/extension filters')
160 |
161 | else:
162 | for share in self.shares:
163 | for remote_file in self.list_files(share):
164 | if not self.parent.no_download or self.parent.parser.content_filters:
165 | self.get_file(remote_file)
166 | yield remote_file
167 |
168 |
169 |
170 | def parse_file(self, file):
171 | '''
172 | Simple wrapper around self.parent.parser.parse_file()
173 | For sole purpose of threading
174 | '''
175 |
176 | try:
177 |
178 | if type(file) == RemoteFile:
179 | matches = self.parent.parser.parse_file(str(file.tmp_filename), pretty_filename=str(file))
180 | if matches and not self.parent.no_download:
181 | self.save_file(file)
182 | else:
183 | file.tmp_filename.unlink()
184 |
185 | else:
186 | log.debug(f'Found file: {file}')
187 | self.parent.parser.parse_file(file, file)
188 |
189 | # log all exceptions
190 | except Exception as e:
191 | if log.level <= logging.DEBUG:
192 | log.error(format_exc())
193 | else:
194 | log.error(f'Error parsing file {file}: {e}')
195 |
196 | except KeyboardInterrupt:
197 | log.critical('File parsing interrupted')
198 |
199 |
200 | @property
201 | def shares(self):
202 | '''
203 | Lists all shares on single target
204 | '''
205 |
206 | for share in self.smb_client.shares:
207 | if self.share_match(share):
208 | yield share
209 |
210 |
211 |
212 | def list_files(self, share, path='', depth=0, tries=2):
213 | '''
214 | List files inside a specific directory
215 | Only yield files which conform to all filters (except content)
216 | '''
217 |
218 | if depth < self.parent.maxdepth and self.dir_match(path):
219 |
220 | files = []
221 | while tries > 0:
222 | try:
223 | files = list(self.smb_client.ls(share, path))
224 | break
225 | except FileListError as e:
226 | if 'ACCESS_DENIED' in str(e):
227 | log.debug(f'{self.target}: Error listing files: {e}')
228 | break
229 | else:
230 | tries -= 1
231 |
232 | if files:
233 | log.debug(f'{self.target}: {share}{path}: contains {len(files):,} items')
234 |
235 | for f in files:
236 | name = f.get_longname()
237 | full_path = f'{path}\\{name}'
238 | # if it's a directory, go deeper
239 | if f.is_directory():
240 | for file in self.list_files(share, full_path, (depth+1)):
241 | yield file
242 |
243 | else:
244 |
245 | # skip the file if it didn't match extension filters
246 | if self.extension_blacklisted(name):
247 | log.debug(f'{self.target}: Skipping {share}{full_path}: extension is blacklisted')
248 | continue
249 |
250 | if not self.path_match(name):
251 | if not (
252 | # all of these have to be true in order to get past this point
253 | # "or logic" is enabled
254 | self.parent.or_logic and
255 | # and file does not have a "don't parse" extension
256 | (not self.is_binary_file(name)) and
257 | # and content filters are enabled
258 | self.parent.parser.content_filters
259 | ):
260 | log.debug(f'{self.target}: Skipping {share}{full_path}: filename/extensions do not match')
261 | continue
262 |
263 | # try to get the size of the file
264 | try:
265 | filesize = f.get_filesize()
266 | except Exception as e:
267 | self.smb_client.handle_impacket_error(e)
268 | continue
269 |
270 | # make the RemoteFile object (the file won't be read yet)
271 | full_path_fixed = full_path.lstrip('\\')
272 | remote_file = RemoteFile(full_path_fixed, share, self.target, size=filesize)
273 |
274 | # if it's a non-empty file that's smaller than the size limit
275 | if filesize > 0 and filesize < self.parent.max_filesize:
276 |
277 | # if it matched filename/extension filters and we're downloading files
278 | if (self.parent.file_extensions or self.parent.filename_filters) and not self.parent.no_download:
279 | # but the extension is marked as "don't parse"
280 | if self.is_binary_file(name):
281 | # don't parse it, instead save it and continue
282 | log.info(f'{self.target}: {remote_file.share}\\{remote_file.name}')
283 | if self.get_file(remote_file):
284 | self.save_file(remote_file)
285 | continue
286 |
287 | # file is ready to be parsed
288 | yield remote_file
289 |
290 | else:
291 | log.debug(f'{self.target}: {full_path} is either empty or too large')
292 |
293 |
294 | def path_match(self, file):
295 | '''
296 | Based on whether "or" logic is enabled, return True or False
297 | if the filename + extension meets the requirements
298 | '''
299 | filename_match = self.filename_match(file)
300 | extension_match = self.extension_whitelisted(file)
301 | if self.parent.or_logic:
302 | return (filename_match and self.parent.filename_filters) or (extension_match and self.parent.file_extensions)
303 | else:
304 | return filename_match and extension_match
305 |
306 |
307 |
308 | def share_match(self, share):
309 | '''
310 | Return true if "share" matches any of the share filters
311 | '''
312 |
313 | # if the share has been whitelisted
314 | if ((not self.parent.share_whitelist) or (share.lower() in self.parent.share_whitelist)):
315 | # and hasn't been blacklisted
316 | if ((not self.parent.share_blacklist) or (share.lower() not in self.parent.share_blacklist)):
317 | return True
318 | else:
319 | log.debug(f'{self.target}: Skipping blacklisted share: {share}')
320 | else:
321 | log.debug(f'{self.target}: Skipping share {share}: not in whitelist')
322 |
323 | return False
324 |
325 |
326 | def dir_match(self, path):
327 | '''
328 | Return true if "path" matches any of the directory filters
329 | '''
330 |
331 | # convert forward slashes to backwards
332 | dirname = str(path).lower().replace('/', '\\')
333 |
334 | # root path always passes
335 | if not path:
336 | return True
337 |
338 | # if whitelist check passes
339 | if (not self.parent.dir_whitelist) or any([k.lower() in dirname for k in self.parent.dir_whitelist]):
340 | # and blacklist check passes
341 | if (not self.parent.dir_blacklist) or not any([k.lower() in dirname for k in self.parent.dir_blacklist]):
342 | return True
343 | else:
344 | log.debug(f'{self.target}: Skipping blacklisted dir: {path}')
345 | else:
346 | log.debug(f'{self.target}: Skipping dir {path}: not in whitelist')
347 |
348 | return False
349 |
350 |
351 | def filename_match(self, filename):
352 | '''
353 | Return true if "filename" matches any of the filename filters
354 | '''
355 |
356 | if (not self.parent.filename_filters) or any([f_regex.match(str(pathlib.Path(filename).stem)) for f_regex in self.parent.filename_filters]):
357 | return True
358 | else:
359 | log.debug(f'{self.target}: {filename} does not match filename filters')
360 |
361 | return False
362 |
363 |
364 | def is_binary_file(self, filename):
365 | '''
366 | Returns true if file is a bad extension type, e.g. encrypted or compressed
367 | '''
368 |
369 | extension = ''.join(pathlib.Path(filename).suffixes).lower()
370 | if any([extension.endswith(e.lower()) for e in self.dont_parse]):
371 | if extension not in self.parent.file_extensions:
372 | log.debug(f'{self.target}: Not parsing {filename} due to undesirable extension')
373 | return True
374 | return False
375 |
376 |
377 | def extension_blacklisted(self, filename):
378 | '''
379 | Return True if folder, file name, or extension has been blacklisted
380 | '''
381 | extension = ''.join(pathlib.Path(filename).suffixes).lower()
382 | excluded_extensions = list(self.parent.extension_blacklist)
383 |
384 | if not excluded_extensions:
385 | return False
386 |
387 | if not any([extension.endswith(e) for e in excluded_extensions]):
388 | return False
389 | else:
390 | log.debug(f'{self.target}: Skipping file with blacklisted extension: {filename}')
391 | return True
392 |
393 |
394 | def extension_whitelisted(self, filename):
395 | '''
396 | Return True if file extension has been whitelisted
397 | '''
398 | # a .tar.gz file will match both filters ".gz" and ".tar.gz"
399 | extension = ''.join(pathlib.Path(filename).suffixes).lower()
400 | extensions = list(self.parent.file_extensions)
401 |
402 | if not extensions:
403 | return True
404 |
405 | # if whitelist check passes
406 | if any([(extension.endswith(e) if e else extension == e) for e in extensions]):
407 | log.debug(f'{self.target}: {filename} matches extension filters')
408 | return True
409 | else:
410 | log.debug(f'{self.target}: Skipping file {filename}, does not match extension filters')
411 | return False
412 |
413 |
414 | def message_parent(self, message_type, content=''):
415 | '''
416 | Send a message to the parent spider
417 | '''
418 |
419 | self.parent.spiderling_queue.put(
420 | SpiderlingMessage(message_type, self.target, content)
421 | )
422 |
423 |
424 | def parse_local_files(self, files):
425 |
426 | with ProcessPool(self.parent.threads) as pool:
427 | for r in pool.map(self.parse_file, files):
428 | pass
429 |
430 |
431 | def save_file(self, remote_file):
432 | '''
433 | Moves a file from temp storage into the loot directory
434 | '''
435 |
436 | allowed_chars = string.ascii_lowercase + string.ascii_uppercase + string.digits + '._ '
437 |
438 | # replace backslashes with underscores to preserve directory names
439 | loot_filename = str(remote_file).replace('\\', '_')
440 | # remove weird characters
441 | loot_filename = ''.join([c for c in loot_filename if c in allowed_chars])
442 | loot_dest = self.parent.loot_dir / loot_filename
443 | try:
444 | move(str(remote_file.tmp_filename), str(loot_dest))
445 | except Exception:
446 | log.warning(f'Error saving {remote_file}')
447 |
448 |
449 | def get_file(self, remote_file):
450 | '''
451 | Attempts to retrieve "remote_file" from share and returns True if successful
452 | '''
453 |
454 | try:
455 | smb_client = self.parent.get_smb_client(self.target)
456 | log.debug(f'{self.target}: Downloading {remote_file.share}\\{remote_file.name}')
457 | remote_file.get(smb_client)
458 | return True
459 | except FileRetrievalError as e:
460 | log.debug(f'{self.target}: {e}')
461 |
462 | return False
463 |
464 |
--------------------------------------------------------------------------------
/man_spider/lib/util.py:
--------------------------------------------------------------------------------
1 | import os
2 | import magic
3 | import string
4 | import random
5 | import logging
6 | import ipaddress
7 | from pathlib import Path
8 |
9 | log = logging.getLogger('manspider.util')
10 |
11 |
12 | def str_to_list(s):
13 |
14 | l = set()
15 | # try to open as file
16 | try:
17 | with open(s) as f:
18 | lines = set([l.strip() for l in f.readlines()])
19 | for line in lines:
20 | if line:
21 | l.add(line)
22 | except OSError:
23 | l.add(s)
24 |
25 | return list(l)
26 |
27 |
28 | def make_targets(s):
29 | '''
30 | Accepts filename, CIDR, IP, hostname, file, or folder
31 | Returns list of targets as IPs, hostnames, or Path() objects
32 | '''
33 |
34 | targets = set()
35 |
36 | p = Path(s)
37 | if s.lower() == 'loot':
38 | targets.add(Path.home() / '.manspider' / 'loot')
39 |
40 | elif p.is_dir():
41 | targets.add(p)
42 |
43 | else:
44 | for i in str_to_list(s):
45 | try:
46 | for ip in ipaddress.ip_network(i, strict=False):
47 | targets.add(str(ip))
48 | except ValueError:
49 | targets.add(i)
50 |
51 | return list(targets)
52 |
53 |
54 | def human_to_int(h):
55 | '''
56 | converts human-readable number to integer
57 | e.g. 1K --> 1000
58 | '''
59 |
60 | if type(h) == int:
61 | return h
62 |
63 | units = {'': 1, 'K': 1024, 'M': 1024**2, 'G': 1024**3, 'T': 1024**4}
64 |
65 | try:
66 | h = h.upper().strip()
67 | i = float(''.join(c for c in h if c in string.digits + '.'))
68 | unit = ''.join([c for c in h if c in units.keys()])
69 | except (ValueError, KeyError):
70 | raise ValueError(f'Invalid filesize "{h}"')
71 |
72 | return int(i * units[unit])
73 |
74 |
75 | def bytes_to_human(_bytes):
76 | '''
77 | converts bytes to human-readable filesize
78 | e.g. 1024 --> 1KB
79 | '''
80 |
81 | sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB']
82 | units = {}
83 | count = 0
84 | for size in sizes:
85 | units[size] = pow(1024, count)
86 | count +=1
87 |
88 | for size in sizes:
89 | if abs(_bytes) < 1024.0:
90 | if size == sizes[0]:
91 | _bytes = str(int(_bytes))
92 | else:
93 | _bytes = '{:.2f}'.format(_bytes)
94 | return '{}{}'.format(_bytes, size)
95 | _bytes /= 1024
96 |
97 | raise ValueError
98 |
99 |
100 | def better_decode(b):
101 |
102 | # detect encoding with libmagic
103 | m = magic.Magic(mime_encoding=True)
104 | encoding = m.from_buffer(b)
105 |
106 | try:
107 | return b.decode(encoding)
108 | except Exception:
109 | return str(b)[2:-1]
110 |
111 |
112 | def random_string(length):
113 |
114 | return ''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits) for i in range(length))
115 |
116 |
117 | def list_files(path):
118 |
119 | path = Path(path)
120 |
121 | if path.is_file() and not path.is_symlink():
122 | yield path
123 |
124 | elif path.is_dir():
125 | for dir_name, dirnames, filenames in os.walk(path):
126 | for file in filenames:
127 | file = Path(dir_name) / file
128 | if file.is_file() and not file.is_symlink():
129 | yield file
130 |
131 |
132 | def rmdir(directory):
133 | '''
134 | Recursively remove directory
135 | '''
136 | directory = Path(directory)
137 | for item in directory.iterdir():
138 | if item.is_dir():
139 | rmdir(item)
140 | else:
141 | item.unlink()
142 | directory.rmdir()
--------------------------------------------------------------------------------
/man_spider/manspider.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | import pathlib
5 | import logging
6 | import argparse
7 | import traceback
8 | from time import sleep
9 | import multiprocessing
10 |
11 | from man_spider.lib import *
12 |
13 |
14 | # set up logging
15 | log = logging.getLogger('manspider')
16 | log.setLevel(logging.INFO)
17 |
18 |
19 | def go(options):
20 |
21 | log.info('MANSPIDER command executed: ' + ' '.join(sys.argv))
22 |
23 | try:
24 |
25 | # warn if --or-logic is enabled
26 | if options.or_logic and options.content and not all([type(t) == pathlib.PosixPath for t in options.targets]):
27 | log.warning('WARNING: "--or-logic" causes files to be content-searched even if filename/extension filters do not match!!')
28 | sleep(2)
29 |
30 | # exit if no filters were specified
31 | if not (options.filenames or options.extensions or options.exclude_extensions or options.content):
32 | log.error('Please specify at least one of --filenames, --content, --extensions, or --exclude-extensions')
33 | return
34 |
35 | # exit if --maxdepth is invalid
36 | if options.maxdepth <= 0:
37 | log.error('--maxdepth must be greater than zero')
38 | return
39 |
40 | log.info(f'Skipping files larger than {bytes_to_human(options.max_filesize)}')
41 | log.info(f'Using {options.threads:,} threads')
42 |
43 | manspider = MANSPIDER(options)
44 | manspider.start()
45 |
46 | except KeyboardInterrupt:
47 | log.critical('Interrupted')
48 |
49 | except Exception as e:
50 | if log.level <= logging.DEBUG:
51 | log.critical(traceback.format_exc())
52 | else:
53 | log.critical(f'Critical error (-v to debug): {e}')
54 |
55 | finally:
56 | # make sure temp files are cleaned up before exiting
57 | #rmdir(manspider.tmp_dir)
58 | pass
59 |
60 |
61 | def main():
62 |
63 | interrupted = False
64 |
65 | examples = '''
66 |
67 | # EXAMPLES
68 |
69 | Example 1: Search the network for filenames that may contain creds
70 | $ manspider 192.168.0.0/24 -f passw user admin account network login logon cred -d evilcorp -u bob -p Passw0rd
71 |
72 | Example 2: Search for XLSX files containing "password"
73 | $ manspider share.evilcorp.local -c password -e xlsx -d evilcorp -u bob -p Passw0rd
74 |
75 | Example 3: Search for interesting file extensions
76 | $ manspider share.evilcorp.local -e bat com vbs ps1 psd1 psm1 pem key rsa pub reg txt cfg conf config -d evilcorp -u bob -p Passw0rd
77 |
78 | Example 4: Search for finance-related files
79 | $ manspider share.evilcorp.local --dirnames bank financ payable payment reconcil remit voucher vendor eft swift -f '[0-9]{5,}' -d evilcorp -u bob -p Passw0rd
80 | '''
81 |
82 | parser = argparse.ArgumentParser(description='Scan for juicy data on SMB shares. Matching files and logs are stored in $HOME/.manspider. All filters are case-insensitive.')
83 | parser.add_argument('targets', nargs='+', type=make_targets, help='IPs, Hostnames, CIDR ranges, or files containing targets to spider (NOTE: local searching also supported, specify directory name or keyword "loot" to search downloaded files)')
84 | parser.add_argument('-u', '--username', default='', help='username for authentication')
85 | parser.add_argument('-p', '--password', default='', help='password for authentication')
86 | parser.add_argument('-d', '--domain', default='', help='domain for authentication')
87 | parser.add_argument('-l','--loot-dir', default='', help='loot directory (default ~/.manspider/)')
88 | parser.add_argument('-m', '--maxdepth', type=int, default=10, help='maximum depth to spider (default: 10)')
89 | parser.add_argument('-H', '--hash', default='', help='NTLM hash for authentication')
90 | parser.add_argument('-k', '--kerberos', action='store_true', help='Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters')
91 | parser.add_argument('-aesKey', '--aes-key', action='store', metavar='HEX', help='AES key to use for Kerberos Authentication (128 or 256 bits)')
92 | parser.add_argument('-dc-ip', '--dc-ip', action='store', metavar='IP', help='IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter')
93 | parser.add_argument('-t', '--threads', type=int, default=5, help='concurrent threads (default: 5)')
94 | parser.add_argument('-f', '--filenames', nargs='+', default=[], help=f'filter filenames using regex (space-separated)', metavar='REGEX')
95 | parser.add_argument('-e', '--extensions',nargs='+', default=[], help='only show filenames with these extensions (space-separated, e.g. `docx xlsx` for only word & excel docs)', metavar='EXT')
96 | parser.add_argument('--exclude-extensions',nargs='+', default=[], help='ignore files with these extensions', metavar='EXT')
97 | parser.add_argument('-c', '--content', nargs='+', default=[], help='search for file content using regex (multiple supported)', metavar='REGEX')
98 | parser.add_argument('--sharenames', nargs='+', default=[], help='only search shares with these names (multiple supported)', metavar='SHARE')
99 | parser.add_argument('--exclude-sharenames', nargs='*', default=['IPC$', 'C$', 'ADMIN$', 'PRINT$'],help='don\'t search shares with these names (multiple supported)', metavar='SHARE')
100 | parser.add_argument('--dirnames', nargs='+', default=[], help='only search directories containing these strings (multiple supported)', metavar='DIR')
101 | parser.add_argument('--exclude-dirnames', nargs='+', default=[], help='don\'t search directories containing these strings (multiple supported)', metavar='DIR')
102 | parser.add_argument('-q', '--quiet', action='store_true', help='don\'t display matching file content')
103 | parser.add_argument('-n', '--no-download', action='store_true', help='don\'t download matching files')
104 | parser.add_argument('-mfail', '--max-failed-logons', type=int, help='limit failed logons', metavar='INT')
105 | parser.add_argument('-o', '--or-logic', action='store_true', help=f'use OR logic instead of AND (files are downloaded if filename OR extension OR content match)')
106 | parser.add_argument('-s', '--max-filesize', type=human_to_int, default=human_to_int('10M'), help=f'don\'t retrieve files over this size, e.g. "500K" or ".5M" (default: 10M)', metavar='SIZE')
107 | parser.add_argument('-v', '--verbose', action='store_true', help='show debugging messages')
108 |
109 |
110 | syntax_error = False
111 | try:
112 |
113 | if len(sys.argv) == 1:
114 | parser.print_help()
115 | sys.exit(1)
116 |
117 | options = parser.parse_args()
118 |
119 | if options.verbose:
120 | log.setLevel('DEBUG')
121 |
122 | if options.kerberos and not "KRB5CCNAME" in os.environ:
123 | log.error("KRB5CCNAME is not set in the environment")
124 | sys.exit(1)
125 |
126 | # make sure extension formats are valid
127 | for i, extension in enumerate(options.extensions):
128 | if extension and not extension.startswith('.'):
129 | extension = f'.{extension}'
130 | options.extensions[i] = extension.lower()
131 |
132 | # make sure extension blacklist is valid
133 | for i, extension in enumerate(options.exclude_extensions):
134 | if not extension.startswith('.'):
135 | extension = f'.{extension}'
136 | options.exclude_extensions[i] = extension.lower()
137 |
138 | # lowercase share names
139 | options.sharenames = [s.lower() for s in options.sharenames]
140 | options.exclude_sharenames = [s.lower() for s in options.exclude_sharenames]
141 |
142 | # lowercase directory names
143 | options.dirnames = [s.lower() for s in options.dirnames]
144 | options.exclude_dirnames = [s.lower() for s in options.exclude_dirnames]
145 |
146 | # deduplicate targets
147 | targets = set()
148 | [[targets.add(t) for t in g] for g in options.targets]
149 | options.targets = list(targets)
150 |
151 | p = multiprocessing.Process(target=go, args=(options,), daemon=False)
152 | p.start()
153 | listener.start()
154 |
155 | except argparse.ArgumentError as e:
156 | syntax_error = True
157 | log.error(e)
158 | log.error('Check your syntax')
159 | sys.exit(2)
160 |
161 | except KeyboardInterrupt:
162 | log.critical('Interrupted')
163 | sys.exit(1)
164 |
165 | # pretty format all errors if we're not debugging
166 | except Exception as e:
167 | if log.level <= logging.DEBUG:
168 | log.critical(traceback.format_exc())
169 | else:
170 | log.critical(f'Critical error (-v to debug): {e}')
171 |
172 | finally:
173 | if '-h' in sys.argv or '--help' in sys.argv or len(sys.argv) == 1 or syntax_error:
174 | print(examples)
175 | sleep(1)
176 | try:
177 | # wait for go to finish
178 | p.join()
179 | except:
180 | pass
181 | try:
182 | # stop the log listener
183 | listener.stop()
184 | except:
185 | pass
186 |
187 |
188 | if __name__ == '__main__':
189 | main()
--------------------------------------------------------------------------------
/manspider.sh:
--------------------------------------------------------------------------------
1 | docker run -v ./loot:/root/.manspider/loot -v ./logs:/root/.manspider/logs blacklanternsecurity/manspider "$@"
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
2 |
3 | [[package]]
4 | name = "blinker"
5 | version = "1.8.2"
6 | description = "Fast, simple object-to-object and broadcast signaling"
7 | optional = false
8 | python-versions = ">=3.8"
9 | groups = ["main"]
10 | files = [
11 | {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"},
12 | {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"},
13 | ]
14 |
15 | [[package]]
16 | name = "cffi"
17 | version = "1.17.1"
18 | description = "Foreign Function Interface for Python calling C code."
19 | optional = false
20 | python-versions = ">=3.8"
21 | groups = ["main"]
22 | markers = "platform_python_implementation != \"PyPy\""
23 | files = [
24 | {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
25 | {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
26 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
27 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
28 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
29 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
30 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
31 | {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
32 | {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
33 | {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
34 | {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
35 | {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
36 | {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
37 | {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
38 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
39 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
40 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
41 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
42 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
43 | {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
44 | {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
45 | {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
46 | {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
47 | {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
48 | {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
49 | {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
50 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
51 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
52 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
53 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
54 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
55 | {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
56 | {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
57 | {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
58 | {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
59 | {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
60 | {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
61 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
62 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
63 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
64 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
65 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
66 | {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
67 | {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
68 | {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
69 | {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
70 | {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
71 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
72 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
73 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
74 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
75 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
76 | {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
77 | {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
78 | {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
79 | {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
80 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
81 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
82 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
83 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
84 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
85 | {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
86 | {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
87 | {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
88 | {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
89 | {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
90 | {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
91 | ]
92 |
93 | [package.dependencies]
94 | pycparser = "*"
95 |
96 | [[package]]
97 | name = "charset-normalizer"
98 | version = "3.4.2"
99 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
100 | optional = false
101 | python-versions = ">=3.7"
102 | groups = ["main"]
103 | files = [
104 | {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"},
105 | {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"},
106 | {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"},
107 | {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"},
108 | {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"},
109 | {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"},
110 | {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"},
111 | {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"},
112 | {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"},
113 | {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"},
114 | {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"},
115 | {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"},
116 | {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"},
117 | {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"},
118 | {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"},
119 | {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"},
120 | {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"},
121 | {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"},
122 | {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"},
123 | {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"},
124 | {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"},
125 | {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"},
126 | {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"},
127 | {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"},
128 | {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"},
129 | {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"},
130 | {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"},
131 | {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"},
132 | {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"},
133 | {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"},
134 | {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"},
135 | {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"},
136 | {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"},
137 | {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"},
138 | {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"},
139 | {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"},
140 | {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"},
141 | {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"},
142 | {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"},
143 | {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"},
144 | {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"},
145 | {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"},
146 | {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"},
147 | {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"},
148 | {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"},
149 | {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"},
150 | {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"},
151 | {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"},
152 | {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"},
153 | {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"},
154 | {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"},
155 | {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"},
156 | {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"},
157 | {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"},
158 | {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"},
159 | {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"},
160 | {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"},
161 | {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"},
162 | {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"},
163 | {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"},
164 | {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"},
165 | {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"},
166 | {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"},
167 | {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"},
168 | {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"},
169 | {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"},
170 | {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"},
171 | {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"},
172 | {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"},
173 | {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"},
174 | {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"},
175 | {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"},
176 | {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"},
177 | {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"},
178 | {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"},
179 | {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"},
180 | {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"},
181 | {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"},
182 | {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"},
183 | {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"},
184 | {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"},
185 | {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"},
186 | {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"},
187 | {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"},
188 | {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"},
189 | {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"},
190 | {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"},
191 | {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"},
192 | {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"},
193 | {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"},
194 | {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"},
195 | {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"},
196 | ]
197 |
198 | [[package]]
199 | name = "click"
200 | version = "8.1.8"
201 | description = "Composable command line interface toolkit"
202 | optional = false
203 | python-versions = ">=3.7"
204 | groups = ["main"]
205 | files = [
206 | {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
207 | {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
208 | ]
209 |
210 | [package.dependencies]
211 | colorama = {version = "*", markers = "platform_system == \"Windows\""}
212 |
213 | [[package]]
214 | name = "colorama"
215 | version = "0.4.6"
216 | description = "Cross-platform colored terminal text."
217 | optional = false
218 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
219 | groups = ["main"]
220 | markers = "platform_system == \"Windows\""
221 | files = [
222 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
223 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
224 | ]
225 |
226 | [[package]]
227 | name = "cryptography"
228 | version = "42.0.8"
229 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
230 | optional = false
231 | python-versions = ">=3.7"
232 | groups = ["main"]
233 | files = [
234 | {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"},
235 | {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"},
236 | {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"},
237 | {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"},
238 | {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"},
239 | {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"},
240 | {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"},
241 | {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"},
242 | {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"},
243 | {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"},
244 | {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"},
245 | {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"},
246 | {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"},
247 | {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"},
248 | {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"},
249 | {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"},
250 | {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"},
251 | {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"},
252 | {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"},
253 | {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"},
254 | {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"},
255 | {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"},
256 | {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"},
257 | {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"},
258 | {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"},
259 | {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"},
260 | {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"},
261 | {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"},
262 | {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"},
263 | {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"},
264 | {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"},
265 | {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"},
266 | ]
267 |
268 | [package.dependencies]
269 | cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
270 |
271 | [package.extras]
272 | docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
273 | docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
274 | nox = ["nox"]
275 | pep8test = ["check-sdist", "click", "mypy", "ruff"]
276 | sdist = ["build"]
277 | ssh = ["bcrypt (>=3.1.5)"]
278 | test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
279 | test-randomorder = ["pytest-randomly"]
280 |
281 | [[package]]
282 | name = "dnspython"
283 | version = "2.6.1"
284 | description = "DNS toolkit"
285 | optional = false
286 | python-versions = ">=3.8"
287 | groups = ["main"]
288 | files = [
289 | {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"},
290 | {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"},
291 | ]
292 |
293 | [package.extras]
294 | dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"]
295 | dnssec = ["cryptography (>=41)"]
296 | doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"]
297 | doq = ["aioquic (>=0.9.25)"]
298 | idna = ["idna (>=3.6)"]
299 | trio = ["trio (>=0.23)"]
300 | wmi = ["wmi (>=1.5.1)"]
301 |
302 | [[package]]
303 | name = "extractous"
304 | version = "0.3.0"
305 | description = "Extractous Python Binding"
306 | optional = false
307 | python-versions = "<3.14,>=3.8"
308 | groups = ["main"]
309 | files = [
310 | {file = "extractous-0.3.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9f513d8da78b74ab655e659c64e287ddd653009f5af011cef0d2c8467a931e38"},
311 | {file = "extractous-0.3.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6e43d9dee26e7cb322b6f9059f3e3565252f444b80d2f4ff2007a002fdd00727"},
312 | {file = "extractous-0.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:01ef136a5901aed0e813f747989bbeaff60e4777cc77b933bef8f10de80b7dfd"},
313 | {file = "extractous-0.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:d86754320beba736f33c10227a641cce473cfb690c9e2d5ae823c917110c98ab"},
314 | {file = "extractous-0.3.0.tar.gz", hash = "sha256:ebccc7778137d6be7680660f1e880e8a1898dedca7f64ab4b354a68d22359e84"},
315 | ]
316 |
317 | [package.extras]
318 | docs = ["pdoc"]
319 | test = ["pytest", "scikit-learn"]
320 |
321 | [[package]]
322 | name = "flask"
323 | version = "3.0.3"
324 | description = "A simple framework for building complex web applications."
325 | optional = false
326 | python-versions = ">=3.8"
327 | groups = ["main"]
328 | files = [
329 | {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"},
330 | {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"},
331 | ]
332 |
333 | [package.dependencies]
334 | blinker = ">=1.6.2"
335 | click = ">=8.1.3"
336 | importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
337 | itsdangerous = ">=2.1.2"
338 | Jinja2 = ">=3.1.2"
339 | Werkzeug = ">=3.0.0"
340 |
341 | [package.extras]
342 | async = ["asgiref (>=3.2)"]
343 | dotenv = ["python-dotenv"]
344 |
345 | [[package]]
346 | name = "impacket"
347 | version = "0.12.0"
348 | description = "Network protocols Constructors and Dissectors"
349 | optional = false
350 | python-versions = "*"
351 | groups = ["main"]
352 | files = [
353 | {file = "impacket-0.12.0.tar.gz", hash = "sha256:89587d1b836a5220d74848c934757962b382886dca8b1b4a0c44d693f2600643"},
354 | ]
355 |
356 | [package.dependencies]
357 | charset_normalizer = "*"
358 | flask = ">=1.0"
359 | ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6"
360 | ldapdomaindump = ">=0.9.0"
361 | pyasn1 = ">=0.2.3"
362 | pyasn1_modules = "*"
363 | pycryptodomex = "*"
364 | pyOpenSSL = "24.0.0"
365 | pyreadline3 = {version = "*", markers = "sys_platform == \"win32\""}
366 | setuptools = "*"
367 | six = "*"
368 |
369 | [[package]]
370 | name = "importlib-metadata"
371 | version = "8.5.0"
372 | description = "Read metadata from Python packages"
373 | optional = false
374 | python-versions = ">=3.8"
375 | groups = ["main"]
376 | markers = "python_version < \"3.10\""
377 | files = [
378 | {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"},
379 | {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"},
380 | ]
381 |
382 | [package.dependencies]
383 | zipp = ">=3.20"
384 |
385 | [package.extras]
386 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
387 | cover = ["pytest-cov"]
388 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
389 | enabler = ["pytest-enabler (>=2.2)"]
390 | perf = ["ipython"]
391 | test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
392 | type = ["pytest-mypy"]
393 |
394 | [[package]]
395 | name = "itsdangerous"
396 | version = "2.2.0"
397 | description = "Safely pass data to untrusted environments and back."
398 | optional = false
399 | python-versions = ">=3.8"
400 | groups = ["main"]
401 | files = [
402 | {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
403 | {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
404 | ]
405 |
406 | [[package]]
407 | name = "jinja2"
408 | version = "3.1.6"
409 | description = "A very fast and expressive template engine."
410 | optional = false
411 | python-versions = ">=3.7"
412 | groups = ["main"]
413 | files = [
414 | {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
415 | {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
416 | ]
417 |
418 | [package.dependencies]
419 | MarkupSafe = ">=2.0"
420 |
421 | [package.extras]
422 | i18n = ["Babel (>=2.7)"]
423 |
424 | [[package]]
425 | name = "ldap3"
426 | version = "2.9.1"
427 | description = "A strictly RFC 4510 conforming LDAP V3 pure Python client library"
428 | optional = false
429 | python-versions = "*"
430 | groups = ["main"]
431 | files = [
432 | {file = "ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70"},
433 | {file = "ldap3-2.9.1.tar.gz", hash = "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f"},
434 | ]
435 |
436 | [package.dependencies]
437 | pyasn1 = ">=0.4.6"
438 |
439 | [[package]]
440 | name = "ldapdomaindump"
441 | version = "0.10.0"
442 | description = "Active Directory information dumper via LDAP"
443 | optional = false
444 | python-versions = ">=3.6"
445 | groups = ["main"]
446 | files = [
447 | {file = "ldapdomaindump-0.10.0-py3-none-any.whl", hash = "sha256:3797259596df7a5e1fda98388c96b1d94196f5da5551f1af1aaeedda0c9f5a11"},
448 | {file = "ldapdomaindump-0.10.0.tar.gz", hash = "sha256:cbc66b32a7787473ffd169c5319acde46c02fdc9d444556e6448e0def91d3299"},
449 | ]
450 |
451 | [package.dependencies]
452 | dnspython = "*"
453 | ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6"
454 |
455 | [[package]]
456 | name = "markupsafe"
457 | version = "2.1.5"
458 | description = "Safely add untrusted strings to HTML/XML markup."
459 | optional = false
460 | python-versions = ">=3.7"
461 | groups = ["main"]
462 | files = [
463 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
464 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
465 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"},
466 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"},
467 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"},
468 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"},
469 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"},
470 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"},
471 | {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"},
472 | {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"},
473 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"},
474 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"},
475 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"},
476 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"},
477 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"},
478 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"},
479 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"},
480 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"},
481 | {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"},
482 | {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"},
483 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"},
484 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"},
485 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"},
486 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"},
487 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"},
488 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"},
489 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"},
490 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"},
491 | {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"},
492 | {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"},
493 | {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"},
494 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"},
495 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"},
496 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"},
497 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"},
498 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"},
499 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"},
500 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"},
501 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"},
502 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"},
503 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"},
504 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"},
505 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"},
506 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"},
507 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"},
508 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"},
509 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"},
510 | {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"},
511 | {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"},
512 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"},
513 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"},
514 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"},
515 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"},
516 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"},
517 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"},
518 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"},
519 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"},
520 | {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"},
521 | {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"},
522 | {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
523 | ]
524 |
525 | [[package]]
526 | name = "pyasn1"
527 | version = "0.6.1"
528 | description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
529 | optional = false
530 | python-versions = ">=3.8"
531 | groups = ["main"]
532 | files = [
533 | {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"},
534 | {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"},
535 | ]
536 |
537 | [[package]]
538 | name = "pyasn1-modules"
539 | version = "0.4.2"
540 | description = "A collection of ASN.1-based protocols modules"
541 | optional = false
542 | python-versions = ">=3.8"
543 | groups = ["main"]
544 | files = [
545 | {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"},
546 | {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"},
547 | ]
548 |
549 | [package.dependencies]
550 | pyasn1 = ">=0.6.1,<0.7.0"
551 |
552 | [[package]]
553 | name = "pycparser"
554 | version = "2.22"
555 | description = "C parser in Python"
556 | optional = false
557 | python-versions = ">=3.8"
558 | groups = ["main"]
559 | markers = "platform_python_implementation != \"PyPy\""
560 | files = [
561 | {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
562 | {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
563 | ]
564 |
565 | [[package]]
566 | name = "pycryptodomex"
567 | version = "3.23.0"
568 | description = "Cryptographic library for Python"
569 | optional = false
570 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
571 | groups = ["main"]
572 | files = [
573 | {file = "pycryptodomex-3.23.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:add243d204e125f189819db65eed55e6b4713f70a7e9576c043178656529cec7"},
574 | {file = "pycryptodomex-3.23.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1c6d919fc8429e5cb228ba8c0d4d03d202a560b421c14867a65f6042990adc8e"},
575 | {file = "pycryptodomex-3.23.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1c3a65ad441746b250d781910d26b7ed0a396733c6f2dbc3327bd7051ec8a541"},
576 | {file = "pycryptodomex-3.23.0-cp27-cp27m-win32.whl", hash = "sha256:47f6d318fe864d02d5e59a20a18834819596c4ed1d3c917801b22b92b3ffa648"},
577 | {file = "pycryptodomex-3.23.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:d9825410197a97685d6a1fa2a86196430b01877d64458a20e95d4fd00d739a08"},
578 | {file = "pycryptodomex-3.23.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:267a3038f87a8565bd834317dbf053a02055915acf353bf42ededb9edaf72010"},
579 | {file = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7b37e08e3871efe2187bc1fd9320cc81d87caf19816c648f24443483005ff886"},
580 | {file = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:91979028227543010d7b2ba2471cf1d1e398b3f183cb105ac584df0c36dac28d"},
581 | {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8962204c47464d5c1c4038abeadd4514a133b28748bcd9fa5b6d62e3cec6fa"},
582 | {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a33986a0066860f7fcf7c7bd2bc804fa90e434183645595ae7b33d01f3c91ed8"},
583 | {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7947ab8d589e3178da3d7cdeabe14f841b391e17046954f2fbcd941705762b5"},
584 | {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c25e30a20e1b426e1f0fa00131c516f16e474204eee1139d1603e132acffc314"},
585 | {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:da4fa650cef02db88c2b98acc5434461e027dce0ae8c22dd5a69013eaf510006"},
586 | {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58b851b9effd0d072d4ca2e4542bf2a4abcf13c82a29fd2c93ce27ee2a2e9462"},
587 | {file = "pycryptodomex-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:a9d446e844f08299236780f2efa9898c818fe7e02f17263866b8550c7d5fb328"},
588 | {file = "pycryptodomex-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bc65bdd9fc8de7a35a74cab1c898cab391a4add33a8fe740bda00f5976ca4708"},
589 | {file = "pycryptodomex-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c885da45e70139464f082018ac527fdaad26f1657a99ee13eecdce0f0ca24ab4"},
590 | {file = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6"},
591 | {file = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545"},
592 | {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587"},
593 | {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c"},
594 | {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c"},
595 | {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003"},
596 | {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744"},
597 | {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd"},
598 | {file = "pycryptodomex-3.23.0-cp37-abi3-win32.whl", hash = "sha256:189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c"},
599 | {file = "pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9"},
600 | {file = "pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51"},
601 | {file = "pycryptodomex-3.23.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:febec69c0291efd056c65691b6d9a339f8b4bc43c6635b8699471248fe897fea"},
602 | {file = "pycryptodomex-3.23.0-pp27-pypy_73-win32.whl", hash = "sha256:c84b239a1f4ec62e9c789aafe0543f0594f0acd90c8d9e15bcece3efe55eca66"},
603 | {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ebfff755c360d674306e5891c564a274a47953562b42fb74a5c25b8fc1fb1cb5"},
604 | {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eca54f4bb349d45afc17e3011ed4264ef1cc9e266699874cdd1349c504e64798"},
605 | {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2596e643d4365e14d0879dc5aafe6355616c61c2176009270f3048f6d9a61f"},
606 | {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdfac7cda115bca3a5abb2f9e43bc2fb66c2b65ab074913643803ca7083a79ea"},
607 | {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:14c37aaece158d0ace436f76a7bb19093db3b4deade9797abfc39ec6cd6cc2fe"},
608 | {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7de1e40a41a5d7f1ac42b6569b10bcdded34339950945948529067d8426d2785"},
609 | {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bffc92138d75664b6d543984db7893a628559b9e78658563b0395e2a5fb47ed9"},
610 | {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df027262368334552db2c0ce39706b3fb32022d1dce34673d0f9422df004b96a"},
611 | {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e79f1aaff5a3a374e92eb462fa9e598585452135012e2945f96874ca6eeb1ff"},
612 | {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:27e13c80ac9a0a1d050ef0a7e0a18cc04c8850101ec891815b6c5a0375e8a245"},
613 | {file = "pycryptodomex-3.23.0.tar.gz", hash = "sha256:71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da"},
614 | ]
615 |
616 | [[package]]
617 | name = "pyopenssl"
618 | version = "24.0.0"
619 | description = "Python wrapper module around the OpenSSL library"
620 | optional = false
621 | python-versions = ">=3.7"
622 | groups = ["main"]
623 | files = [
624 | {file = "pyOpenSSL-24.0.0-py3-none-any.whl", hash = "sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3"},
625 | {file = "pyOpenSSL-24.0.0.tar.gz", hash = "sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf"},
626 | ]
627 |
628 | [package.dependencies]
629 | cryptography = ">=41.0.5,<43"
630 |
631 | [package.extras]
632 | docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"]
633 | test = ["flaky", "pretend", "pytest (>=3.0.1)"]
634 |
635 | [[package]]
636 | name = "pyreadline3"
637 | version = "3.5.4"
638 | description = "A python implementation of GNU readline."
639 | optional = false
640 | python-versions = ">=3.8"
641 | groups = ["main"]
642 | markers = "sys_platform == \"win32\""
643 | files = [
644 | {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"},
645 | {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"},
646 | ]
647 |
648 | [package.extras]
649 | dev = ["build", "flake8", "mypy", "pytest", "twine"]
650 |
651 | [[package]]
652 | name = "python-magic"
653 | version = "0.4.27"
654 | description = "File type identification using libmagic"
655 | optional = false
656 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
657 | groups = ["main"]
658 | files = [
659 | {file = "python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b"},
660 | {file = "python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"},
661 | ]
662 |
663 | [[package]]
664 | name = "setuptools"
665 | version = "75.3.2"
666 | description = "Easily download, build, install, upgrade, and uninstall Python packages"
667 | optional = false
668 | python-versions = ">=3.8"
669 | groups = ["main"]
670 | files = [
671 | {file = "setuptools-75.3.2-py3-none-any.whl", hash = "sha256:90ab613b6583fc02d5369cbca13ea26ea0e182d1df2d943ee9cbe81d4c61add9"},
672 | {file = "setuptools-75.3.2.tar.gz", hash = "sha256:3c1383e1038b68556a382c1e8ded8887cd20141b0eb5708a6c8d277de49364f5"},
673 | ]
674 |
675 | [package.extras]
676 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.5.2) ; sys_platform != \"cygwin\""]
677 | core = ["importlib-metadata (>=6) ; python_version < \"3.10\"", "importlib-resources (>=5.10.2) ; python_version < \"3.9\"", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"]
678 | cover = ["pytest-cov"]
679 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
680 | enabler = ["pytest-enabler (>=2.2)"]
681 | test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "ruff (<=0.7.1)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
682 | type = ["importlib-metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.12.*)", "pytest-mypy"]
683 |
684 | [[package]]
685 | name = "six"
686 | version = "1.17.0"
687 | description = "Python 2 and 3 compatibility utilities"
688 | optional = false
689 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
690 | groups = ["main"]
691 | files = [
692 | {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
693 | {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
694 | ]
695 |
696 | [[package]]
697 | name = "werkzeug"
698 | version = "3.0.6"
699 | description = "The comprehensive WSGI web application library."
700 | optional = false
701 | python-versions = ">=3.8"
702 | groups = ["main"]
703 | files = [
704 | {file = "werkzeug-3.0.6-py3-none-any.whl", hash = "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17"},
705 | {file = "werkzeug-3.0.6.tar.gz", hash = "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d"},
706 | ]
707 |
708 | [package.dependencies]
709 | MarkupSafe = ">=2.1.1"
710 |
711 | [package.extras]
712 | watchdog = ["watchdog (>=2.3)"]
713 |
714 | [[package]]
715 | name = "zipp"
716 | version = "3.20.2"
717 | description = "Backport of pathlib-compatible object wrapper for zip files"
718 | optional = false
719 | python-versions = ">=3.8"
720 | groups = ["main"]
721 | markers = "python_version < \"3.10\""
722 | files = [
723 | {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"},
724 | {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"},
725 | ]
726 |
727 | [package.extras]
728 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
729 | cover = ["pytest-cov"]
730 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
731 | enabler = ["pytest-enabler (>=2.2)"]
732 | test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
733 | type = ["pytest-mypy"]
734 |
735 | [metadata]
736 | lock-version = "2.1"
737 | python-versions = ">=3.8,<3.14"
738 | content-hash = "b31ad3917f22c5a9833891c57b80a023f27225af5e4cacedcada44e0406f0e0d"
739 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "man-spider"
3 | version = "1.1.2"
4 | description = "Full-featured SMB spider capable of searching file content"
5 | authors = [
6 | {name = "TheTechromancer"}
7 | ]
8 | license = {text = "GPL-3.0"}
9 | readme = "README.md"
10 | requires-python = ">=3.8,<3.14"
11 | dependencies = [
12 | "extractous (>=0.3.0,<0.4.0)",
13 | "impacket (>=0.12.0,<0.13.0)",
14 | "python-magic (>=0.4.27,<0.5.0)"
15 | ]
16 | repository = "https://github.com/blacklanternsecurity/MANSPIDER"
17 | homepage = "https://github.com/blacklanternsecurity/MANSPIDER"
18 |
19 | [tool.poetry.scripts]
20 | manspider = 'man_spider.manspider:main'
21 |
22 | [build-system]
23 | requires = ["poetry-core>=2.0.0,<3.0.0"]
24 | build-backend = "poetry.core.masonry.api"
25 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | blinker==1.8.2 ; python_version >= "3.8" and python_version < "3.14"
2 | cffi==1.17.1 ; python_version >= "3.8" and python_version < "3.14" and platform_python_implementation != "PyPy"
3 | charset-normalizer==3.4.2 ; python_version >= "3.8" and python_version < "3.14"
4 | click==8.1.8 ; python_version >= "3.8" and python_version < "3.14"
5 | colorama==0.4.6 ; python_version >= "3.8" and python_version < "3.14" and platform_system == "Windows"
6 | cryptography==42.0.8 ; python_version >= "3.8" and python_version < "3.14"
7 | dnspython==2.6.1 ; python_version >= "3.8" and python_version < "3.14"
8 | extractous==0.3.0 ; python_version >= "3.8" and python_version < "3.14"
9 | flask==3.0.3 ; python_version >= "3.8" and python_version < "3.14"
10 | impacket==0.12.0 ; python_version >= "3.8" and python_version < "3.14"
11 | importlib-metadata==8.5.0 ; python_version >= "3.8" and python_version < "3.10"
12 | itsdangerous==2.2.0 ; python_version >= "3.8" and python_version < "3.14"
13 | jinja2==3.1.6 ; python_version >= "3.8" and python_version < "3.14"
14 | ldap3==2.9.1 ; python_version >= "3.8" and python_version < "3.14"
15 | ldapdomaindump==0.10.0 ; python_version >= "3.8" and python_version < "3.14"
16 | markupsafe==2.1.5 ; python_version >= "3.8" and python_version < "3.14"
17 | pyasn1-modules==0.4.2 ; python_version >= "3.8" and python_version < "3.14"
18 | pyasn1==0.6.1 ; python_version >= "3.8" and python_version < "3.14"
19 | pycparser==2.22 ; python_version >= "3.8" and python_version < "3.14" and platform_python_implementation != "PyPy"
20 | pycryptodomex==3.23.0 ; python_version >= "3.8" and python_version < "3.14"
21 | pyopenssl==24.0.0 ; python_version >= "3.8" and python_version < "3.14"
22 | pyreadline3==3.5.4 ; python_version >= "3.8" and python_version < "3.14" and sys_platform == "win32"
23 | python-magic==0.4.27 ; python_version >= "3.8" and python_version < "3.14"
24 | setuptools==78.1.1 ; python_version >= "3.8" and python_version < "3.14"
25 | six==1.17.0 ; python_version >= "3.8" and python_version < "3.14"
26 | werkzeug==3.0.6 ; python_version >= "3.8" and python_version < "3.14"
27 | zipp==3.20.2 ; python_version >= "3.8" and python_version < "3.10"
28 |
--------------------------------------------------------------------------------