├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── conftest.py ├── contrib └── onionrouter.service ├── docs ├── Makefile ├── authors.rst ├── conf.py ├── contributing.rst ├── history.rst ├── index.rst ├── installation.rst ├── make.bat └── readme.rst ├── onionrouter ├── __init__.py ├── config_handlers.py ├── configs │ ├── map.yml │ └── onionrouter.ini ├── custom_exceptions.py ├── lookups.py ├── msockets.py ├── olib.py ├── rerouter.py └── routers.py ├── onionrouter_run ├── requirements.txt ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py └── test_onionrouter.py └── tox.ini /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * onionrouter version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | # pyenv python configuration file 62 | .python-version 63 | 64 | # IDE stuff 65 | .idea 66 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This file was autogenerated and will overwrite each time you run travis_pypi_setup.py 2 | env: 3 | matrix: 4 | - TOXENV=py 5 | - TOXENV=flake8 6 | install: pip install -U tox 7 | language: python 8 | python: 9 | - 3.6 10 | - 3.7 11 | - 3.8 12 | - 3.9 13 | - 3.10 14 | - 3.11 15 | - 3.12 16 | script: tox -e ${TOXENV} 17 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Ehlo Onion 9 | * George Kargiotakis 10 | * John Paraskevopoulos 11 | 12 | Contributors 13 | ------------ 14 | 15 | * George Kargiotakis 16 | * John Paraskevopoulos 17 | * Craig Andrews 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every 8 | little bit helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/ehloonion/onionrouter/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" 30 | and "help wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | onionrouter could always use more documentation, whether as part of the 42 | official onionrouter docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/ehloonion/onionrouter/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `onionrouter` for local development. 61 | 62 | 1. Fork the `onionrouter` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/onionrouter.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv onionrouter 70 | $ cd onionrouter/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: 80 | 81 | $ flake8 onionrouter tests 82 | $ python setup.py test or py.test 83 | $ tox 84 | 85 | To get flake8 and tox, just pip install them into your virtualenv. 86 | 87 | 6. Commit your changes and push your branch to GitHub:: 88 | 89 | $ git add . 90 | $ git commit -m "Your detailed description of your changes." 91 | $ git push origin name-of-your-bugfix-or-feature 92 | 93 | 7. Submit a pull request through the GitHub website. 94 | 95 | Pull Request Guidelines 96 | ----------------------- 97 | 98 | Before you submit a pull request, check that it meets these guidelines: 99 | 100 | 1. The pull request should include tests. 101 | 2. If the pull request adds functionality, the docs should be updated. Put 102 | your new functionality into a function with a docstring, and add the 103 | feature to the list in README.rst. 104 | 3. The pull request should work for Python 3.6, 3.7, 3.8, and 3.9, and for PyPy. Check 105 | https://travis-ci.org/ehloonion/onionrouter/pull_requests 106 | and make sure that the tests pass for all supported Python versions. 107 | 108 | Tips 109 | ---- 110 | 111 | To run a subset of tests:: 112 | 113 | $ py.test tests.test_onionrouter 114 | 115 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.6.2 (2021-10-15) 6 | ------------------ 7 | * Bump pip version in setup.py to >=19.2 due to CVE-2019-20916 8 | * Lift PyYAML restriction of < 6.0 and pin to 6.0 in test requirements and requirements.txt 9 | * Add support for Python 3.10 10 | 11 | 0.6.1 (2020-10-29) 12 | ------------------ 13 | 14 | * Bump cryptography requirement to 3.2 due to CVE-2020-25659 15 | * Fix systemd paths and add hardening options 16 | 17 | 0.6.0 (2020-10-29) 18 | ------------------ 19 | 20 | * Fix byte/string conversion errors 21 | * Add support for python 3.7/3.8/3.9 22 | * Drop support for EOL Python 2.7/3.3/3.4 23 | * Use newer versions of dnspython and PyYAML 24 | 25 | 0.5.4 (2020-10-20) 26 | ------------------ 27 | 28 | * Send a newline when replying to postfiX 29 | 30 | 0.5.3 (2020-10-20) 31 | ------------------ 32 | 33 | * Last version that supports Python 2.7 34 | * Fixes issue with renamed module that rendered 0.5.2 non-working 35 | * Pins dnspython < 2.0.0 since 2.0.0 does not support Python 2.7 36 | 37 | 0.4.1 (2017-05-13) 38 | ------------------ 39 | 40 | * Fix "[Errno 98] Address already in use" error when restarting the daemon 41 | 42 | 0.4.0 (2017-03-20) 43 | ------------------ 44 | 45 | * Beta release on PyPI. 46 | 47 | 0.1.0 (2017-03-14) 48 | ------------------ 49 | 50 | * First release on PyPI. 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | GNU GENERAL PUBLIC LICENSE 3 | Version 3, 29 June 2007 4 | 5 | Python Routed Onion Deliveries 6 | Copyright (C) 2017 Ehlo Onion 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program. If not, see . 20 | 21 | Also add information on how to contact you by electronic and paper mail. 22 | 23 | You should also get your employer (if you work as a programmer) or school, 24 | if any, to sign a "copyright disclaimer" for the program, if necessary. 25 | For more information on this, and how to apply and follow the GNU GPL, see 26 | . 27 | 28 | The GNU General Public License does not permit incorporating your program 29 | into proprietary programs. If your program is a subroutine library, you 30 | may consider it more useful to permit linking proprietary applications with 31 | the library. If this is what you want to do, use the GNU Lesser General 32 | Public License instead of this License. But first, please read 33 | . 34 | 35 | 36 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | 2 | include AUTHORS.rst 3 | 4 | include CONTRIBUTING.rst 5 | include HISTORY.rst 6 | include LICENSE 7 | include README.rst 8 | 9 | include conftest.py 10 | 11 | recursive-include onionrouter *.ini 12 | recursive-include tests * 13 | recursive-exclude * __pycache__ 14 | recursive-exclude * *.py[co] 15 | 16 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | define BROWSER_PYSCRIPT 4 | import os, webbrowser, sys 5 | try: 6 | from urllib import pathname2url 7 | except: 8 | from urllib.request import pathname2url 9 | 10 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 11 | endef 12 | export BROWSER_PYSCRIPT 13 | 14 | define PRINT_HELP_PYSCRIPT 15 | import re, sys 16 | 17 | for line in sys.stdin: 18 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 19 | if match: 20 | target, help = match.groups() 21 | print("%-20s %s" % (target, help)) 22 | endef 23 | export PRINT_HELP_PYSCRIPT 24 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 25 | 26 | help: 27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 28 | 29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 30 | 31 | 32 | clean-build: ## remove build artifacts 33 | rm -fr build/ 34 | rm -fr dist/ 35 | rm -fr .eggs/ 36 | find . -name '*.egg-info' -exec rm -fr {} + 37 | find . -name '*.egg' -exec rm -f {} + 38 | 39 | clean-pyc: ## remove Python file artifacts 40 | find . -name '*.pyc' -exec rm -f {} + 41 | find . -name '*.pyo' -exec rm -f {} + 42 | find . -name '*~' -exec rm -f {} + 43 | find . -name '__pycache__' -exec rm -fr {} + 44 | 45 | clean-test: ## remove test and coverage artifacts 46 | rm -fr .tox/ 47 | rm -f .coverage 48 | rm -fr htmlcov/ 49 | 50 | lint: ## check style with flake8 51 | flake8 onionrouter tests 52 | 53 | test: ## run tests quickly with the default Python 54 | py.test 55 | 56 | 57 | test-all: ## run tests on every Python version with tox 58 | tox 59 | 60 | coverage: ## check code coverage quickly with the default Python 61 | coverage run --source onionrouter -m pytest 62 | 63 | coverage report -m 64 | coverage html 65 | $(BROWSER) htmlcov/index.html 66 | 67 | docs: ## generate Sphinx HTML documentation, including API docs 68 | rm -f docs/onionrouter.rst 69 | rm -f docs/modules.rst 70 | sphinx-apidoc -o docs/ onionrouter 71 | $(MAKE) -C docs clean 72 | $(MAKE) -C docs html 73 | $(BROWSER) docs/_build/html/index.html 74 | 75 | servedocs: docs ## compile the docs watching for changes 76 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 77 | 78 | release: clean ## package and upload a release 79 | python setup.py sdist upload 80 | python setup.py bdist_wheel upload 81 | 82 | dist: clean ## builds source and wheel package 83 | python setup.py sdist 84 | python setup.py bdist_wheel 85 | ls -l dist 86 | 87 | install: clean ## install the package to the active Python's site-packages 88 | python setup.py install 89 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | OnionRouter 2 | =========== 3 | 4 | 5 | .. image:: https://img.shields.io/pypi/v/onionrouter.svg 6 | :target: https://pypi.python.org/pypi/onionrouter 7 | 8 | .. image:: https://img.shields.io/travis/ehloonion/onionrouter.svg 9 | :target: https://travis-ci.org/ehloonion/onionrouter 10 | 11 | .. image:: https://readthedocs.org/projects/onionrouter/badge/?version=latest 12 | :target: https://onionrouter.readthedocs.io/en/latest/?badge=latest 13 | :alt: Documentation Status 14 | 15 | .. image:: https://pyup.io/repos/github/ehloonion/onionrouter/shield.svg 16 | :target: https://pyup.io/repos/github/ehloonion/onionrouter/ 17 | :alt: Updates 18 | 19 | 20 | Python Onion Routed Mail Deliveries 21 | 22 | * Free software: GNU General Public License v3 23 | * Documentation: https://onionrouter.readthedocs.io. 24 | 25 | What is this ? 26 | -------------- 27 | 28 | This python script implements dynamic SRV lookups to be fed to postfix as TCP transport map replies. More information on mail delivery over onion services can be found at https://github.com/ehloonion/onionmx. 29 | 30 | An alternative implementation in Go can be found at https://git.autistici.org/ale/postfix-onion-transport 31 | 32 | 33 | Features 34 | -------- 35 | * Interactive/Client/Daemon/Debug modes 36 | * Configurable SRV lookup string 37 | * Domain whitelisting 38 | * Lazy rerouting using static mappings 39 | 40 | How to run 41 | ---------- 42 | 43 | There are two ways to install onionrouter, automatically using pip or cloning the repository and manually installing the needed packages on Debian. Currently onionrouter has only been tested on Debian Jessie. 44 | 45 | Using pip 46 | ^^^^^^^^^ 47 | .. code-block:: console 48 | 49 | $ sudo pip install onionrouter 50 | 51 | Test functionality 52 | """""""""""""""""" 53 | .. code-block:: console 54 | 55 | $ onionrouter --help 56 | 57 | Manual installation on Debian Jessie 58 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 59 | 60 | Clone repository 61 | """"""""""""""""""""""" 62 | .. code-block:: console 63 | 64 | $ git clone https://github.com/ehloonion/onionrouter.git 65 | 66 | Install Debian packages 67 | """"""""""""""""""""""" 68 | onionrouter has only been tested on Debian Jessie. Install the following packages: 69 | 70 | .. code-block:: console 71 | 72 | $ sudo apt install python-dnspython python-yaml 73 | 74 | Test functionality 75 | """""""""""""""""" 76 | .. code-block:: console 77 | 78 | $ cd onionrouter 79 | $ ./onionrouter_run --help 80 | 81 | Configuration and other options 82 | ------------------------------- 83 | * Copy or update the onionrouter.ini file and with your settings (reference file is in onionrouter/configs folder if you cloned the git repo or in /etc/onionrouter/ if you installed the package) 84 | * Edit the configuration file 85 | * Under the **DOMAIN** section replace the value of the **hostname** key with your local domain to be whitelisted from lookups. To add multiple local domains, separate them with comma ',' 86 | * Under the **RESOLVER** section put in the **resolver_ip** field your preferred resolver (default is 127.0.0.1). To use multiple resolvers, separate them with comma ',' 87 | * Under the **RESOLVER** section put in the **resolver_port** field the port that your resolver listens to (default is 53) 88 | 89 | onionrouter by default queries the destination domain for a specific SRV record, *_onion-mx._tcp.* and if it finds a .onion address in the reply it gives it back to postfix to be used by the smtptor service defined in master.cf. If no valid SRV record is found the mail is passed to smtp service. This gives us dynamic SRV lookups that lead to SMTP over onion addresses! 90 | 91 | * To change the SRV record the scripts looks for, edit the config file mentioned above and change under the **DNS** section the **srv_record** field with the SRV record you have setup (default is _onion-mx._tcp.) 92 | * To change the service that will be used when a .onion address is found, edit the config file mentioned above and change under the **REROUTE** section the **onion_transport** field with the service you want to be used (default is smtptor) 93 | * To *blacklist/ignore* domains in case you have a custom routing rule, or a black list of domains, add those domains under the **IGNORED** section in the **domains** field. For multiple domains, separate them with comma ','. 94 | 95 | Execution options 96 | ----------------- 97 | onionrouter by default runs in server mode and acts as a daemon waiting for connections. 98 | 99 | Daemon mode can be configured with the following options: 100 | 101 | * **--port PORT** or **-p PORT** to define port for daemon to listen (default 23000) 102 | * **--host HOST** or **-l HOST** to define host for daemon to listen (default 127.0.0.1) 103 | 104 | Other options are supported as well: 105 | 106 | * **--mappings MAPPINGS** to define absolute path to static mappings folder (everything inside will be parsed as a yaml file) or yaml file 107 | * **--config CONFIG** to define the absolute path to config folder (must contain a onionrouter.ini file inside) or config file 108 | * **--client** or **-c** to connect to the daemon and interact with. Use the host and port options to define the options for the connection to the daemon 109 | * **--debug** or **-d** to start the daemon in debug mode. In this mode, daemon will also print (besides replying) the queries and answers Use the host and port options to define the options for the daemon 110 | * **--interactive** or **-i** to run onionrouter in interactive input mode for debugging or testing purposes without daemon 111 | 112 | How to run 113 | ---------- 114 | Currently onionrouter runs in the foreground, so you need to either run it via a systemd unit file or through some other daemonizing method (eg screen/tmux/etc). An example systemd unit is included in the *contrib* directory, modify it to your liking. 115 | 116 | .. code-block:: console 117 | 118 | $ ./onionrouter_run --config /srv/onionrouter/onionrouter/configs/onionrouter.ini --mappings /srv/onionrouter/onionrouter/configs/map.yml -p 23002 --debug 119 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import pytest 3 | import io 4 | try: 5 | import configparser 6 | except ImportError: 7 | import ConfigParser as configparser 8 | from onionrouter import rerouter, config_handlers 9 | 10 | 11 | config = """ 12 | [RESOLVER] 13 | resolver_ip: 127.0.0.1 14 | resolver_port: 53 15 | tcp: True 16 | 17 | [DOMAIN] 18 | hostname: myself.net, myself2.net 19 | 20 | [DNS] 21 | srv_record: _onion-mx._tcp. 22 | 23 | [REROUTE] 24 | onion_transport: smtptor 25 | 26 | [IGNORED] 27 | domains: ignore.me, ignore2.me 28 | """ 29 | 30 | 31 | @pytest.fixture(scope="session", name="dummy_config") 32 | def fixture_config(): 33 | return config 34 | 35 | 36 | @pytest.fixture(scope="function", name="dummy_onionrouter") 37 | def fixture_onionrouter(monkeypatch, dummy_config): 38 | monkeypatch.setattr( 39 | config_handlers, "get_conffile", 40 | lambda *args, **kwargs: rerouter.OnionRouter.ref_config) 41 | custom_config = configparser.ConfigParser() 42 | custom_config._read(io.StringIO(dummy_config), None) 43 | monkeypatch.setattr(config_handlers, "config_reader", 44 | lambda *args: custom_config) 45 | return rerouter.OnionRouter("nothing?") 46 | 47 | -------------------------------------------------------------------------------- /contrib/onionrouter.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=onionrouter 3 | After=network.target 4 | Before=postfix.service 5 | 6 | [Service] 7 | ExecStart=/usr/bin/onionrouter 8 | Restart=on-failure 9 | RestartSec=10s 10 | DynamicUser=true 11 | PrivateDevices=true 12 | PrivateUsers=true 13 | ProtectClock=true 14 | ProtectControlGroups=true 15 | ProtectHome=true 16 | ProtectKernelLogs=true 17 | ProtectKernelModules=true 18 | ProtectKernelTunables=true 19 | ProtectHostname=true 20 | RestrictRealtime=true 21 | MemoryDenyWriteExecute=true 22 | 23 | [Install] 24 | WantedBy=default.target 25 | 26 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/onionrouter.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/onionrouter.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/onionrouter" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/onionrouter" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # onionrouter documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Jul 9 22:26:36 2013. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another 20 | # directory, add these directories to sys.path here. If the directory is 21 | # relative to the documentation root, use os.path.abspath to make it 22 | # absolute, like shown here. 23 | #sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # Get the project root dir, which is the parent dir of this 26 | cwd = os.getcwd() 27 | project_root = os.path.dirname(cwd) 28 | 29 | # Insert the project root dir as the first element in the PYTHONPATH. 30 | # This lets us ensure that the source package is imported, and that its 31 | # version is used. 32 | sys.path.insert(0, project_root) 33 | 34 | import onionrouter 35 | 36 | # -- General configuration --------------------------------------------- 37 | 38 | # If your documentation needs a minimal Sphinx version, state it here. 39 | #needs_sphinx = '1.0' 40 | 41 | # Add any Sphinx extension module names here, as strings. They can be 42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 43 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | templates_path = ['_templates'] 47 | 48 | # The suffix of source filenames. 49 | source_suffix = '.rst' 50 | 51 | # The encoding of source files. 52 | #source_encoding = 'utf-8-sig' 53 | 54 | # The master toctree document. 55 | master_doc = 'index' 56 | 57 | # General information about the project. 58 | project = u'onionrouter' 59 | copyright = u"2017, Ehlo Onion" 60 | 61 | # The version info for the project you're documenting, acts as replacement 62 | # for |version| and |release|, also used in various other places throughout 63 | # the built documents. 64 | # 65 | # The short X.Y version. 66 | version = onionrouter.__version__ 67 | # The full version, including alpha/beta/rc tags. 68 | release = onionrouter.__version__ 69 | 70 | # The language for content autogenerated by Sphinx. Refer to documentation 71 | # for a list of supported languages. 72 | #language = None 73 | 74 | # There are two options for replacing |today|: either, you set today to 75 | # some non-false value, then it is used: 76 | #today = '' 77 | # Else, today_fmt is used as the format for a strftime call. 78 | #today_fmt = '%B %d, %Y' 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | exclude_patterns = ['_build'] 83 | 84 | # The reST default role (used for this markup: `text`) to use for all 85 | # documents. 86 | #default_role = None 87 | 88 | # If true, '()' will be appended to :func: etc. cross-reference text. 89 | #add_function_parentheses = True 90 | 91 | # If true, the current module name will be prepended to all description 92 | # unit titles (such as .. function::). 93 | #add_module_names = True 94 | 95 | # If true, sectionauthor and moduleauthor directives will be shown in the 96 | # output. They are ignored by default. 97 | #show_authors = False 98 | 99 | # The name of the Pygments (syntax highlighting) style to use. 100 | pygments_style = 'sphinx' 101 | 102 | # A list of ignored prefixes for module index sorting. 103 | #modindex_common_prefix = [] 104 | 105 | # If true, keep warnings as "system message" paragraphs in the built 106 | # documents. 107 | #keep_warnings = False 108 | 109 | 110 | # -- Options for HTML output ------------------------------------------- 111 | 112 | # The theme to use for HTML and HTML Help pages. See the documentation for 113 | # a list of builtin themes. 114 | html_theme = 'default' 115 | 116 | # Theme options are theme-specific and customize the look and feel of a 117 | # theme further. For a list of options available for each theme, see the 118 | # documentation. 119 | #html_theme_options = {} 120 | 121 | # Add any paths that contain custom themes here, relative to this directory. 122 | #html_theme_path = [] 123 | 124 | # The name for this set of Sphinx documents. If None, it defaults to 125 | # " v documentation". 126 | #html_title = None 127 | 128 | # A shorter title for the navigation bar. Default is the same as 129 | # html_title. 130 | #html_short_title = None 131 | 132 | # The name of an image file (relative to this directory) to place at the 133 | # top of the sidebar. 134 | #html_logo = None 135 | 136 | # The name of an image file (within the static path) to use as favicon 137 | # of the docs. This file should be a Windows icon file (.ico) being 138 | # 16x16 or 32x32 pixels large. 139 | #html_favicon = None 140 | 141 | # Add any paths that contain custom static files (such as style sheets) 142 | # here, relative to this directory. They are copied after the builtin 143 | # static files, so a file named "default.css" will overwrite the builtin 144 | # "default.css". 145 | html_static_path = ['_static'] 146 | 147 | # If not '', a 'Last updated on:' timestamp is inserted at every page 148 | # bottom, using the given strftime format. 149 | #html_last_updated_fmt = '%b %d, %Y' 150 | 151 | # If true, SmartyPants will be used to convert quotes and dashes to 152 | # typographically correct entities. 153 | #html_use_smartypants = True 154 | 155 | # Custom sidebar templates, maps document names to template names. 156 | #html_sidebars = {} 157 | 158 | # Additional templates that should be rendered to pages, maps page names 159 | # to template names. 160 | #html_additional_pages = {} 161 | 162 | # If false, no module index is generated. 163 | #html_domain_indices = True 164 | 165 | # If false, no index is generated. 166 | #html_use_index = True 167 | 168 | # If true, the index is split into individual pages for each letter. 169 | #html_split_index = False 170 | 171 | # If true, links to the reST sources are added to the pages. 172 | #html_show_sourcelink = True 173 | 174 | # If true, "Created using Sphinx" is shown in the HTML footer. 175 | # Default is True. 176 | #html_show_sphinx = True 177 | 178 | # If true, "(C) Copyright ..." is shown in the HTML footer. 179 | # Default is True. 180 | #html_show_copyright = True 181 | 182 | # If true, an OpenSearch description file will be output, and all pages 183 | # will contain a tag referring to it. The value of this option 184 | # must be the base URL from which the finished HTML is served. 185 | #html_use_opensearch = '' 186 | 187 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 188 | #html_file_suffix = None 189 | 190 | # Output file base name for HTML help builder. 191 | htmlhelp_basename = 'onionrouterdoc' 192 | 193 | 194 | # -- Options for LaTeX output ------------------------------------------ 195 | 196 | latex_elements = { 197 | # The paper size ('letterpaper' or 'a4paper'). 198 | #'papersize': 'letterpaper', 199 | 200 | # The font size ('10pt', '11pt' or '12pt'). 201 | #'pointsize': '10pt', 202 | 203 | # Additional stuff for the LaTeX preamble. 204 | #'preamble': '', 205 | } 206 | 207 | # Grouping the document tree into LaTeX files. List of tuples 208 | # (source start file, target name, title, author, documentclass 209 | # [howto/manual]). 210 | latex_documents = [ 211 | ('index', 'onionrouter.tex', 212 | u'onionrouter Documentation', 213 | u'Ehlo Onion', 'manual'), 214 | ] 215 | 216 | # The name of an image file (relative to this directory) to place at 217 | # the top of the title page. 218 | #latex_logo = None 219 | 220 | # For "manual" documents, if this is true, then toplevel headings 221 | # are parts, not chapters. 222 | #latex_use_parts = False 223 | 224 | # If true, show page references after internal links. 225 | #latex_show_pagerefs = False 226 | 227 | # If true, show URL addresses after external links. 228 | #latex_show_urls = False 229 | 230 | # Documents to append as an appendix to all manuals. 231 | #latex_appendices = [] 232 | 233 | # If false, no module index is generated. 234 | #latex_domain_indices = True 235 | 236 | 237 | # -- Options for manual page output ------------------------------------ 238 | 239 | # One entry per manual page. List of tuples 240 | # (source start file, name, description, authors, manual section). 241 | man_pages = [ 242 | ('index', 'onionrouter', 243 | u'onionrouter Documentation', 244 | [u'Ehlo Onion'], 1) 245 | ] 246 | 247 | # If true, show URL addresses after external links. 248 | #man_show_urls = False 249 | 250 | 251 | # -- Options for Texinfo output ---------------------------------------- 252 | 253 | # Grouping the document tree into Texinfo files. List of tuples 254 | # (source start file, target name, title, author, 255 | # dir menu entry, description, category) 256 | texinfo_documents = [ 257 | ('index', 'onionrouter', 258 | u'onionrouter Documentation', 259 | u'Ehlo Onion', 260 | 'onionrouter', 261 | 'One line description of project.', 262 | 'Miscellaneous'), 263 | ] 264 | 265 | # Documents to append as an appendix to all manuals. 266 | #texinfo_appendices = [] 267 | 268 | # If false, no module index is generated. 269 | #texinfo_domain_indices = True 270 | 271 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 272 | #texinfo_show_urls = 'footnote' 273 | 274 | # If true, do not generate a @detailmenu in the "Top" node's menu. 275 | #texinfo_no_detailmenu = False 276 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to onionrouter's documentation! 2 | ====================================== 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | readme 10 | installation 11 | usage 12 | contributing 13 | authorshistory 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | There are two ways to install onionrouter, automatically using pip or cloning the repository and manually installing the needed packages on Debian. Currently onionrouter has only been tested on Debian Jessie. 4 | 5 | Using pip 6 | ^^^^^^^^^ 7 | .. code-block:: console 8 | 9 | $ sudo pip install onionrouter 10 | 11 | Test functionality 12 | """""""""""""""""" 13 | .. code-block:: console 14 | 15 | $ onionrouter --help 16 | 17 | Manual installation on Debian Jessie 18 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 19 | 20 | Clone repository 21 | """"""""""""""""""""""" 22 | .. code-block:: console 23 | 24 | $ git clone https://github.com/ehloonion/onionrouter.git 25 | 26 | Install Debian packages 27 | """"""""""""""""""""""" 28 | onionrouter has only been tested on Debian Jessie. Install the following packages: 29 | 30 | .. code-block:: console 31 | 32 | $ sudo apt install python-dnspython python-yaml 33 | 34 | Test functionality 35 | """""""""""""""""" 36 | .. code-block:: console 37 | 38 | $ cd onionrouter 39 | $ ./onionrouter_run --help 40 | 41 | 42 | .. _Github repo: https://github.com/ehloonion/onionrouter 43 | .. _tarball: https://github.com/ehloonion/onionrouter/tarball/master 44 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\onionrouter.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\onionrouter.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /onionrouter/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = """Ehlo Onion""" 4 | __email__ = 'onionmx@lists.immerda.ch' 5 | __version__ = '0.6.2' 6 | -------------------------------------------------------------------------------- /onionrouter/config_handlers.py: -------------------------------------------------------------------------------- 1 | import os 2 | try: 3 | import ConfigParser as configparser 4 | except ImportError: 5 | import configparser 6 | from yaml import safe_load 7 | from onionrouter.olib import find_file, find_files_with_suffix 8 | from onionrouter.custom_exceptions import (ConfigIntegrityError, 9 | ConfigNotFoundError) 10 | 11 | 12 | class ConfigIntegrityChecker(object): 13 | def __init__(self, ref_config, other_config): 14 | self._ref_reader = config_reader(ref_config) 15 | self._oth_reader = config_reader(other_config) 16 | self.ref_config = ref_config 17 | self.other_config = other_config 18 | 19 | def _verify_sections(self): 20 | if set(self._ref_reader.sections()).difference( 21 | set(self._oth_reader.sections())): 22 | raise RuntimeError 23 | 24 | def _verify_options(self): 25 | for section in self._ref_reader.sections(): 26 | if set(self._ref_reader.options(section)).difference( 27 | set(self._oth_reader.options(section))): 28 | raise RuntimeError 29 | 30 | def verify(self): 31 | try: 32 | self._verify_sections() 33 | self._verify_options() 34 | except RuntimeError: 35 | raise ConfigIntegrityError( 36 | "{loc} does not match the reference config file {ref}\n" 37 | "Is your local config up to date?".format( 38 | loc=self.other_config, ref=self.ref_config)) 39 | 40 | 41 | def config_reader(conf_path): 42 | """ 43 | Reads a conf file and returns the onfigParser object 44 | """ 45 | config = configparser.ConfigParser() 46 | config.read(conf_path) 47 | return config 48 | 49 | 50 | def get_conffile(conf_path, prefix='', suffix=".ini"): 51 | """ 52 | Finds conf file in `conf_path` 53 | If `conf_path` is file, returns `conf_path` 54 | Else it searches to find a file with `prefix``suffix` format 55 | """ 56 | if os.path.isfile(conf_path): 57 | return conf_path 58 | 59 | found_file = find_file(conf_path, "{0}{1}".format(prefix, suffix)) 60 | if not found_file: 61 | raise ConfigNotFoundError('No configuration file found in {0}' 62 | .format(conf_path)) 63 | return found_file 64 | 65 | 66 | def yaml_loader(yaml_path): 67 | """ 68 | Yields yaml files 69 | Supports folder lookup 70 | :param yaml_path: path pointing to a yaml file or a dir with yaml files 71 | :return: yields open file 72 | """ 73 | if not os.path.exists(yaml_path): 74 | return 75 | 76 | if os.path.isdir(yaml_path): 77 | all_files = sorted(find_files_with_suffix(yaml_path, "yml")) 78 | else: 79 | all_files = [yaml_path] 80 | 81 | for filename in all_files: 82 | with open(filename, 'r') as yaml_file: 83 | yield yaml_file 84 | 85 | 86 | def load_yamls(yaml_path): 87 | """ 88 | Returns a dict with mappings defined in yaml file(s) 89 | :param yaml_path: path pointing to a yaml file or a dir with yaml files 90 | :rtype: dict 91 | """ 92 | yaml_mapping = dict() 93 | for f in yaml_loader(yaml_path): 94 | yaml_mapping.update(safe_load(f)) 95 | return yaml_mapping 96 | -------------------------------------------------------------------------------- /onionrouter/configs/map.yml: -------------------------------------------------------------------------------- 1 | # A static mapping of email domains to onion services, 2 | # where the key of the map are the onion servies itself. 3 | # 4 | # Basic structure: 5 | # 'onion_service1': 6 | # - domainA 7 | # 'onion_service2': 8 | # - domain1 9 | # - domain2 10 | # 11 | # Adding a new mapping: 12 | # 13 | # * Either add a domain to your existing 14 | # onion service, keeping them alphabetically 15 | # sorted. 16 | # * Or add a new onion service name and its domains to 17 | # the list in an alphabetical order. 18 | # 19 | # Rerun the scripts to generate the mappings for your MTA. 20 | --- 21 | 'cssj56utefk3wxsv.onion': 22 | - 'boum.org' 23 | 'h2qkxasmmqdmyiov.onion': 24 | - 'systemli.org' 25 | 'hi2yaj2v3yl4nggt.onion': 26 | - 'koumbit.org' 27 | 'j7xlydwquvyhasnk.onion': 28 | - 'sindominio.net' 29 | 'lloiryev7cvzszsn.onion': 30 | - 'espiv.net' 31 | 'm5545lfnfnw6z2he.onion': 32 | - 'so36.net' 33 | - 'mail36.net' 34 | 'mssatgg2rwa4aytk.onion': 35 | - 'anargeek.net' 36 | - 'irq7.fr' 37 | - 'pimienta.org' 38 | - 'poivron.org' 39 | - 'potager.org' 40 | - 'sweetpepper.org' 41 | 'wvctipy4ylajd5fc.onion': 42 | - 'lelutin.ca' 43 | 'wy6zk3pmcwiyhiao.onion': 44 | - 'riseup.net' 45 | 'tyiw2fzn3uckv2my.onion': 46 | - 'espora.org' 47 | 'xpgylzydxykgdqyg.onion': 48 | - 'lists.riseup.net' 49 | 'ysp4gfuhnmj6b4mb.onion': 50 | - 'glei.ch' 51 | - 'brief.li' 52 | - 'cronopios.org' 53 | - 'cryptomail.ch' 54 | - 'einfachsicher.ch' 55 | - 'immer-da.ch' 56 | - 'immerda.ch' 57 | - 'linksunten.ch' 58 | - 'lists.immerda.ch' 59 | - 'muhvie.ch' 60 | - 'muh-vie.ch' 61 | - 'politischkorrekt.ch' 62 | - 'paratodastodo.net' 63 | - 'paratodastodo.org' 64 | - 'paratodostodo.org' 65 | - 'sichermail.ch' 66 | - 'sicheremail.ch' 67 | - 'supernormal.net' 68 | - 'ungehorsam.ch' 69 | 'zq36q5rfxd3cnvd5.onion': 70 | - 'lists.espiv.net' 71 | -------------------------------------------------------------------------------- /onionrouter/configs/onionrouter.ini: -------------------------------------------------------------------------------- 1 | [RESOLVER] 2 | resolver_ip: 127.0.0.1 3 | resolver_port: 53 4 | tcp: True 5 | 6 | [DOMAIN] 7 | hostname: myself.net 8 | 9 | [DNS] 10 | srv_record: _onion-mx._tcp. 11 | 12 | [REROUTE] 13 | onion_transport: smtptor 14 | 15 | [IGNORED] 16 | domains: ignored.com 17 | -------------------------------------------------------------------------------- /onionrouter/custom_exceptions.py: -------------------------------------------------------------------------------- 1 | class ConfigError(Exception): 2 | pass 3 | 4 | 5 | class ConfigNotFoundError(ConfigError): 6 | pass 7 | 8 | 9 | class ConfigIntegrityError(ConfigError): 10 | pass 11 | -------------------------------------------------------------------------------- /onionrouter/lookups.py: -------------------------------------------------------------------------------- 1 | from dns.resolver import Resolver 2 | import re 3 | 4 | 5 | class OnionServiceLookup(object): 6 | def __init__(self, config): 7 | self.config = config 8 | self.resolver = Resolver() 9 | self.resolver.nameservers = self.config.get("RESOLVER", 10 | "resolver_ip").split(",") 11 | self.resolver.port = int(self.config.get("RESOLVER", "resolver_port")) 12 | 13 | @property 14 | def srv_record(self): 15 | return self.config.get("DNS", "srv_record") 16 | 17 | @property 18 | def use_tcp(self): 19 | return self.config.get("RESOLVER", "tcp") == "True" 20 | 21 | @staticmethod 22 | def is_onion(response): 23 | return re.search(r"onion\.$", response) is not None 24 | 25 | def _craft_format_record(self, domain): 26 | return "{0}{1}".format(self.srv_record, domain) 27 | 28 | def _map_answers(self, answers): 29 | return tuple(filter(self.is_onion, (str(x.target) for x in answers))) 30 | 31 | @staticmethod 32 | def _craft_output(answers): 33 | if answers: 34 | return tuple("{data}".format(data=str(x).rstrip('.')) 35 | for x in answers) 36 | else: 37 | return tuple("") 38 | 39 | def lookup(self, domain): 40 | query = self.resolver.query(self._craft_format_record(domain), 41 | "SRV", tcp=self.use_tcp) 42 | return self._craft_output(self._map_answers(query)) 43 | -------------------------------------------------------------------------------- /onionrouter/msockets.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import socket 3 | import multiprocessing 4 | import atexit 5 | from onionrouter import olib 6 | 7 | 8 | def close_socket(sock): 9 | sock.shutdown(socket.SHUT_RDWR) 10 | sock.close() 11 | 12 | 13 | def resolve(rerouter, conn, resolve_callback=lambda q, a: (q, a)): 14 | try: 15 | while True: 16 | addr = conn.recv(1024).decode().strip() 17 | if not addr: 18 | # connection ended 19 | return 20 | if addr == 'get *': 21 | conn.sendall( 22 | "500 Request key is not an email address\n".encode() 23 | ) 24 | else: 25 | result = rerouter.run(addr) 26 | resolve_callback(addr, result) 27 | conn.sendall("{0}\n".format(result).encode()) 28 | except socket.timeout: 29 | return 30 | except BaseException as err: 31 | # todo log 32 | conn.sendall("500 {0}\n".format(err).encode()) 33 | 34 | 35 | def daemonize_server(rerouter, host, port, resolver=resolve): 36 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 37 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 38 | sock.bind((host, port)) 39 | sock.listen(1) 40 | atexit.register(close_socket, sock=sock) 41 | while True: 42 | conn, address = sock.accept() 43 | process = multiprocessing.Process(target=resolver, 44 | args=(rerouter, conn)) 45 | process.daemon = True 46 | process.start() 47 | 48 | 49 | def client(host, port): 50 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 51 | sock.connect((host, port)) 52 | atexit.register(close_socket, sock=sock) 53 | while True: 54 | addr = olib.cross_input("Enter an email address: ") 55 | if addr == 'get *': 56 | print("500 Request key is not an email address") 57 | else: 58 | sock.sendall(addr.encode()) 59 | print(sock.recv(1024).decode().strip()) 60 | -------------------------------------------------------------------------------- /onionrouter/olib.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import re 4 | 5 | 6 | def cross_input(text): 7 | """ 8 | Returns the correct input function callback to be used for python 3.x 9 | and python 2.x 10 | """ 11 | if sys.version_info[0] < 3: 12 | return raw_input(text) # noqa 13 | return input(text) 14 | 15 | 16 | def get_full_path_files_for_dir(directory_path): 17 | """ 18 | List all filepaths in a dir recursively 19 | """ 20 | return set([os.path.join(dirpath, fname) for dirpath, dirnames, filenames 21 | in tuple(os.walk(directory_path)) 22 | for fname in filenames]) 23 | 24 | 25 | def find_file(directory_path, filename): 26 | """ 27 | Find filename in a dir recursively 28 | """ 29 | return next((x for x in get_full_path_files_for_dir(directory_path) 30 | if filename == os.path.split(x)[-1]), None) 31 | 32 | 33 | def find_files_with_suffix(directory_path, suffix): 34 | all_files = get_full_path_files_for_dir(directory_path) 35 | matcher = re.compile(r".*\.{suffix}$".format(suffix=suffix), re.IGNORECASE) 36 | return (a_file for a_file in all_files if re.match(matcher, a_file)) 37 | -------------------------------------------------------------------------------- /onionrouter/rerouter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | import argparse 4 | from collections import namedtuple 5 | from functools import partial 6 | from pkg_resources import resource_filename 7 | import sys 8 | from socket import error as socket_error 9 | from onionrouter.lookups import OnionServiceLookup 10 | from onionrouter import (config_handlers, custom_exceptions as exc, 11 | msockets, olib, routers) 12 | 13 | 14 | default_config_path = "/etc/onionrouter/" 15 | default_mappings_path = "/etc/onionrouter/mappings" 16 | 17 | 18 | class OnionRouter(object): 19 | ref_config = (olib.find_file("/etc/onionrouter/", "onionrouter.ini") 20 | or resource_filename("onionrouter", 21 | "configs/onionrouter.ini")) 22 | rerouters = namedtuple('rerouters', ('lazy', 'onion')) 23 | 24 | def __init__(self, config_path, map_path=""): 25 | self.config_file = config_handlers.get_conffile(config_path, 26 | prefix="onionrouter") 27 | self.mappings_path = map_path 28 | config_handlers.ConfigIntegrityChecker( 29 | ref_config=self.ref_config, other_config=self.config_file).verify() 30 | self.config = config_handlers.config_reader(self.config_file) 31 | self.rerouters = self.rerouters( 32 | lazy=routers.LazyPostfixRerouter(self.config, self.mappings_path), 33 | onion=routers.OnionPostfixRerouter( 34 | self.config, OnionServiceLookup(self.config))) 35 | 36 | @property 37 | def myname(self): 38 | return [x.strip().upper() for x in self.config.get( 39 | "DOMAIN", "hostname").split(",")] 40 | 41 | @property 42 | def ignored_domains(self): 43 | return [x.strip().upper() for x in self.config.get( 44 | "IGNORED", "domains").split(",")] 45 | 46 | @staticmethod 47 | def get_domain(address): 48 | split_addr = address.split("@") 49 | if address.count("@") != 1 or split_addr[1] == "": 50 | raise RuntimeError 51 | return split_addr[1] 52 | 53 | def reroute(self, domain): 54 | if domain.upper() in self.myname: 55 | return tuple(["200 :"]) 56 | elif domain.upper() in self.ignored_domains: 57 | return tuple(["500 Domain is in ignore list"]) 58 | else: 59 | return (self.rerouters.lazy.reroute(domain) 60 | or self.rerouters.onion.reroute(domain)) 61 | 62 | def run(self, address): 63 | try: 64 | domain = self.get_domain(address) 65 | except RuntimeError: 66 | return "500 Request key is not an email address" 67 | routing = self.reroute(domain) 68 | # in the end, there can be only one response 69 | return routing[0] if routing else "500 Not found" 70 | 71 | 72 | class OnionRouterRunner(object): 73 | 74 | @staticmethod 75 | def add_arguments(): 76 | parser = argparse.ArgumentParser( 77 | description='onionrouter daemon for postifx rerouting') 78 | parser.add_argument('--interactive', '-i', default=False, 79 | action='store_true', 80 | help='Simple test route mode without daemon') 81 | parser.add_argument('--debug', '-d', default=False, 82 | action='store_true', 83 | help='Debug mode. Run daemon and also print the ' 84 | 'queries & replies') 85 | parser.add_argument('--client', '-c', default=False, 86 | action='store_true', 87 | help='Client mode. Connect as a client to daemon ' 88 | 'for testing / debug') 89 | parser.add_argument('--config', default=default_config_path, 90 | help='Absolute path to config folder/file ' 91 | '(default: %(default)s)', type=str) 92 | parser.add_argument('--mappings', default=default_mappings_path, 93 | help='Absolute path to static mappings folder/file' 94 | ' (default: %(default)s)', type=str) 95 | parser.add_argument('--host', '-l', default="127.0.0.1", 96 | help="Host for daemon to listen " 97 | "(default: %(default)s)", type=str) 98 | parser.add_argument('--port', '-p', default=23000, type=int, 99 | help="Port for daemon to listen " 100 | "(default: %(default)s)") 101 | return parser 102 | 103 | @staticmethod 104 | def interactive_reroute(onion_router): 105 | while True: 106 | addr = olib.cross_input("Enter an email address: ") 107 | if addr == 'get *': 108 | print("500 Request key is not an email address") 109 | else: 110 | print(onion_router.run(addr)) 111 | 112 | @staticmethod 113 | def reroute_debugger(question, answer): 114 | print("[Q]: {q}\n" 115 | "[A]: {a}".format(q=question, a=answer)) 116 | 117 | @staticmethod 118 | def craft_resolver(callback): 119 | return partial(msockets.resolve, resolve_callback=callback) 120 | 121 | @staticmethod 122 | def validate_flag_arguments(*flags): 123 | if len([x for x in flags if x]) > 1: 124 | raise RuntimeWarning("Cannot use multiple modes. " 125 | "Choose only one mode") 126 | 127 | def main(self): 128 | args = self.add_arguments().parse_args() 129 | self.validate_flag_arguments(args.interactive, args.client, args.debug) 130 | onion_router = OnionRouter(config_path=args.config, 131 | map_path=args.mappings) 132 | 133 | if args.interactive: 134 | self.interactive_reroute(onion_router) 135 | 136 | if args.client: 137 | msockets.client(args.host, args.port) 138 | else: 139 | resolver = self.craft_resolver(self.reroute_debugger 140 | if args.debug 141 | else lambda *args: args) 142 | msockets.daemonize_server(onion_router, args.host, args.port, 143 | resolver=resolver) 144 | 145 | 146 | def main(): 147 | try: 148 | OnionRouterRunner().main() 149 | except (exc.ConfigError, socket_error, RuntimeWarning) as err: 150 | print(err) 151 | sys.exit(1) 152 | except KeyboardInterrupt: 153 | sys.exit(0) 154 | 155 | 156 | if __name__ == '__main__': 157 | main() 158 | -------------------------------------------------------------------------------- /onionrouter/routers.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod, ABCMeta 2 | from dns import exception as dnsexception 3 | from onionrouter.config_handlers import load_yamls 4 | 5 | 6 | class PostfixRerouter(object): 7 | __metaclass__ = ABCMeta 8 | 9 | def __init__(self, config): 10 | self.config = config 11 | 12 | @property 13 | def reroute_service(self): 14 | return self.config.get("REROUTE", "onion_transport") 15 | 16 | @abstractmethod 17 | def reroute(self, answers): 18 | return tuple("200 {rservice}:[{ans}]".format( 19 | rservice=self.reroute_service, ans=x) for x in answers) 20 | 21 | 22 | class LazyPostfixRerouter(PostfixRerouter): 23 | def __init__(self, config, yaml_path): 24 | super(LazyPostfixRerouter, self).__init__(config) 25 | self.mappings_path = yaml_path 26 | self.static_mappings = dict() 27 | 28 | def _setup_static_mappings(self): 29 | for (key, vals) in load_yamls(self.mappings_path).items(): 30 | self.static_mappings.update({x.upper(): key for x in vals}) 31 | 32 | def _lazy(self, domain): 33 | mapping = self.static_mappings.get(domain.upper()) 34 | return tuple([mapping]) if mapping else tuple() 35 | 36 | def reroute(self, domain): 37 | if not self.static_mappings: 38 | self._setup_static_mappings() 39 | return super(LazyPostfixRerouter, self).reroute(self._lazy(domain)) 40 | 41 | 42 | class OnionPostfixRerouter(PostfixRerouter): 43 | def __init__(self, config, onion_resolver): 44 | super(OnionPostfixRerouter, self).__init__(config) 45 | self.onion_resolver = onion_resolver 46 | 47 | def reroute(self, domain): 48 | try: 49 | return super(OnionPostfixRerouter, self).reroute( 50 | self.onion_resolver.lookup(domain)) 51 | except dnsexception.DNSException as err: 52 | return tuple(("500 {0}".format(str(err) or "Not found"),)) 53 | -------------------------------------------------------------------------------- /onionrouter_run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Convenience wrapper for running onionrouter directly from source tree.""" 4 | from onionrouter.rerouter import main 5 | 6 | 7 | if __name__ == '__main__': 8 | main() 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dnspython==2.0.0 2 | PyYAML==6.0 3 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip==21.1 2 | bumpversion==0.6.0 3 | wheel==0.38.1 4 | watchdog==0.10.3 5 | flake8==3.8.4 6 | tox==3.20.1 7 | coverage==5.3 8 | Sphinx==3.2.1 9 | cryptography==41.0.3 10 | pytest==6.1.1 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.6.2 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:onionrouter/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup 5 | 6 | with open('README.rst') as readme_file: 7 | readme = readme_file.read() 8 | 9 | with open('HISTORY.rst') as history_file: 10 | history = history_file.read() 11 | 12 | requirements = [ 13 | "dnspython>=2.0.0,<3.0.0", 14 | "PyYAML>=4.2b1", 15 | ] 16 | 17 | test_requirements = [ 18 | "pip>=19.2", 19 | "bumpversion==0.5.3", 20 | "wheel==0.38.1", 21 | "watchdog==0.8.3", 22 | "flake8==2.6.0", 23 | "tox==2.3.1", 24 | "coverage==4.1", 25 | "Sphinx==1.4.8", 26 | "cryptography==41.0.3", 27 | "PyYAML==6.0", 28 | "pytest==2.9.2" 29 | ] 30 | 31 | setup( 32 | name='onionrouter', 33 | version='0.6.2', 34 | description="Python Onion Routed Mail Deliveries", 35 | long_description=readme, 36 | author="Ehlo Onion", 37 | author_email='onionmx@lists.immerda.ch', 38 | url='https://github.com/ehloonion/onionrouter', 39 | packages=[ 40 | 'onionrouter', 41 | ], 42 | entry_points={ 43 | "console_scripts": ['onionrouter = onionrouter.rerouter:main'] 44 | }, 45 | include_package_data=True, 46 | install_requires=requirements, 47 | license="GNU General Public License v3", 48 | zip_safe=False, 49 | keywords='onionrouter', 50 | python_requires='>=3.6', 51 | classifiers=[ 52 | 'Development Status :: 4 - Beta', 53 | 'Intended Audience :: Developers', 54 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 55 | 'Natural Language :: English', 56 | 'Programming Language :: Python :: 3', 57 | 'Programming Language :: Python :: 3.6', 58 | 'Programming Language :: Python :: 3.7', 59 | 'Programming Language :: Python :: 3.8', 60 | 'Programming Language :: Python :: 3.9', 61 | 'Programming Language :: Python :: 3.10', 62 | 'Programming Language :: Python :: 3.11', 63 | 'Programming Language :: Python :: 3.12', 64 | ], 65 | test_suite='tests', 66 | tests_require=test_requirements 67 | ) 68 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /tests/test_onionrouter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | test_onionrouter 6 | ---------------------------------- 7 | 8 | Tests for `onionrouter` module. 9 | """ 10 | 11 | import pytest 12 | from onionrouter.routers import OnionPostfixRerouter 13 | 14 | 15 | class TestOnionRouter(object): 16 | def test_multiple_hostnames_support(self, dummy_onionrouter): 17 | assert len(dummy_onionrouter.myname) > 1 18 | 19 | def test_hostname_is_upper(self, dummy_onionrouter): 20 | assert all(map(lambda x: x.isupper(), dummy_onionrouter.myname)) 21 | 22 | def test_get_domain_multiple_at(self, dummy_onionrouter): 23 | with pytest.raises(RuntimeError): 24 | dummy_onionrouter.get_domain("lalla@lalala.com@lalal.net") 25 | 26 | def test_get_domain_no_email(self, dummy_onionrouter): 27 | with pytest.raises(RuntimeError): 28 | dummy_onionrouter.get_domain("lalla") 29 | 30 | def test_get_domain_correct_email(self, dummy_onionrouter): 31 | assert dummy_onionrouter.get_domain("lol@test.com") == "test.com" 32 | 33 | def test_reroute_local_domain(self, dummy_onionrouter): 34 | assert dummy_onionrouter.reroute("myself.net") == tuple(["200 :"]) 35 | 36 | def test_reroute_no_lazy_config(self, dummy_onionrouter, 37 | monkeypatch): 38 | dummy_answer = tuple(["test"]) 39 | monkeypatch.setattr(OnionPostfixRerouter, "reroute", 40 | lambda *args: dummy_answer) 41 | assert dummy_onionrouter.reroute("testme") == dummy_answer 42 | 43 | def test_reroute_run_wrong_domain(self, dummy_onionrouter): 44 | assert (dummy_onionrouter.run("llalalla") 45 | == "500 Request key is not an email address") 46 | 47 | def test_reroute_multiple_findings(self, dummy_onionrouter, 48 | monkeypatch): 49 | dummy_answer = tuple(["test1", "test2", "test3"]) 50 | monkeypatch.setattr(OnionPostfixRerouter, "reroute", 51 | lambda *args: dummy_answer) 52 | assert dummy_onionrouter.run("t@t.c") == dummy_answer[0] 53 | 54 | def test_ignored_domains_exist(self, dummy_onionrouter): 55 | assert dummy_onionrouter.ignored_domains 56 | 57 | def test_ignored_domains_case_insensitive(self, dummy_onionrouter): 58 | assert all(map(lambda x: x.isupper(), 59 | dummy_onionrouter.ignored_domains)) 60 | 61 | def test_ignored_domain_rerouting(self, dummy_onionrouter): 62 | assert (dummy_onionrouter.run("please@iGnORe.Me") 63 | == "500 Domain is in ignore list") 64 | 65 | def test_check_local_domain_before_ignored(self, dummy_onionrouter): 66 | assert dummy_onionrouter.run("m@myself.net") == "200 :" 67 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26, py27, py34, py35, flake8, py 3 | 4 | [testenv:flake8] 5 | basepython=python 6 | deps=flake8 7 | commands=flake8 onionrouter 8 | 9 | [testenv] 10 | setenv = 11 | PYTHONPATH = {toxinidir}:{toxinidir}/onionrouter 12 | deps = 13 | -r{toxinidir}/requirements.txt 14 | -r{toxinidir}/requirements_dev.txt 15 | commands = 16 | pip install -U pip 17 | py.test --basetemp={envtmpdir} 18 | 19 | --------------------------------------------------------------------------------