From 0af8465d1b14b45ff7cc51c989d5ef5e981fb435 Mon Sep 17 00:00:00 2001 From: hackish Date: Mon, 4 Dec 2023 14:09:05 -0800 Subject: [PATCH] move to git --- .flake8 | 5 + .gitignore | 161 +++++++++ .pre-commit-config.yaml | 28 ++ LICENSE | 674 +++++++++++++++++++++++++++++++++++ README.md | 51 +++ drawbridge.png | Bin 0 -> 44981 bytes drawbridge/__init__.py | 40 +++ drawbridge/drawbridge.py | 65 ++++ drawbridge/net_queue.py | 134 +++++++ drawbridge/utils/__init__.py | 0 drawbridge/utils/logger.py | 35 ++ drawbridge/utils/lookup.py | 33 ++ examples/chat.html | 65 ++++ examples/hijack.py | 70 ++++ examples/server.py | 47 +++ pyproject.toml | 28 ++ 16 files changed, 1436 insertions(+) create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 drawbridge.png create mode 100644 drawbridge/__init__.py create mode 100644 drawbridge/drawbridge.py create mode 100644 drawbridge/net_queue.py create mode 100644 drawbridge/utils/__init__.py create mode 100644 drawbridge/utils/logger.py create mode 100644 drawbridge/utils/lookup.py create mode 100644 examples/chat.html create mode 100644 examples/hijack.py create mode 100644 examples/server.py create mode 100644 pyproject.toml diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..42361f6 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +max-line-length = 160 +exclude = docs/*, .git, __pycache__, build +per-file-ignores = + __init__.py: F401 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e05e2e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,161 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6ed3078 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,28 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files +- repo: https://github.com/asottile/reorder_python_imports + rev: v3.9.0 + hooks: + - id: reorder-python-imports + args: [--application-directories, '.:drawbridge', --py39-plus] +- repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black + language_version: python3.11 +- repo: https://github.com/PyCQA/bandit + rev: 1.7.5 + hooks: + - id: bandit + args: ["-c", "pyproject.toml"] + additional_dependencies: ["bandit[toml]"] +- repo: https://github.com/pycqa/flake8 + rev: 6.0.0 + hooks: + - id: flake8 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cd96cbc --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +
+drawbridge Logo + +# drawbridge + +**drawbridge** simplifies local nfqueue queues + +without sacrificing performance?
+ +[Installation](#installation) • +[Examples](#examples) • +[Contributing](#contributing) • +[License](#license) +
+ +## Installation + +### with pip +Eventually, install with `pip install drawbridge`, maybe. + +For now, clone the repo, navigate to it, and run `pip install .`. You'll need a Linux system for nfqueue. + +## Examples + +See the examples directory for a WebSocket example. + +```python +from drawbridge import DrawBridge + +def my_packet_handler(raw_packet): + # do things to the raw packet, like + # from scapy.all import * + # pkt = IP(raw_packet) + # ... + # return bytes(pkt) + return raw_packet + +db = DrawBridge() +db.add_queue(my_packet_handler, src_port=80) +db.run() +``` + +## Contributing + +If you would like to contribute to this project, feel free to submit a pull request or open an issue on GitHub. + +This tool was written as part of my coursework for INFA 735 - Offensive Security at Dakota State University. Consequently, I may choose not to maintain this tool beyond the length of the course, but have selected a license that enables open contributions in any case. + +## License + +This project is licensed under the MIT License. See the `LICENSE` file for details. diff --git a/drawbridge.png b/drawbridge.png new file mode 100644 index 0000000000000000000000000000000000000000..3c624f384c5a507150660af4a8c104595da59e8a GIT binary patch literal 44981 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 K*+c*~89I8?cxBr5-B4ud+71Bm-Vahs zCS{8CJLlk-^#N2VYkVeTfV<6v5L3+)ffVdL1?(%eDQgR<&81JX@F=Be3*mnhr<@C+rZ4CexV#ak{pEG`Hm1AZbx7)2n14Z*=yr|!0UalZLj zWTU;}gj%GUuJ2XA0L#iHmjp_Q^l$$={`ze4B+pM#MDz3a*4Iz-dOW-Hc2VE`FaMK& z|3CPb|E1skonQF_|Hxmt`1Cuzxyb~aK32D`UESVXa>2_<1+&psD>#lAUEbWn_~5#V z!8e;-+qTEYhd2Vu_3Cm#(rl*mcMvjL%$Gul5YcwmHI0&aK9FR!Qd~+OV;Ux$Dw!ya z26pMk@D-Ixav5CL#lNRG9E`#ia|c#?db?_MuIZnfLX?&VqSt?4(NQCfmIS5|=F zizXtsUOd~*pO;lJJ$y*{K9CYnyS;gBR=0%d_3|1)rX`#S8s?FC)wi5yj0p&Xf%9%)k;NXm>2oIki0Us0z$>hSiuE!J&W3zDz{1ez|n7f5Sic1FEx5q=T6BaB> zT^L}(A9gvzB>jP)a)wdbVDL6L@onM$^+RVAqOjK1kz`{Wrl1b9fQDtTXpkxyMX9wI zlOZyKw+|RzlQuddELEA|MM+diEjpYNihyANyywCNKE?OnIsEOr`C}47*KQorqw%P$ zGW_!8|GK{V)HK)i^sw)g(G6w7ri{+&u5a3|={y3EuhSO6!!@*TaR)(MWE{{w9^^BN zs0bLUhbVpWm@?f1{@cPnh*IWVuN6oKwAXqlD)(Ivi5kLC&JXFvL$LWkwuR-4DdS4c zT!_5G;8RLHDr^{b{gVlW$bx%u5Q$P?5YvZD(kc$)m(#4Qpmp*YXHfit-% z(y%V$lAnzX$0J6=x0K#3We(0k)-fc95f&5v9x@OBDj^z@LJnFn`~{*e6%3`)ZlX)T zy$cd1ArNDyAr6dEctfA&uq8P&;(*ho8xo6#2^ko}W_C~f?8EVw_v-`VK@W}s{~rJc VQq#Yg3uOQR002ovPDHLkV1hZt8Ib@0 literal 0 HcmV?d00001 diff --git a/drawbridge/__init__.py b/drawbridge/__init__.py new file mode 100644 index 0000000..389584f --- /dev/null +++ b/drawbridge/__init__.py @@ -0,0 +1,40 @@ +import os + +import iptc + +from .drawbridge import DrawBridge +from .net_queue import NetQueue + + +def is_root(): + return os.geteuid() == 0 + + +def check_nfqueue(): + try: + import nfqueue + + return True + except ImportError: + return False + + +def check_iptables(): + try: + iptc.Table(iptc.Table.FILTER) + return True + except iptc.ip4tc.IPTCError: + return False + + +def check_requirements(): + if not is_root(): + raise RuntimeError("Must be run as root") + if not check_nfqueue(): + raise RuntimeError("nfqueue is not installed or is not supported on your platform") + if not check_iptables(): + raise RuntimeError("iptables not installed or is not supported on your platform") + + +if __name__ == "__main__": + check_requirements() diff --git a/drawbridge/drawbridge.py b/drawbridge/drawbridge.py new file mode 100644 index 0000000..085afd5 --- /dev/null +++ b/drawbridge/drawbridge.py @@ -0,0 +1,65 @@ +import asyncio +import atexit +from typing import Callable +from typing import Optional + +import fnfqueue + +from .net_queue import NetQueue +from .utils.logger import logger + + +class DrawBridge: + def __init__(self): + self.net_queues = [] + atexit.register(self._delete_rules) + + def add_queue( + self, + callback: Callable, + queue: int = 0, + src_ip: Optional[str] = None, + dst_ip: Optional[str] = None, + src_port: Optional[int] = None, + dst_port: Optional[int] = None, + protocol: Optional[str] = "tcp", + override: bool = False, + ): + try: + new_queue = NetQueue(callback, queue, src_ip, dst_ip, src_port, dst_port, protocol, override) + new_queue.write_rule() + except Exception as e: + logger.error(f"Failed to initialize NetQueue: {e}") + raise e + self.net_queues.append(new_queue) + + def run(self): + asyncio.run(self.raise_bridges()) + + async def _listen(self, connection, callback: Callable) -> None: + try: + for packet in connection: + if asyncio.iscoroutinefunction(callback): + packet.payload = await callback(packet.payload) + else: + packet.payload = callback(packet.payload) + packet.mangle() + except fnfqueue.BufferOverflowException: + logger.warning("Packets arriving too quickly") + + def _delete_rules(self): + for queue in self.net_queues: + try: + queue.delete_rule() + except Exception as e: + logger.error(f"Failed to delete rule: {e}") + + async def raise_bridges(self): + tasks = [] + for queue in self.net_queues: + connection = fnfqueue.Connection() + listener = connection.bind(queue.queue) + listener.set_mode(65535, fnfqueue.COPY_PACKET) + task = asyncio.create_task(self._listen(connection, queue.callback)) + tasks.append(task) + await asyncio.gather(*tasks) diff --git a/drawbridge/net_queue.py b/drawbridge/net_queue.py new file mode 100644 index 0000000..f648bf9 --- /dev/null +++ b/drawbridge/net_queue.py @@ -0,0 +1,134 @@ +from ipaddress import AddressValueError +from ipaddress import ip_address +from ipaddress import IPv6Address +from typing import Callable +from typing import Optional +from typing import Union + +import iptc + +from .utils.logger import logger +from .utils.lookup import PROTOCOLS, ALL_TABLES, OUTGOING_MANGLE + + +class NetQueue: + def __init__( + self, + callback: Callable, + queue: int, + src_ip: Optional[str] = None, + dst_ip: Optional[str] = None, + src_port: Optional[int] = None, + dst_port: Optional[int] = None, + protocol: Optional[str] = "tcp", + override: bool = False, + ): + self.callback = self.validate_callable(callback) + self.src_port = self.validate_port(src_port, "source") + self.dst_port = self.validate_port(dst_port, "destination") + self.src_ip = self.validate_ip(src_ip, "source") + self.dst_ip = self.validate_ip(dst_ip, "destination") + self.protocol = self.validate_protocol(protocol) + + self.queue = self._validate_queue(queue, override) + self.rule = self._create_rule() + + def _create_rule(self) -> iptc.Rule: + rule = iptc.Rule() + target = iptc.Target(rule, "NFQUEUE") + target.set_parameter("queue-num", str(self.queue)) + rule.protocol = self.protocol + match = rule.create_match(self.protocol) + if self.dst_port: + match.dport = str(self.dst_port) + if self.src_port: + match.sport = str(self.src_port) + match = iptc.Match(rule, "iprange") + if self.src_ip: + match.src_range = str(self.src_ip) + if self.dst_ip: + match.dst_range = str(self.dst_ip) + rule.target = target + return rule + + def write_rule(self): + OUTGOING_MANGLE.insert_rule(self.rule) + + def delete_rule(self): + try: + OUTGOING_MANGLE.delete_rule(self.rule) + except iptc.ip4tc.IPTCError: + logger.warning("Failed to delete rule, it may have already been deleted") + + @staticmethod + def validate_callable(callback: Callable) -> Callable: + if not callable(callback): + raise ValueError(f"Invalid callback: {callback}") + return callback + + @staticmethod + def validate_ip(ip: Optional[str], description: str) -> Optional[str]: + if ip: + try: + if type(ip := ip_address(ip)) == IPv6Address: + raise NotImplementedError(f"IPv6 not supported: {ip}") + except (AddressValueError, ValueError): + raise AddressValueError(f"Invalid {description} IP address: {ip}") + return ip + + @staticmethod + def validate_port(port: Optional[int], description: str) -> Union[int, None]: + if port: + if not 0 <= port <= 65535: + raise ValueError(f"Invalid {description} port: {port}") + return port + + @staticmethod + def validate_protocol(protocol: Optional[str]) -> Union[str, None]: + if protocol: + try: + PROTOCOLS[protocol] + except KeyError: + raise KeyError(f"Invalid protocol: {protocol}") + return protocol + + @staticmethod + def _is_queue_taken(queue: int, override: bool) -> bool: + for table in ALL_TABLES: + for chain in table.chains: + for rule in chain.rules: + if rule.target.name == "NFQUEUE" and rule.target.get_all_parameters()["queue-num"] == str(queue): + if override: + logger.warning(f"Queue {queue} is already taken, clearing it") + chain.delete_rule(rule) + return False + return True + return False + + @staticmethod + def _validate_queue(queue: int, override: bool) -> int: + if not 0 <= queue <= 65535: + raise ValueError(f"Invalid queue number: {queue}") + + if NetQueue._is_queue_taken(queue, override): + logger.warning(f"Queue {queue} is already taken, raising error") + raise ValueError(f"Queue {queue} is already taken") + + return queue + + def __repr__(self): + return ( + f"NetQueueFilter(" + f"queue={self.queue}, " + f"callback={self.callback}, " + f"src_ip={self.src_ip}, " + f"dst_ip={self.dst_ip}, " + f"src_port={self.src_port}, " + f"dst_port={self.dst_port}, " + f"protocol={self.protocol}, " + f"rule={self.rule}, " + f")" + ) + + def __str__(self): + return self.__repr__() diff --git a/drawbridge/utils/__init__.py b/drawbridge/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/drawbridge/utils/logger.py b/drawbridge/utils/logger.py new file mode 100644 index 0000000..b6006ad --- /dev/null +++ b/drawbridge/utils/logger.py @@ -0,0 +1,35 @@ +from loguru import logger + + +logger.add( + "app.log", + format="{time:YYYY-MM-DD HH:mm:ss} | {message}", + level="INFO", + rotation="1 day", + retention="30 days", +) + +logger.add( + "errors.log", + format="ℹ️ {time:YYYY-MM-DD HH:mm:ss} | {message}", + level="WARNING", + rotation="1 day", + retention="30 days", +) + +logger.add( + "error.log", + format="⛔️ {time:YYYY-MM-DD HH:mm:ss} | {message}", + level="ERROR", + rotation="1 day", + retention="30 days", +) + + +logger.add( + "critical.log", + format="🚨 {time:YYYY-MM-DD HH:mm:ss} | {message}", + level="CRITICAL", + rotation="1 day", + retention="30 days", +) diff --git a/drawbridge/utils/lookup.py b/drawbridge/utils/lookup.py new file mode 100644 index 0000000..fa33cdb --- /dev/null +++ b/drawbridge/utils/lookup.py @@ -0,0 +1,33 @@ +import socket +import iptc + +PROTOCOLS = { + "ah": socket.IPPROTO_AH, + "dstopts": socket.IPPROTO_DSTOPTS, + "egp": socket.IPPROTO_EGP, + "esp": socket.IPPROTO_ESP, + "fragment": socket.IPPROTO_FRAGMENT, + "gre": socket.IPPROTO_GRE, + "hopopts": socket.IPPROTO_HOPOPTS, + "icmp": socket.IPPROTO_ICMP, + "icmpv6": socket.IPPROTO_ICMPV6, + "idp": socket.IPPROTO_IDP, + "igmp": socket.IPPROTO_IGMP, + "ip": socket.IPPROTO_IP, + "ipip": socket.IPPROTO_IPIP, + "ipv6": socket.IPPROTO_IPV6, + "none": socket.IPPROTO_NONE, + "pim": socket.IPPROTO_PIM, + "pup": socket.IPPROTO_PUP, + "raw": socket.IPPROTO_RAW, + "routing": socket.IPPROTO_ROUTING, + "rsvp": socket.IPPROTO_RSVP, + "sctp": socket.IPPROTO_SCTP, + "tcp": socket.IPPROTO_TCP, + "tp": socket.IPPROTO_TP, + "udp": socket.IPPROTO_UDP, +} + +ALL_TABLES = [iptc.Table(t) for t in iptc.Table.ALL] +PREROUTING_MANGLE = iptc.Chain(iptc.Table(iptc.Table.MANGLE), "PREROUTING") +OUTGOING_MANGLE = iptc.Chain(iptc.Table(iptc.Table.FILTER), "OUTPUT") \ No newline at end of file diff --git a/examples/chat.html b/examples/chat.html new file mode 100644 index 0000000..1452384 --- /dev/null +++ b/examples/chat.html @@ -0,0 +1,65 @@ + + + + + WebSocket Chat + + + + +

+ + + + + + + \ No newline at end of file diff --git a/examples/hijack.py b/examples/hijack.py new file mode 100644 index 0000000..72b96bb --- /dev/null +++ b/examples/hijack.py @@ -0,0 +1,70 @@ + +from drawbridge import DrawBridge +from scapy.layers.http import HTTPResponse, HTTP +from scapy.all import IP, TCP +from scapy.packet import Raw +import json + + +def modify_websocket_chat(raw_packet): + pkt = IP(raw_packet) + + tcp0 = pkt.getlayer(TCP) + try: + jsonb = bytes(tcp0.payload)[2:] + if jsonb == b'': + return raw_packet + except IndexError: + return raw_packet + try: + json.loads(jsonb.decode("utf-8")) + except (json.decoder.JSONDecodeError, UnicodeDecodeError): + return raw_packet + + tcp0.payload = Raw(bytes(tcp0.payload)[:2] + b'{"sender": "hackerboy23", "message": "boom"}') + del pkt[IP].len + del pkt[IP].chksum + del pkt[TCP].chksum + + return bytes(pkt) + + +black_png = ( + b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\n\x00\x00' + b'\x00\n\x08\x06\x00\x00\x00\x8d2\xcf\xbd\x00\x00\x00\x0cIDATx' + b'\xda\xed\xc1\x01\r\x00\x00\x00\xc2\xa0\xf5H\xfd\x00\x00\x00' + b'\x00IEND\xaeB`\x82' +) +black_payload = HTTP(bytes( + "HTTP/1.1 200 OK\r\nContent-Type: image/png\r\n\r\n", 'utf-8') + black_png + ) +black_len = str(len(black_png)).encode() + + + +def modify_img_request(raw_packet): + pkt = IP(raw_packet) + + if pkt.haslayer(HTTPResponse): + http_layer = pkt.getlayer(HTTP) + http_response = pkt.getlayer(HTTPResponse) + + if http_response.fields.get('Content_Type') == b'image/png': + print("Found PNG file...") + + # http_layer.payload = black_payload + pkt.show2() + + if pkt.haslayer(TCP): + http_layer.fields['Content_Length'] = black_len + del pkt[TCP].chksum + del pkt[IP].chksum + del pkt[IP].len + else: + return raw_packet + return bytes(IP(bytes(pkt))) + + +db = DrawBridge() +db.add_queue(modify_websocket_chat, queue=2, src_port=80) +db.run() diff --git a/examples/server.py b/examples/server.py new file mode 100644 index 0000000..eb4c0a8 --- /dev/null +++ b/examples/server.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +import asyncio +import websockets +import json + +connected = set() + + +def sanitize(message): + # A very secure sanitization function. + return message.replace('<', '').replace('>', '') + + +async def handler(websocket): + try: + connected.add(websocket) + print(f"Connected: {websocket.remote_address[0]}") + while True: + try: + message = await websocket.recv() + sanitized_message = sanitize(message) + sanitized_sender = sanitize(websocket.remote_address[0]) + + payload = json.dumps({ + 'sender': sanitized_sender, + 'message': sanitized_message + }) + + print(f"{websocket.remote_address[0]} says: {message}") + for conn in connected: + await conn.send(payload) + + except Exception: + break + finally: + print(f"Disconnected: {websocket.remote_address[0]}.") + connected.remove(websocket) + + +async def main(): + async with websockets.serve(handler, "", 80, compression=None): + await asyncio.Future() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..735b2d3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[build-system] +requires = ["setuptools>=67.8"] +build-backend = "setuptools.build_meta" + +[project] +name = "drawbridge" +version = "0.0.1" +authors = [{ name = "drawbridge", email = "git@nixon.mozmail.com" }] +description = "An nfqueue abstraction module for building security tools" +readme = "README.md" +requires-python = ">=3.9" +license = { text = "GPLv3" } +dependencies = ["fnfqueue>=1.1.2", "python-iptables>=1.0.1", "loguru>=0.7.0"] + +[project.urls] +homepage = "https://github.com/DarrylNixon/drawbridge" +repository = "https://github.com/DarrylNixon/drawbridge" + +[tool.setuptools] +py-modules = ["drawbridge"] + +[tool.bandit] +exclude_dirs = ["/doc", "/build"] +# TODO: Stop skipping B104 (binding on 0.0.0.0), is there a nice way to get a good docker bind address? +skips = ["B104"] + +[tool.black] +line-length = 120