├── .dockerignore ├── .github └── workflows │ ├── bake_to_latest.yml │ ├── bake_to_next.yml │ └── bake_to_test.yml ├── .gitignore ├── .readthedocs.yaml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── charts └── py-kms │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── hpa.yaml │ ├── ingress.yaml │ ├── service.yaml │ ├── serviceaccount.yaml │ └── tests │ │ └── test-connection.yaml │ └── values.yaml ├── docker ├── README.md ├── docker-py3-kms-minimal │ ├── Dockerfile │ └── requirements.txt ├── docker-py3-kms │ ├── Dockerfile │ └── requirements.txt ├── entrypoint.py ├── healthcheck.py └── start.py ├── docs ├── Contributing.md ├── Documentation.md ├── Getting Started.md ├── Keys.md ├── Makefile ├── Troubleshooting.md ├── Usage.md ├── changelog.md ├── conf.py ├── img │ ├── off1.png │ ├── off2.png │ ├── off3.png │ ├── off4.png │ ├── webinterface.png │ ├── win1.png │ └── win2.png ├── index.rst ├── make.bat ├── readme.md └── requirements.txt ├── py-kms ├── KmsDataBase.xml ├── pykms_Aes.py ├── pykms_Base.py ├── pykms_Client.py ├── pykms_Connect.py ├── pykms_DB2Dict.py ├── pykms_Dcerpc.py ├── pykms_Filetimes.py ├── pykms_Format.py ├── pykms_Misc.py ├── pykms_PidGenerator.py ├── pykms_RequestUnknown.py ├── pykms_RequestV4.py ├── pykms_RequestV5.py ├── pykms_RequestV6.py ├── pykms_RpcBase.py ├── pykms_RpcBind.py ├── pykms_RpcRequest.py ├── pykms_Server.py ├── pykms_Sql.py ├── pykms_Structure.py ├── pykms_WebUI.py ├── static │ └── css │ │ └── bulma.min.css └── templates │ ├── base.html │ ├── clients.html │ ├── license.html │ └── products.html └── requirements.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | log/ 2 | .idea 3 | .github 4 | *.db 5 | *.yml 6 | *.md 7 | *.sh 8 | Makefile 9 | -------------------------------------------------------------------------------- /.github/workflows/bake_to_latest.yml: -------------------------------------------------------------------------------- 1 | name: Build release-tags 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | bake-latest: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | packages: write 14 | contents: read 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2.3.4 18 | - name: Set up QEMU 19 | uses: docker/setup-qemu-action@v1 20 | with: 21 | platforms: all 22 | - name: Set up Docker Buildx 23 | uses: docker/setup-buildx-action@v1.6.0 24 | - name: Login to GitHub Container Registry 25 | uses: docker/login-action@v1.10.0 26 | with: 27 | registry: ghcr.io 28 | username: ${{ github.actor }} 29 | password: ${{ secrets.GITHUB_TOKEN }} 30 | - name: Build 31 | uses: docker/build-push-action@v2 32 | with: 33 | context: . 34 | file: ./docker/docker-py3-kms/Dockerfile 35 | platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 36 | push: true 37 | tags: ghcr.io/py-kms-organization/py-kms:python3 38 | build-args: | 39 | BUILD_COMMIT=${{ github.sha }} 40 | BUILD_BRANCH=${{ github.ref_name }} 41 | - name: Build 42 | uses: docker/build-push-action@v2 43 | with: 44 | context: . 45 | file: ./docker/docker-py3-kms-minimal/Dockerfile 46 | platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 47 | push: true 48 | tags: ghcr.io/py-kms-organization/py-kms:latest,ghcr.io/py-kms-organization/py-kms:minimal 49 | build-args: | 50 | BUILD_COMMIT=${{ github.sha }} 51 | BUILD_BRANCH=${{ github.ref_name }} 52 | -------------------------------------------------------------------------------- /.github/workflows/bake_to_next.yml: -------------------------------------------------------------------------------- 1 | name: Build next-tags 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - next 8 | 9 | jobs: 10 | bake-next: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | packages: write 14 | contents: read 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2.3.4 18 | - name: Set up QEMU 19 | uses: docker/setup-qemu-action@v1 20 | with: 21 | platforms: all 22 | - name: Set up Docker Buildx 23 | uses: docker/setup-buildx-action@v1.6.0 24 | - name: Login to GitHub Container Registry 25 | uses: docker/login-action@v1.10.0 26 | with: 27 | registry: ghcr.io 28 | username: ${{ github.actor }} 29 | password: ${{ secrets.GITHUB_TOKEN }} 30 | - name: Build 31 | uses: docker/build-push-action@v2 32 | with: 33 | context: . 34 | file: ./docker/docker-py3-kms/Dockerfile 35 | platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 36 | push: true 37 | tags: ghcr.io/py-kms-organization/py-kms:python3-next 38 | build-args: | 39 | BUILD_COMMIT=${{ github.sha }} 40 | BUILD_BRANCH=${{ github.ref_name }} 41 | - name: Build 42 | uses: docker/build-push-action@v2 43 | with: 44 | context: . 45 | file: ./docker/docker-py3-kms-minimal/Dockerfile 46 | platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 47 | push: true 48 | tags: ghcr.io/py-kms-organization/py-kms:latest-next,ghcr.io/py-kms-organization/py-kms:minimal-next 49 | build-args: | 50 | BUILD_COMMIT=${{ github.sha }} 51 | BUILD_BRANCH=${{ github.ref_name }} 52 | -------------------------------------------------------------------------------- /.github/workflows/bake_to_test.yml: -------------------------------------------------------------------------------- 1 | name: Test-Build Docker Image 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | 7 | jobs: 8 | bake-test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2.3.4 13 | - name: Set up QEMU 14 | uses: docker/setup-qemu-action@v1 15 | with: 16 | platforms: all 17 | - name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v1.6.0 19 | - name: Build 20 | uses: docker/build-push-action@v2 21 | with: 22 | context: . 23 | file: ./docker/docker-py3-kms/Dockerfile 24 | platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 25 | push: false 26 | build-args: | 27 | BUILD_COMMIT=${{ github.sha }} 28 | BUILD_BRANCH=${{ github.ref_name }} 29 | - name: Build 30 | uses: docker/build-push-action@v2 31 | with: 32 | context: . 33 | file: ./docker/docker-py3-kms-minimal/Dockerfile 34 | platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 35 | push: false 36 | build-args: | 37 | BUILD_COMMIT=${{ github.sha }} 38 | BUILD_BRANCH=${{ github.ref_name }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # App files 2 | pykms_logserver.log* 3 | pykms_logclient.log* 4 | pykms_database.db* 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | pip-wheel-metadata/ 29 | share/python-wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don’t work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # celery beat schedule file 98 | celerybeat-schedule 99 | 100 | # SageMath parsed files 101 | *.sage.py 102 | 103 | # Environments 104 | .env 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | .dmypy.json 125 | dmypy.json 126 | 127 | # Pyre type checker 128 | .pyre/ 129 | 130 | # Helm 131 | charts/*/*.tgz 132 | /.idea/ 133 | docker-compose-*.yml 134 | *.sh 135 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: "ubuntu-22.04" 5 | tools: 6 | python: "3.10" 7 | 8 | python: 9 | install: 10 | - requirements: docs/requirements.txt 11 | 12 | formats: all 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## py-kms_2022-12-16 4 | - Added support for new web-gui into Docker 5 | - Implemented whole-new web-based GUI with Flask 6 | - Removed old GUI (Etrigan) from code and resources 7 | - Removed sqliteweb 8 | - Removed Etrigan (GUI) 9 | 10 | ## py-kms_2022-12-07 11 | - Added warning about Etrigan (GUI) being deprecated 12 | - More docs (do not run on same machine as client) 13 | - Added Docker support for multiple listen IPs 14 | - Added graceful Docker shutdowns 15 | 16 | ## py-kms_2021-12-23 17 | - More Windows 10/11 keys 18 | - Fixed some deprecation warnings 19 | - Fixed SO_REUSEPORT platform checks 20 | - Fixed loglevel "MININFO" with Docker 21 | - Added Docker healthcheck 22 | - Added UID/GID change support for Docker 23 | - Dependabot alerts 24 | 25 | ## py-kms_2021-10-22 26 | - Integrated Office 2021 GLVK keys & database 27 | - Docker entrypoint fixes 28 | - Updated docs to include SQLite stuff 29 | - Fix for undefined timezones 30 | - Removed LOGFILE extension checks 31 | - Added support for Windows 11 32 | 33 | ## py-kms_2021-10-07 34 | - Helm charts for Kubernetes deployment 35 | - Windows 2022 updates 36 | - Faster Github Action builds 37 | 38 | ## py-kms_2021-11-12 39 | - Addded GHCR support 40 | - Docs table reformatted 41 | - Updated GUI 42 | - Windows Sandbox fix 43 | - Added contribution guidelines 44 | - Docker multiarch 45 | - Reshot screenshots in docs 46 | 47 | ## py-kms_2020-10-01 48 | - Sql database path customizable. 49 | - Sql database file keeps different AppId. 50 | - Support for multi-address connection. 51 | - Added timeout send / receive. 52 | 53 | ## py-kms_2020-07-01 54 | - py-kms Gui: now matches all cli options, added modes onlyserver / onlyclient, 55 | added some animations. 56 | - Added suboptions FILEOFF and STDOUTOFF of -F. 57 | - Created option for asynchronous messages. 58 | - Cleaned options parsing process. 59 | 60 | ## py-kms_2020-02-02 61 | - Optimized pretty-print messages process. 62 | - Added -F FILESTDOUT option. 63 | - Added deamonization options (via [Etrigan](https://github.com/SystemRage/Etrigan) project). 64 | - py-kms GUI resurrected (and improved). 65 | - Cleaned, cleaned, cleaned. 66 | 67 | ## py-kms_2019-05-15 68 | - Merging for Python2 / Python3 compatibility all-in-one. 69 | - Added new options: 70 | - timeout, [logsize](https://github.com/SystemRage/py-kms/pull/21). 71 | - more control on logging and info visualization (custom loglevel and stdout logfile redirection) to match [this](https://github.com/SystemRage/py-kms/issues/22) request. 72 | - Setup for multithreading support. 73 | - Graphical improvements: 74 | - added a (_"really silly"_) tkinter GUI as an alternative to command line. 75 | - [Dockerized](https://github.com/SystemRage/py-kms/pull/20) with sqlite-web. 76 | - Fixed activation threshold. 77 | - Renamed files, cosmetics and many other little big adjustments. 78 | 79 | ## py-kms_2018-11-15 80 | - Implemented some good modifications inspired by [this](https://github.com/ThunderEX/py-kms) other fork. 81 | - Clean up code ( deleted no longer useful files randomHWID.py, randomEPID.py, timezones.py; 82 | erased useless functions and import modules ) 83 | - Reading parameters directly from a slightly modified KmsDataBase.xml ( created with LicenseManager 5.0 by Hotbird64 HGM ) with kmsDB2Dict.py 84 | - Added support for Windows Server 2019 and Office 2019. 85 | - Improved random EPID generation. 86 | - Corrected [this](https://github.com/SystemRage/py-kms/issues/8) in kmsBase.py 87 | 88 | ## py-kms_2018-03-01 89 | - *py-kms NOW is for Python3 too ( py3-kms ), the previous one ( written with Python2 ) is renamed py2-kms* 90 | - *Repaired logging messages* 91 | - *Added pretty processing messages* 92 | 93 | ## py-kms_2017-06-01 94 | - *Added option verbose logging in a file* 95 | - *Updated "kmsBase.py" with new SKUIDs* 96 | - *Added a brief guide "py-kms-Guide.pdf" ( replaced "client-activation.txt" )* 97 | - *Added a well formatted and more complete list of volume keys "py-kms-ClientKeys.pdf" ( replaced "client-keys.txt" )* 98 | 99 | ## py-kms_2016-12-30 100 | - *Updated kmsBase.py (Matches LicenseManager 4.6.0 by Hotbird64 HGM)* 101 | 102 | ## py-kms_2016-08-13 103 | - *Fixed major bug on Response function* 104 | - *Fixed Random PID Generator (thanks: mkuba50)* 105 | 106 | ## py-kms_2016-08-12 107 | - *Added missing UUID, credits: Hotbird64* 108 | - *Added Windows Server 2016 in random PID generator* 109 | 110 | ## py-kms_2016-08-11 111 | - *Added Windows Server 2016 UUID* 112 | - *Fixed GroupID and PIDRange* 113 | - *Added Office 2016 CountKMSID* 114 | 115 | ## py-kms_2015-07-29 116 | - *Added Windows 10 UUID* 117 | 118 | ## py-kms_2014-10-13 build 3: 119 | - *Added Client Activation Examples: "client-activation.txt"* 120 | - *Added Volume Keys: "client-keys.txt"* 121 | 122 | ## py-kms_2014-10-13 build 2: 123 | - *Added missing skuIds in file "kmsbase.py". Thanks (user_hidden)* 124 | 125 | ## py-kms_2014-10-13 build 1: 126 | - *The server now outputs the hwid in use.* 127 | - *The server hwid can be random by using parameter: "-w random". Example: "python server.py -w random"* 128 | - *Included file "randomHWID.py" to generate random hwid on demand.* 129 | - *Included file "randomPID.py" to generate random epid and hwid on demand.* 130 | 131 | ## py-kms_2014-03-21T232943Z: 132 | - *The server HWID can now be specified on the command line.* 133 | - *The client will print the HWID when using the v6 protocol.* 134 | 135 | ## py-kms_2014-01-03T032458Z: 136 | - *Made the sqlite3 module optional.* 137 | - *Changed the "log" flag to an "sqlite" flag and made a real log flag in preparation for when real request logging is implemented.* 138 | 139 | ## py-kms_2014-01-03T025524Z: 140 | - *Added RPC response decoding to the KMS client emulator.* 141 | 142 | ## py-kms_2013-12-30T064443Z: 143 | - *The v4 hash now uses the proper pre-expanded key.* 144 | 145 | ## py-kms_2013-12-28T073506Z: 146 | - *Modified the v4 code to use the custom aes module in order to make it more streamlined and efficient.* 147 | 148 | ## py-kms_2013-12-20T051257Z: 149 | - *Removed the need for the pre-computed table (tablecomplex.py) for v4 CMAC calculation, cutting the zip file size in half.* 150 | 151 | ## py-kms_2013-12-16T214638Z: 152 | - *Switched to getting the to-be-logged request time from the KMS server instead of the client.* 153 | 154 | ## py-kms_2013-12-16T030001Z: 155 | - *You can now specify the CMID and the Machine Name to use with the client emulator.* 156 | 157 | ## py-kms_2013-12-16T021215Z: 158 | - *Added a request-logging feature to the server. It stores requests in an SQLite database and uses the ePIDs stored there on a per-CMID basis.* 159 | - *The client emulator now works for v4, v5, and v6 requests.* 160 | - *The client emulator now also verifies the KMS v4 responses it receives.* 161 | 162 | ## py-kms_2013-12-14T230215Z 163 | - *Added a client (work in progress) that right now can only generate and send RPC bind requests.* 164 | - *Added a bunch of new classes to handle RPC client stuff, but I might just end up moving their functions back into the old classes at some point.* 165 | - *Lots of other code shuffling.* 166 | - *Made the verbose and debug option help easier to read.* 167 | - *Added some server error messages.* 168 | 169 | ## py-kms_2013-12-08T051332Z: 170 | - *Made some really huge internal changes to streamline packet parsing.* 171 | 172 | ## py-kms_2013-12-06T034100Z: 173 | - *Added tons of new SKU IDs* 174 | 175 | ## py-kms_2013-12-05T044849Z: 176 | - *Added Office SKU IDs* 177 | - *Small internal changes* 178 | 179 | ## py-kms_2013-12-04T010942Z: 180 | - *Made the rpcResponseArray in rpcRequest output closer to spec* 181 | 182 | ## py-kms_2013-12-01T063938Z: 183 | - *SKUID conversion: Converts the SKUID UUID into a human-readable product version for SKUIDs in its SKUID dictionary.* 184 | - *Fancy new timezone conversion stuff.* 185 | - *Enabled setting custom LCIDs.* 186 | - *Data parsing is now handled by structure.py.* 187 | - *Some other minor stuff you probably won't notice.* 188 | 189 | ## py-kms_2013-11-27T061658Z: 190 | - *Got rid of custom functions module (finally)* 191 | 192 | ## py-kms_2013-11-27T054744Z: 193 | - *Simplified custom functions module* 194 | - *Got rid of "v4" subfolder* 195 | - *Cleaned up a bunch of code* 196 | 197 | ## py-kms_2013-11-23T044244Z: 198 | - *Added timestamps to verbose output* 199 | - *Made the verbose output look better* 200 | 201 | ## py-kms_2013-11-21T014002Z: 202 | - *Moved some stuff into verbose output* 203 | - *Enabled server ePIDs of arbitrary length* 204 | 205 | ## py-kms_2013-11-20T180347Z: 206 | - *Permanently fixed machineName decoding* 207 | - *Adheres closer to the DCE/RPC protocol spec* 208 | - *Added client info to program output* 209 | - *Small formatting changes* 210 | 211 | ## py-kms_2013-11-13: 212 | - *First working release added to the Mega folder.* 213 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Readme 2 | ![repo-size](https://img.shields.io/github/repo-size/Py-KMS-Organization/py-kms) 3 | ![open-issues](https://img.shields.io/github/issues/Py-KMS-Organization/py-kms) 4 | ![last-commit](https://img.shields.io/github/last-commit/Py-KMS-Organization/py-kms/master) 5 | ![read-the-docs](https://img.shields.io/readthedocs/py-kms) 6 | *** 7 | 8 | _Keep in mind that this project is not intended for production use. Feel free to use it to test your own systems or maybe even learn something from the protocol structure. :)_ 9 | 10 | ## History 11 | _py-kms_ is a port of node-kms created by [cyrozap](http://forums.mydigitallife.info/members/183074-markedsword), which is a port of either the C#, C++, or .NET implementations of KMS Emulator. The original version was written by [CODYQX4](http://forums.mydigitallife.info/members/89933-CODYQX4) and is derived from the reverse-engineered code of Microsoft's official KMS. 12 | This version of _py-kms_ is for itself a fork of the original implementation by [SystemRage](https://github.com/SystemRage/py-kms), which was abandoned early 2021. 13 | 14 | ## Features 15 | - Responds to `v4`, `v5`, and `v6` KMS requests. 16 | - Supports activating: 17 | - Windows Vista 18 | - Windows 7 19 | - Windows 8 20 | - Windows 8.1 21 | - Windows 10 ( 1511 / 1607 / 1703 / 1709 / 1803 / 1809 ) 22 | - Windows 10 ( 1903 / 1909 / 20H1, 20H2, 21H1, 21H2 ) 23 | - Windows 11 ( 21H2 ) 24 | - Windows Server 2008 25 | - Windows Server 2008 R2 26 | - Windows Server 2012 27 | - Windows Server 2012 R2 28 | - Windows Server 2016 29 | - Windows Server 2019 30 | - Windows Server 2022 31 | - Microsoft Office 2010 ( Volume License ) 32 | - Microsoft Office 2013 ( Volume License ) 33 | - Microsoft Office 2016 ( Volume License ) 34 | - Microsoft Office 2019 ( Volume License ) 35 | - Microsoft Office 2021 ( Volume License ) 36 | - It's written in Python (tested with Python 3.10.1). 37 | - Supports execution by `Docker`, `systemd` and many more... 38 | - Uses `sqlite` for persistent data storage (with a simple web-based explorer). 39 | 40 | ## Documentation 41 | The wiki has been completly reworked and is now available on [readthedocs.io](https://py-kms.readthedocs.io/en/latest/). It should provide you all the necessary information about how to setup and to use _py-kms_ , all without clumping this readme. The documentation also houses more details about activation with _py-kms_ and how to get GVLK keys. 42 | 43 | ## Quick start 44 | - To start the server, execute `python3 pykms_Server.py [IPADDRESS] [PORT]`, the default _IPADDRESS_ is `::` ( all interfaces ) and the default _PORT_ is `1688`. Note that both the address and port are optional. It's allowed to use IPv4 and IPv6 addresses. If you have a IPv6-capable dual-stack OS, a dual-stack socket is created when using a IPv6 address. **In case your OS does not support IPv6, make sure to explicitly specify the legacy IPv4 of `0.0.0.0`!** 45 | - To start the server automatically using Docker, execute `docker run -d --name py-kms --restart always -p 1688:1688 ghcr.io/py-kms-organization/py-kms`. 46 | - To show the help pages type: `python3 pykms_Server.py -h` and `python3 pykms_Client.py -h`. 47 | 48 | ## License 49 | - _py-kms_ is [![Unlicense](https://img.shields.io/badge/license-unlicense-lightgray.svg)](./LICENSE) 50 | -------------------------------------------------------------------------------- /charts/py-kms/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/py-kms/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: py-kms 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "python3" 25 | -------------------------------------------------------------------------------- /charts/py-kms/README.md: -------------------------------------------------------------------------------- 1 | # py-kms 2 | 3 | ![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: python3](https://img.shields.io/badge/AppVersion-python3-informational?style=flat-square) 4 | 5 | A Helm chart for Kubernetes 6 | 7 | ## Deployment 8 | 9 | Below is a basic overview of the steps required to deploy the Helm chart to an existing Kubernetes cluster which is accessible via Kubectl 10 | 11 | ### Create py-kms Namespace (recommended) 12 | 13 | `kubectl create ns py-kms` 14 | 15 | ### Deploy chart with default values.yaml 16 | 17 | `helm install -n py-kms -f myvalues.yaml charts/py-kms` 18 | 19 | For more information please refer to the Helm Install command documentation located at: https://helm.sh/docs/helm/helm_install/ 20 | 21 | ## Values 22 | 23 | | Key | Type | Default | Description | 24 | |-----|------|---------|-------------| 25 | | affinity | object | `{}` | | 26 | | autoscaling.enabled | bool | `false` | | 27 | | autoscaling.maxReplicas | int | `100` | | 28 | | autoscaling.minReplicas | int | `1` | | 29 | | autoscaling.targetCPUUtilizationPercentage | int | `80` | | 30 | | fullnameOverride | string | `""` | | 31 | | image.pullPolicy | string | `"IfNotPresent"` | | 32 | | image.repository | string | `"ghcr.io/py-kms-organization/py-kms"` | | 33 | | image.tag | string | `"python3"` | | 34 | | imagePullSecrets | list | `[]` | | 35 | | ingress.annotations | object | `{}` | | 36 | | ingress.className | string | `""` | | 37 | | ingress.enabled | bool | `false` | | 38 | | ingress.hosts[0].host | string | `"chart-example.local"` | | 39 | | ingress.hosts[0].paths[0].path | string | `"/"` | | 40 | | ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | | 41 | | ingress.tls | list | `[]` | | 42 | | nameOverride | string | `""` | | 43 | | nodeSelector | object | `{}` | | 44 | | podAnnotations | object | `{}` | | 45 | | podSecurityContext | object | `{}` | | 46 | | py-kms.environment.HWID | string | `"RANDOM"` | | 47 | | py-kms.environment.IP | string | `"::"` | | 48 | | py-kms.environment.LOGLEVEL | string | `"INFO"` | | 49 | | py-kms.environment.LOGSIZE | int | `2` | | 50 | | replicaCount | int | `1` | | 51 | | resources | object | `{}` | | 52 | | securityContext | object | `{}` | | 53 | | service.httpPort | int | `80` | | 54 | | service.kmsPort | int | `1688` | | 55 | | service.type | string | `"ClusterIP"` | | 56 | | serviceAccount | object | `{}` | | 57 | | tolerations | list | `[]` | | 58 | 59 | ---------------------------------------------- 60 | Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) 61 | -------------------------------------------------------------------------------- /charts/py-kms/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "py-kms.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "py-kms.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "py-kms.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "py-kms.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /charts/py-kms/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "py-kms.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "py-kms.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "py-kms.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "py-kms.labels" -}} 37 | helm.sh/chart: {{ include "py-kms.chart" . }} 38 | {{ include "py-kms.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "py-kms.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "py-kms.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "py-kms.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "py-kms.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /charts/py-kms/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "py-kms.fullname" . }} 5 | labels: 6 | {{- include "py-kms.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "py-kms.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "py-kms.selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | serviceAccountName: {{ include "py-kms.serviceAccountName" . }} 28 | securityContext: 29 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 30 | containers: 31 | - name: {{ .Chart.Name }} 32 | securityContext: 33 | {{- toYaml .Values.securityContext | nindent 12 }} 34 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 35 | imagePullPolicy: {{ .Values.image.pullPolicy }} 36 | env: 37 | {{- range $key, $val := (index .Values "py-kms" "environment") }} 38 | - name: {{ $key }} 39 | value: {{ $val | quote }} 40 | {{- end }} 41 | ports: 42 | - name: kms 43 | containerPort: 1688 44 | protocol: TCP 45 | - name: http 46 | containerPort: 8080 47 | protocol: TCP 48 | startupProbe: 49 | httpGet: 50 | port: http 51 | path: /readyz 52 | failureThreshold: 30 # 30 seconds seem to be enough under heavy workloads 53 | periodSeconds: 1 54 | livenessProbe: 55 | httpGet: 56 | path: /livez 57 | port: http 58 | periodSeconds: 20 59 | resources: 60 | {{- toYaml .Values.resources | nindent 12 }} 61 | {{- with .Values.nodeSelector }} 62 | nodeSelector: 63 | {{- toYaml . | nindent 8 }} 64 | {{- end }} 65 | {{- with .Values.affinity }} 66 | affinity: 67 | {{- toYaml . | nindent 8 }} 68 | {{- end }} 69 | {{- with .Values.tolerations }} 70 | tolerations: 71 | {{- toYaml . | nindent 8 }} 72 | {{- end }} 73 | -------------------------------------------------------------------------------- /charts/py-kms/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "py-kms.fullname" . }} 6 | labels: 7 | {{- include "py-kms.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "py-kms.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /charts/py-kms/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "py-kms.fullname" . -}} 3 | {{- $svcPort := .Values.service.httpPort -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "py-kms.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /charts/py-kms/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "py-kms.fullname" . }} 5 | labels: 6 | {{- include "py-kms.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.kmsPort }} 11 | targetPort: 1688 12 | protocol: TCP 13 | name: kms 14 | - port: {{ .Values.service.httpPort }} 15 | targetPort: 8080 16 | protocol: TCP 17 | name: http 18 | selector: 19 | {{- include "py-kms.selectorLabels" . | nindent 4 }} 20 | -------------------------------------------------------------------------------- /charts/py-kms/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "py-kms.serviceAccountName" . }} 6 | labels: 7 | {{- include "py-kms.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /charts/py-kms/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "py-kms.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "py-kms.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "py-kms.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /charts/py-kms/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for py-kms. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: ghcr.io/py-kms-organization/py-kms 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: python3 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | 17 | py-kms: 18 | environment: 19 | LOGLEVEL: INFO 20 | LOGSIZE: 2 21 | LOGFILE: /var/log/py-kms.log 22 | HWID: RANDOM 23 | IP: '::' 24 | 25 | serviceAccount: {} 26 | # # Specifies whether a service account should be created 27 | # create: true 28 | # # Annotations to add to the service account 29 | # annotations: {} 30 | # # The name of the service account to use. 31 | # # If not set and create is true, a name is generated using the fullname template 32 | # name: "" 33 | 34 | podAnnotations: {} 35 | 36 | podSecurityContext: {} 37 | # fsGroup: 2000 38 | 39 | securityContext: {} 40 | # capabilities: 41 | # drop: 42 | # - ALL 43 | # readOnlyRootFilesystem: true 44 | # runAsNonRoot: true 45 | # runAsUser: 1000 46 | 47 | service: 48 | type: ClusterIP 49 | httpPort: 80 50 | kmsPort: 1688 51 | 52 | ingress: 53 | enabled: false 54 | className: "" 55 | annotations: {} 56 | # kubernetes.io/ingress.class: nginx 57 | # kubernetes.io/tls-acme: "true" 58 | hosts: 59 | - host: chart-example.local 60 | paths: 61 | - path: / 62 | pathType: ImplementationSpecific 63 | tls: [] 64 | # - secretName: chart-example-tls 65 | # hosts: 66 | # - chart-example.local 67 | 68 | resources: {} 69 | # We usually recommend not to specify default resources and to leave this as a conscious 70 | # choice for the user. This also increases chances charts run on environments with little 71 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 72 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 73 | # limits: 74 | # cpu: 100m 75 | # memory: 128Mi 76 | # requests: 77 | # cpu: 100m 78 | # memory: 128Mi 79 | 80 | autoscaling: 81 | enabled: false 82 | minReplicas: 1 83 | maxReplicas: 100 84 | targetCPUUtilizationPercentage: 80 85 | # targetMemoryUtilizationPercentage: 80 86 | 87 | nodeSelector: {} 88 | 89 | tolerations: [] 90 | 91 | affinity: {} 92 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | Both docker files must access the source code of this repository. Therefore the build context must be the root of the project directory. 2 | Take a look into the build script for the normal py-kms version, as it demonstrates exactly that case and how to use these docker files. 3 | -------------------------------------------------------------------------------- /docker/docker-py3-kms-minimal/Dockerfile: -------------------------------------------------------------------------------- 1 | # This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size 2 | FROM alpine:3.15 3 | 4 | ENV IP :: 5 | ENV DUALSTACK 1 6 | ENV PORT 1688 7 | ENV EPID "" 8 | ENV LCID 1033 9 | ENV CLIENT_COUNT 26 10 | ENV ACTIVATION_INTERVAL 120 11 | ENV RENEWAL_INTERVAL 10080 12 | ENV HWID RANDOM 13 | ENV LOGLEVEL INFO 14 | ENV LOGFILE STDOUT 15 | ENV LOGSIZE "" 16 | ENV WEBUI 0 17 | 18 | COPY docker/docker-py3-kms-minimal/requirements.txt /home/py-kms/requirements.txt 19 | RUN apk add --no-cache --update \ 20 | bash \ 21 | python3 \ 22 | py3-pip \ 23 | ca-certificates \ 24 | shadow \ 25 | tzdata \ 26 | && pip3 install --no-cache-dir -r /home/py-kms/requirements.txt \ 27 | && adduser -S py-kms -G users -s /bin/bash \ 28 | && chown py-kms:users /home/py-kms \ 29 | # Fix undefined timezone, in case the user did not mount the /etc/localtime 30 | && ln -sf /usr/share/zoneinfo/UTC /etc/localtime 31 | 32 | COPY ./py-kms /home/py-kms 33 | COPY docker/entrypoint.py /usr/bin/entrypoint.py 34 | COPY docker/healthcheck.py /usr/bin/healthcheck.py 35 | COPY docker/start.py /usr/bin/start.py 36 | RUN chmod 555 /usr/bin/entrypoint.py /usr/bin/healthcheck.py /usr/bin/start.py 37 | 38 | WORKDIR /home/py-kms 39 | 40 | EXPOSE ${PORT}/tcp 41 | 42 | HEALTHCHECK --interval=5m --timeout=10s --start-period=10s --retries=3 CMD /usr/bin/python3 /usr/bin/healthcheck.py 43 | 44 | ENTRYPOINT ["/usr/bin/python3", "-u", "/usr/bin/entrypoint.py"] 45 | -------------------------------------------------------------------------------- /docker/docker-py3-kms-minimal/requirements.txt: -------------------------------------------------------------------------------- 1 | dnspython==2.6.1 2 | tzlocal==4.2 -------------------------------------------------------------------------------- /docker/docker-py3-kms/Dockerfile: -------------------------------------------------------------------------------- 1 | # Switch to the target image 2 | FROM alpine:3.15 3 | 4 | ARG BUILD_COMMIT=unknown 5 | ARG BUILD_BRANCH=unknown 6 | 7 | ENV IP :: 8 | ENV DUALSTACK 1 9 | ENV PORT 1688 10 | ENV EPID "" 11 | ENV LCID 1033 12 | ENV CLIENT_COUNT 26 13 | ENV ACTIVATION_INTERVAL 120 14 | ENV RENEWAL_INTERVAL 10080 15 | ENV HWID RANDOM 16 | ENV LOGLEVEL INFO 17 | ENV LOGFILE STDOUT 18 | ENV LOGSIZE "" 19 | ENV TZ America/Chicago 20 | ENV WEBUI 1 21 | 22 | COPY docker/docker-py3-kms/requirements.txt /home/py-kms/ 23 | RUN apk add --no-cache --update \ 24 | bash \ 25 | python3 \ 26 | py3-pip \ 27 | sqlite-libs \ 28 | ca-certificates \ 29 | tzdata \ 30 | shadow \ 31 | && pip3 install --no-cache-dir -r /home/py-kms/requirements.txt \ 32 | && mkdir /db/ \ 33 | && adduser -S py-kms -G users -s /bin/bash \ 34 | && chown py-kms:users /home/py-kms \ 35 | # Fix undefined timezone, in case the user did not mount the /etc/localtime 36 | && ln -sf /usr/share/zoneinfo/UTC /etc/localtime 37 | 38 | COPY py-kms /home/py-kms/ 39 | COPY docker/entrypoint.py /usr/bin/entrypoint.py 40 | COPY docker/healthcheck.py /usr/bin/healthcheck.py 41 | COPY docker/start.py /usr/bin/start.py 42 | RUN chmod 555 /usr/bin/entrypoint.py /usr/bin/healthcheck.py /usr/bin/start.py 43 | 44 | # Web-interface specifics 45 | COPY LICENSE /LICENSE 46 | RUN echo "$BUILD_COMMIT" > /VERSION && echo "$BUILD_BRANCH" >> /VERSION 47 | 48 | WORKDIR /home/py-kms 49 | 50 | EXPOSE ${PORT}/tcp 51 | EXPOSE 8080/tcp 52 | 53 | HEALTHCHECK --interval=5m --timeout=10s --start-period=10s --retries=3 CMD /usr/bin/python3 /usr/bin/healthcheck.py 54 | 55 | ENTRYPOINT [ "/usr/bin/python3", "-u", "/usr/bin/entrypoint.py" ] 56 | -------------------------------------------------------------------------------- /docker/docker-py3-kms/requirements.txt: -------------------------------------------------------------------------------- 1 | dnspython==2.6.1 2 | tzlocal==4.2 3 | 4 | Flask==2.3.2 5 | gunicorn==22.0.0 -------------------------------------------------------------------------------- /docker/entrypoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 -u 2 | 3 | # Need root privileges to change timezone, and user uid/gid, file/folder ownernship 4 | 5 | import grp 6 | import logging 7 | import os 8 | import pwd 9 | import subprocess 10 | import sys 11 | import signal 12 | import time 13 | 14 | PYTHON3 = '/usr/bin/python3' 15 | dbPath = os.path.join(os.sep, 'home', 'py-kms', 'db') # Do not include the database file name, as we must correct the folder permissions (the db file is recursively reachable) 16 | 17 | def change_uid_grp(logger): 18 | if os.geteuid() != 0: 19 | logger.info(f'not root user, cannot change uid/gid.') 20 | return None 21 | user_db_entries = pwd.getpwnam("py-kms") 22 | user_grp_db_entries = grp.getgrnam("users") 23 | uid = int(user_db_entries.pw_uid) 24 | gid = int(user_grp_db_entries.gr_gid) 25 | new_gid = int(os.getenv('GID', str(gid))) 26 | new_uid = int(os.getenv('UID', str(uid))) 27 | os.chown("/home/py-kms", new_uid, new_gid) 28 | os.chown("/usr/bin/start.py", new_uid, new_gid) 29 | if os.path.isdir(dbPath): 30 | # Corret permissions recursively, as to access the database file, also its parent folder must be accessible 31 | logger.debug(f'Correcting owner permissions on {dbPath}.') 32 | os.chown(dbPath, new_uid, new_gid) 33 | for root, dirs, files in os.walk(dbPath): 34 | for dName in dirs: 35 | dPath = os.path.join(root, dName) 36 | logger.debug(f'Correcting owner permissions on {dPath}.') 37 | os.chown(dPath, new_uid, new_gid) 38 | for fName in files: 39 | fPath = os.path.join(root, fName) 40 | logger.debug(f'Correcting owner permissions on {fPath}.') 41 | os.chown(fPath, new_uid, new_gid) 42 | logger.debug(subprocess.check_output(['ls', '-la', dbPath]).decode()) 43 | if 'LOGFILE' in os.environ and os.path.exists(os.environ['LOGFILE']): 44 | # Oh, the user also wants a custom log file -> make sure start.py can access it by setting the correct permissions (777) 45 | os.chmod(os.environ['LOGFILE'], 0o777) 46 | logger.error(str(subprocess.check_output(['ls', '-la', os.environ['LOGFILE']]))) 47 | logger.info("Setting gid to '%s'." % str(new_gid)) 48 | os.setgid(new_gid) 49 | 50 | logger.info("Setting uid to '%s'." % str(new_uid)) 51 | os.setuid(new_uid) 52 | 53 | def change_tz(logger): 54 | tz = os.getenv('TZ', 'etc/UTC') 55 | # TZ is not symlinked and defined TZ exists 56 | if tz not in os.readlink('/etc/localtime') and os.path.isfile('/usr/share/zoneinfo/' + tz) and hasattr(time, 'tzset'): 57 | logger.info("Setting timzeone to %s" % tz ) 58 | # time.tzet() should be called on Unix, but doesn't exist on Windows. 59 | time.tzset() 60 | 61 | if __name__ == "__main__": 62 | log_level_bootstrap = log_level = os.getenv('LOGLEVEL', 'INFO') 63 | if log_level_bootstrap == "MININFO": 64 | log_level_bootstrap = "INFO" 65 | loggersrv = logging.getLogger('entrypoint.py') 66 | loggersrv.setLevel(log_level_bootstrap) 67 | streamhandler = logging.StreamHandler(sys.stdout) 68 | streamhandler.setLevel(log_level_bootstrap) 69 | formatter = logging.Formatter(fmt = '\x1b[94m%(asctime)s %(levelname)-8s %(message)s', datefmt = '%a, %d %b %Y %H:%M:%S',) 70 | streamhandler.setFormatter(formatter) 71 | loggersrv.addHandler(streamhandler) 72 | loggersrv.info("Log level: %s" % log_level) 73 | loggersrv.debug("user id: %s" % os.getuid()) 74 | 75 | change_tz(loggersrv) 76 | childProcess = subprocess.Popen(PYTHON3 + " -u /usr/bin/start.py", preexec_fn=change_uid_grp(loggersrv), shell=True) 77 | def shutdown(signum, frame): 78 | loggersrv.info("Received signal %s, shutting down..." % signum) 79 | childProcess.terminate() # This will also cause communicate() from below to continue 80 | signal.signal(signal.SIGTERM, shutdown) # This signal will be sent by Docker to request shutdown 81 | childProcess.communicate() 82 | -------------------------------------------------------------------------------- /docker/healthcheck.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 -u 2 | import os 3 | import sys 4 | import logging 5 | 6 | def do_check(logger): 7 | import socket 8 | listen_ip = os.environ.get('IP', '::').split() 9 | listen_ip.insert(0, '127.0.0.1') # always try to connect to localhost first 10 | listen_port = os.environ.get('PORT', '1688') 11 | for ip in listen_ip: 12 | try: 13 | s = socket.socket(socket.AF_INET6 if ':' in ip else socket.AF_INET, socket.SOCK_STREAM) 14 | s.settimeout(1) # 1 second timeout 15 | address = ip if ':' in ip else (ip, int(listen_port)) 16 | logger.debug(f"Trying to connect to {address}...") 17 | s.connect(address) 18 | s.close() 19 | return True 20 | except: 21 | pass 22 | return False # no connection could be established 23 | 24 | 25 | if __name__ == '__main__': 26 | log_level_bootstrap = log_level = os.getenv('LOGLEVEL', 'INFO') 27 | if log_level_bootstrap == "MININFO": 28 | log_level_bootstrap = "INFO" 29 | loggersrv = logging.getLogger('healthcheck.py') 30 | loggersrv.setLevel(log_level_bootstrap) 31 | streamhandler = logging.StreamHandler(sys.stdout) 32 | streamhandler.setLevel(log_level_bootstrap) 33 | formatter = logging.Formatter(fmt='\x1b[94m%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S') 34 | streamhandler.setFormatter(formatter) 35 | loggersrv.addHandler(streamhandler) 36 | 37 | sys.exit(0 if do_check(loggersrv) else 1) 38 | -------------------------------------------------------------------------------- /docker/start.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 -u 2 | 3 | # This replaces the old start.sh and ensures all arguments are bound correctly from the environment variables... 4 | import logging 5 | import os 6 | import subprocess 7 | import sys 8 | import time 9 | 10 | PYTHON3 = '/usr/bin/python3' 11 | argumentVariableMapping = { 12 | '-l': 'LCID', 13 | '-c': 'CLIENT_COUNT', 14 | '-a': 'ACTIVATION_INTERVAL', 15 | '-r': 'RENEWAL_INTERVAL', 16 | '-w': 'HWID', 17 | '-V': 'LOGLEVEL', 18 | '-F': 'LOGFILE', 19 | '-S': 'LOGSIZE', 20 | '-e': 'EPID' 21 | } 22 | 23 | db_path = os.path.join(os.sep, 'home', 'py-kms', 'db', 'pykms_database.db') 24 | log_file = os.environ.get('LOGFILE', 'STDOUT') 25 | listen_ip = os.environ.get('IP', '::').split() 26 | listen_port = os.environ.get('PORT', '1688') 27 | want_webui = os.environ.get('WEBUI', '0') == '1' # if the variable is not provided, we assume the user does not want the webui 28 | 29 | def start_kms(logger): 30 | # Make sure the full path to the db exists 31 | if want_webui and not os.path.exists(os.path.dirname(db_path)): 32 | os.makedirs(os.path.dirname(db_path), exist_ok=True) 33 | 34 | # Build the command to execute 35 | command = [PYTHON3, '-u', 'pykms_Server.py', listen_ip[0], listen_port] 36 | for (arg, env) in argumentVariableMapping.items(): 37 | if env in os.environ and os.environ.get(env) != '': 38 | command.append(arg) 39 | command.append(os.environ.get(env)) 40 | if want_webui: # add this command directly before the "connect" subparser - otherwise you'll get silent crashes! 41 | command.append('-s') 42 | command.append(db_path) 43 | if len(listen_ip) > 1: 44 | command.append("connect") 45 | for i in range(1, len(listen_ip)): 46 | command.append("-n") 47 | command.append(listen_ip[i] + "," + listen_port) 48 | if dual := os.environ.get('DUALSTACK'): 49 | command.append("-d") 50 | command.append(dual) 51 | 52 | logger.debug("server_cmd: %s" % (" ".join(str(x) for x in command).strip())) 53 | pykms_process = subprocess.Popen(command) 54 | pykms_webui_process = None 55 | 56 | try: 57 | if want_webui: 58 | time.sleep(2) # Wait for the server to start up 59 | pykms_webui_env = os.environ.copy() 60 | pykms_webui_env['PYKMS_SQLITE_DB_PATH'] = db_path 61 | pykms_webui_env['PORT'] = '8080' 62 | pykms_webui_env['PYKMS_LICENSE_PATH'] = '/LICENSE' 63 | pykms_webui_env['PYKMS_VERSION_PATH'] = '/VERSION' 64 | pykms_webui_process = subprocess.Popen(['gunicorn', '--log-level', os.environ.get('LOGLEVEL'), 'pykms_WebUI:app'], env=pykms_webui_env) 65 | except Exception as e: 66 | logger.error("Failed to start webui (ignoring and continuing anyways): %s" % e) 67 | 68 | try: 69 | pykms_process.wait() 70 | except Exception: 71 | # In case of any error - just shut down 72 | pass 73 | except KeyboardInterrupt: 74 | pass 75 | 76 | if pykms_webui_process: 77 | pykms_webui_process.terminate() 78 | pykms_process.terminate() 79 | 80 | 81 | # Main 82 | if __name__ == "__main__": 83 | log_level_bootstrap = log_level = os.environ.get('LOGLEVEL', 'INFO') 84 | if log_level_bootstrap == "MININFO": 85 | log_level_bootstrap = "INFO" 86 | loggersrv = logging.getLogger('start.py') 87 | loggersrv.setLevel(log_level_bootstrap) 88 | streamhandler = logging.StreamHandler(sys.stdout) 89 | streamhandler.setLevel(log_level_bootstrap) 90 | formatter = logging.Formatter(fmt='\x1b[94m%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S') 91 | streamhandler.setFormatter(formatter) 92 | loggersrv.addHandler(streamhandler) 93 | loggersrv.debug("user id: %s" % os.getuid()) 94 | 95 | start_kms(loggersrv) 96 | -------------------------------------------------------------------------------- /docs/Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | You want to improve this project? 4 | Awesome! But before you write or modify the existing source code, please note the following guideline: 5 | 6 | - Always make sure to add your changes to the wiki. 7 | - 8-space indentation without tabs. 8 | - Docstrings as this: 9 | ```python 10 | """ This is single line docstring. """ 11 | """ This is a 12 | """ multiline comment. 13 | ``` 14 | - Wrap lines only if really long (it does not matter 79 chars return) 15 | - For the rest a bit as it comes with a look at [PEP8](https://www.python.org/dev/peps/pep-0008/) :) 16 | -------------------------------------------------------------------------------- /docs/Documentation.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | What follows are some detailed explanations how the KMS infrastructure works. 3 | 4 | ## Understanding Key Management Service 5 | KMS activates Microsoft products on a local network, eliminating the need for individual computers to connect to Microsoft. To do this, KMS uses a client–server topology. A KMS client locates a KMS server by using DNS or a static 6 | configuration, then contact it by using Remote Procedure Call (RPC) and tries to activate against it. KMS can activate both physical computers and virtual machines, but a network must meet or exceed the activation threshold 7 | (minimum number of computers that KMS requires) of 25. For activation, KMS clients on the network need to install a KMS client key (General Volume License Key, GVLK), so the product no longer asks Microsoft server but a user–defined 8 | server (the KMS server) which usually resides in a company’s intranet. 9 | 10 | _py-kms_ is a free open source KMS server emulator written in Python, while Microsoft gives their KMS server only to corporations that signed a Select contract. Furthermore _py-kms_ never refuses activation since it is without 11 | restrictions, while the Microsoft KMS server only activates the products the customer has paid for. _py-kms_ supports KMS protocol versions `4`, `5` and `6`. 12 | 13 | **Although _py-kms_ does neither require an activation key nor any payment, it is not meant to run illegal copies of Windows.** Its purpose is to ensure that owners of legal copies can use their software without restrictions, 14 | e.g. if you buy a new computer or motherboard and your key will be refused activation from Microsoft servers due to hardware changes. 15 | 16 | Activation with _py-kms_ is achieved with the following steps: 17 | 1. Run _py-kms_ on a computer in the network (this is KMS server or local host). 18 | 2. Install the product on client (or said remote host, which is the computer sending data to local host) and enter the GVLK. 19 | 3. Configure the client to use the KMS server. 20 | 21 | Note that KMS activations are only valid for 180 days, the activation validity interval, or 30 to 45 days with consumer-only products. To remain activated, KMS client computers must renew their activation by connecting to the KMS 22 | server at least once every 180 days. For this to work, you have to should ensure that a KMS server is always reachable for all clients on the network. Also remember **you can't activate Windows 8.1 (and above) on a KMS server hosted 23 | on the same machine** (the KMS server must be a different computer than the client). 24 | 25 | ### About GVLK keys 26 | The GVLK keys for products sold via volume license contracts (renewal every 180 days) are published on Microsoft’s Technet web site. 27 | 28 | * Windows: [https://technet.microsoft.com/en-us/library/jj612867.aspx](https://technet.microsoft.com/en-us/library/jj612867.aspx) 29 | * Office 2010: [https://technet.microsoft.com/en-us/library/ee624355(v=office.14).aspx#section2_3](https://technet.microsoft.com/en-us/library/ee624355(v=office.14).aspx) 30 | * Office 2013: [https://technet.microsoft.com/en-us/library/dn385360.aspx](https://technet.microsoft.com/en-us/library/dn385360.aspx) 31 | * Office 2016: [https://technet.microsoft.com/en-en/library/dn385360(v=office.16).aspx](https://technet.microsoft.com/en-en/library/dn385360(v=office.16).aspx) 32 | 33 | There are also not official keys for consumer-only versions of Windows that require activation renewal every 45 days (Windows 8.1) or 30 days (Windows 8). A more complete and well defined list is available [here](Keys.md). 34 | 35 | ### SLMGR and OSPP commands 36 | The software License Manager (`slmgr.vbs`) is a Visual Basic script used to configure and retrieve Volume Activation information. The script can be run locally or remotely on the target computer, using the Windows-based script host 37 | (`wscript.exe`) or the command-based script host (`cscript.exe`) - administrators can specify which script engine to use. If no script engine is specified, _SLMGR_ runs using the default script engine (it is recommended to utilize 38 | the `cscript.exe` script engine that resides in the system32 directory). The Software Licensing Service must be restarted for any changes to take effect. To restart it, the Microsoft Management Console (MMC) Services can be used or 39 | running the following command: 40 | 41 | ``` 42 | net stop sppsvc && net start sppsvc 43 | ``` 44 | 45 | The _SLMGR_ requires at least one parameter. If the script is run without any parameters, it displays help information. The general syntax of `slmgr.vbs` is as follows (using the `cscript.exe` as the script engine): 46 | ``` 47 | cscript slmgr.vbs /parameter 48 | cscript slmgr.vbs [ComputerName] [User] [Password] [Option] 49 | ``` 50 | 51 | Where command line options are: 52 | ``` 53 | [ComputerName] Name of a remote computer (default is local computer). 54 | [User] Account with the required privilege on the remote computer. 55 | [Password] Password for the account with required privileges on the remote compute. 56 | [Option] Options are shown in the table below. 57 | ``` 58 | 59 | #### SLMGR 60 | Following tables lists _SLMGR_ more relevant options and a brief description of each. Most of the parameters configure the KMS host. 61 | 62 | | Global options | Description | 63 | | --- | --- | 64 | | `/ipk ` | Attempts to install a 5×5 ProductKey for Windows or other application identified by the ProductKey. If the key is valid, this is installed. If a key is already installed, it's silently replaced. | 65 | | `/ato [ActivationID]` | Prompts Windows to attempt online activation, for retail and volume systems with KMS host key. Specifying the ActivationID parameter isolates the effects of the option to the edition associated with that value. | 66 | | `/dli [ActivationID | All]` | Display license information. Specifying the ActivationID parameter displays the license information for the specified edition associated with that ActivationID. Specifying All will display all applicable installed products’ license information. Useful for retrieve the current KMS activation count from the KMS host. | 67 | | `/dlv [ActivationID | All]` | Display detailed license information. | 68 | | `/xpr [ActivationID]` | Display the activation expiration date for the current license state. | 69 | 70 | | Advanced options | Description | 71 | | --- | --- | 72 | | `/cpky` | Some servicing operations require the product key to be available in the registry during Out-of-Box Experience (OOBE) operations. So this option removes the product key from the registry to prevent from being stolen by malicious code. | 73 | | `/ilc ` | Installs the LicenseFile specified by the required parameter. | 74 | | `/rilc` | Reinstalls all licenses stored in %SystemRoot%\system32\oem and %SystemRoot%\System32\spp\tokens. | 75 | | `/rearm` | Resets the activation timers. | 76 | | `/rearm-app ` | Resets the licensing status of the specified application. | 77 | | `/rearm-sku ` | Resets the licensing status of the specified SKU. | 78 | | `/upk [ActivationID]` | Uninstalls the product key of the current Windows edition. After a restart, the system will be in an unlicensed state unless a new product key is installed. | 79 | | `/dti [ActivationID]` | Displays installation ID for offline activation of the KMS host for Windows (default) or the application that is identified when its ActivationID is provided. | 80 | | `/atp [ConfirmationID][ActivationID]` | Activate product with user-provided ConfirmationID. | 81 | 82 | | KMS client options | Description | 83 | | --- | --- | 84 | | `/skms [ActivationID]` | Specifies the name and the port of the KMS host computer to contact. Setting this value disables auto-detection of the KMS host. If the KMS host uses IPv6 only, the address must be specified in the format [hostname]:port. | 85 | | `/skms-domain [ActivationID]` | Sets the specific DNS domain in which all KMS SRV records can be found. This setting has no effect if the specific single KMS host is set with the /skms option. Use this option, especially in disjoint namespace environments, to force KMS to ignore the DNS suffix search list and look for KMS host records in the specified DNS domain instead. | 86 | | `/ckms [ActivationID]` | Removes the specified KMS hostname, address, and port information from the registry and restores KMS auto-discovery behavior. | 87 | | `/skhc` | Enables KMS host caching (default), which blocks the use of DNS priority and weight after the initial discovery of a working KMS host. If the system can no longer contact the working KMS host, discovery will be attempted again. | 88 | | `/ckhc` | Disables KMS host caching. This setting instructs the client to use DNS auto-discovery each time it attempts KMS activation (recommended when using priority and weight). | 89 | | `/sai ` | Changes how often a KMS client attempts to activate itself when it cannot find a KMS host. Replace ActivationInterval with a number of minutes between 15 minutes an 30 days. The default setting is 120. | 90 | | `/sri ` | Changes how often a KMS client attempts to renew its activation by contacting a KMS host. Replace RenewalInterval with a number of minutes between 15 minutes an 30 days. The default setting is 10080 (7 days). | 91 | | `/sprt ` | Sets the TCP communications port on a KMS host. It replaces PortNumber with the TCP port number to use. The default setting is 1688. | 92 | | `/sdns` | Enables automatic DNS publishing by the KMS host. | 93 | | `/cdns` | Disables automatic DNS publishing by a KMS host. | 94 | | `/spri` | Sets the priority of KMS host processes to Normal. | 95 | | `/cpri` | Set the KMS priority to Low. | 96 | | `/act-type [ActivationType] [ActivationID]` | Sets a value in the registry that limits volume activation to a single type. ActivationType 1 limits activation to active directory only; 2 limits it to KMS activation; 3 to token-based activation. The 0 option allows any activation type and is the default value. | 97 | 98 | #### OSPP 99 | The Office Software Protection Platform script (`ospp.vbs`) can help you to configure and test volume license editions of Office client products. You must open a command prompt by using administrator permissions and navigate to the 100 | folder that contains the mentioned script. The script is located in the folder of the Office installation (use `\Office14` for Office 2010, `\Office15` for Office 2013 and `\Office16` for Office 2016): `%installdir%\Program Files\Microsoft Office\Office15`. 101 | If you are running a 32-bit Office on a 64-bit operating system, the script is located in the folder: `%installdir%\Program Files (x86)\Microsoft Office\Office15`. 102 | 103 | Running _OSPP_ requires the `cscript.exe` script engine. To see the help file, type the following command, and then press ENTER: 104 | ``` 105 | cscript ospp.vbs /? 106 | ``` 107 | 108 | The general syntax is as follows: 109 | ``` 110 | cscript ospp.vbs [Option:Value] [ComputerName] [User] [Password] 111 | ``` 112 | 113 | Where command line options are: 114 | ``` 115 | [Option:Value] Specifies the option and Value to use to activate a product, install or uninstall a product key, install and display license information, set KMS host name and port, and remove KMS host. The options and values are listed in the tables below. 116 | [ComputerName] Name of the remote computer. If a computer name is not provided, the local computer is used. 117 | [User] Account that has the required permission on the remote computer. 118 | [Password] Password for the account. If a user account and password are not provided, the current credentials are used. 119 | ``` 120 | 121 | | Global options | Description | 122 | | --- | --- | 123 | | `/act` | Activates installed Office product keys. | 124 | | `/inpkey:` | Installs a ProductKey (replaces existing key) with a user-provided ProductKey. | 125 | | `/unpkey:` | Uninstalls an installed ProductKey with the last five digits of the ProductKey to uninstall (as displayed by the /dstatus option). | 126 | | `/inslic:` | Installs a LicenseFile with user-provided path of the .xrm-ms license. | 127 | | `/dstatus` | Displays license information for installed product keys. | 128 | | `/dstatusall` | Displays license information for all installed licenses. | 129 | | `/dhistoryacterr` | Displays the failure history for MAK / Retail activation. | 130 | | `/dinstid` | Displays Installation ID for offline activation. | 131 | | `/actcid:` | Activates product with user-provided ConfirmationID. | 132 | | `/rearm` | Resets the licensing status for all installed Office product keys. | 133 | | `/rearm:` | Resets the licensing status for an Office license with a user-provided SKUID value. Use this option with the SKUID value specified by using the /dstatus option if you have run out of rearms and have activated Office through KMS or Active Directory-based activation to gain an additional rearm. | 134 | | `/ddescr:` | Displays the description for a user-provided ErrorCode. | 135 | 136 | | KMS client options | Description | 137 | | --- | --- | 138 | | `/dhistorykms` | Displays KMS client activation history. | 139 | | `/dcmid` | Displays KMS client computer ID (CMID) | 140 | | `/sethst:` | Sets a KMS host name with a user-provided HostName. | 141 | | `/setprt:` | Sets a KMS port with a user-provided Port number. | 142 | | `/remhst` | Removes KMS hostname (sets port to default). | 143 | | `/cachst:` | Allows or denies KMS host caching. Parameter Value can be TRUE or FALSE. | 144 | | `/actype:` | (Windows 8 and later only) Sets volume activation type. Parameter Value can be: 1 (for Active Directory-based), 2 (for KMS), 0 (for both). | 145 | | `/skms-domain:` | (Windows 8 and later only) Sets the specific DNS domain in which all KMS SRV records can be found. This setting has no effect if the specific single KMS host is set by the /sethst option. Parameter Value is the Fully Qualified Domain Name (FQDN). | 146 | | `/ckms-domain` | (Windows 8 and later only) Clears the specific DNS domain in which all KMS SRV records can be found. The specific KMS host is used if it is set by the /sethst option. Otherwise, auto-discovery of the KMS host is used. | 147 | 148 | 149 | ## Supported Products 150 | Note that it is possible to activate all versions in the VL (Volume License) channel, so long as you provide the proper key to let Windows know that it should be activating against a KMS server. KMS activation can't be used for 151 | Retail channel products, however you can install a VL product key specific to your edition of Windows even if it was installed as Retail. This effectively converts Retail installation to VL channel and will allow you to activate 152 | from a KMS server. **However, this is not valid for Office's products**, so Office, Project and Visio must be always volume license versions. Newer version may work as long as the KMS protocol does not change... 153 | 154 | ## Further References 155 | * [1] https://forums.mydigitallife.net/threads/emulated-kms-servers-on-non-windows-platforms.50234 156 | * [2] https://forums.mydigitallife.net/threads/discussion-microsoft-office-2019.75232 157 | * [3] https://forums.mydigitallife.net/threads/miscellaneous-kms-related-developments.52594 158 | * [4] https://forums.mydigitallife.net/threads/kms-activate-windows-8-1-en-pro-and-office-2013.49686 159 | * [5] https://github.com/myanaloglife/py-kms 160 | * [6] https://github.com/Wind4/vlmcsd 161 | * [7] https://github.com/ThunderEX/py-kms 162 | * [8] https://github.com/CNMan/balala/blob/master/pkconfig.csv 163 | * [9] http://www.level7techgroup.com/docs/kms_overview.pdf 164 | * [10] https://www.dell.com/support/article/it/it/itbsdt1/sln266176/windows-server-using-the-key-management-service-kms-for-activation-of-volume-licensed-systems?lang=en 165 | * [11] https://social.technet.microsoft.com/Forums/en-US/c3331743-cba2-4d92-88aa-9633ac74793a/office-2010-kms-current-count-remain-at-10?forum=officesetupdeployprevious 166 | * [12] https://betawiki.net/wiki/Microsoft_Windows 167 | * [13] https://thecollectionbook.info/builds/windows 168 | * [14] https://www.betaarchive.com/forum/viewtopic.php%3Ft%3D29131+&cd=10&hl=it&ct=clnk&gl=it 169 | * [15] https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=12&cad=rja&uact=8&ved=2ahUKEwjmvZihtOHeAhVwyoUKHSjeD5Q4ChAWMAF6BAgHEAI&url=ftp%3A%2F%2Flynas.ittc.vu.lt%2Fpub%2FMicrosoft%2FWindows%2520Vista%2FWindows%2520Vista_Volume_Activation_2.0%2FWindows%2520Vista%2520Volume%2520Activation%2FWindows%2520Vista_Volume_Activation_2.0%2Fvolume%2520activation%25202%25200%2520step-by-step%2520guide.doc&usg=AOvVaw3kqhCu3xT-3r416DRGUUs_ 170 | * [16] https://www.itprotoday.com/windows-78/volume-activation-server-2008 171 | * [17] https://docs.microsoft.com/en-us/windows-server/get-started-19/activation-19 172 | * [18] https://docs.microsoft.com/en-us/windows-server/get-started/windows-server-release-info 173 | * [19] https://support.microsoft.com/en-us/help/13853/windows-lifecycle-fact-sheet 174 | -------------------------------------------------------------------------------- /docs/Getting Started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | What follows are some guides how to start the `pykms_Server.py` script, which provides the emulated server. 3 | 4 | ## Running as a service 5 | You can simply manage a daemon that runs as a background process. This can be achieved by using any of the notes below or by writing your own solution. 6 | 7 | ### Docker 8 | If you wish to get _py-kms_ just up and running without installing any dependencies or writing own scripts: Just use Docker ! 9 | Docker also solves problems regarding the explicit IPv4 and IPv6 usage (it just supports both). The following 10 | command will download, "install" and start _py-kms_ and also keep it alive after any service disruption. 11 | ```bash 12 | docker run -d --name py-kms --restart always -p 1688:1688 -v /etc/localtime:/etc/localtime:ro ghcr.io/py-kms-organization/py-kms 13 | ``` 14 | If you just want to use the image and don't want to build them yourself, you can always use the official image at the [GitHub Container Registry](https://github.com/Py-KMS-Organization/py-kms/pkgs/container/py-kms) (`ghcr.io/py-kms-organization/py-kms`). To ensure that you are using always the latest version you should check something like [watchtower](https://github.com/containrrr/watchtower) out! 15 | 16 | #### Tags 17 | There are currently three tags of the image available (select one just by appending `:` to the image from above): 18 | * `latest`, currently the same like `minimal`. 19 | * `minimal`, which is based on the python3 minimal configuration of py-kms. _This tag does NOT include `sqlite` support !_ 20 | * `python3`, which is fully configurable and equipped with `sqlite` support and a web-interface (make sure to expose port `8080`) for management. 21 | 22 | Wait... Web-interface? Yes! `py-kms` now comes with a simple web-ui to let you browse the known clients or its supported products. In case you wonder, here is a screenshot of the web-ui (*note that this screenshot may not reflect the current state of the ui*): 23 | 24 | ![web-ui](img/webinterface.png) 25 | 26 | #### Architectures 27 | There are currently the following architectures available (if you need an other, feel free to open an issue): 28 | * `amd64` 29 | * `arm32v6` Raspberry PI 1 (A, A+, B, B+, Zero) 30 | * `arm32v7` Raspberry PI 2 (B) 31 | * `arm64v8` Raspberry PI 2 (B v1.2), Raspberry PI 3 (A+, B, B+), Raspberry PI 4 (B) 32 | 33 | _Please note that any architecture other than the classic `amd64` is slightly bigger (~4 MB), caused by the use of qemu during building._ 34 | 35 | #### Docker Compose 36 | You can use `docker-compose` instead of building and running the Dockerfile, so you do not need to respecify your settings again and again. The following Docker Compose file will deploy the `latest` image with the log into your local directory. 37 | Make sure to take a look into the `entrypoint.py` script to see all supported variable mappings! 38 | ```yaml 39 | version: '3' 40 | 41 | services: 42 | kms: 43 | image: ghcr.io/py-kms-organization/py-kms:python3 44 | ports: 45 | - 1688:1688 # kms 46 | - 8080:8080 # web-interface 47 | environment: 48 | IP: "::" 49 | HWID: RANDOM 50 | LOGLEVEL: INFO 51 | restart: always 52 | volumes: 53 | - ./db:/home/py-kms/db 54 | - /etc/localtime:/etc/localtime:ro 55 | ``` 56 | 57 | #### Parameters 58 | Below is a little bit more extended run command, detailing all the different supported environment variables to set. For further reference see the [start parameters](#docker-environment) for the docker environment. 59 | ```bash 60 | docker run -it -d --name py3-kms \ 61 | -p 8080:8080 \ 62 | -p 1688:1688 \ 63 | -v /etc/localtime:/etc/localtime:ro \ 64 | --restart unless-stopped ghcr.io/py-kms-organization/py-kms:[TAG] 65 | ``` 66 | You can omit the `-p 8080:8080` option if you plan to use the `minimal` or `latest` image, which does not include the `sqlite` module support. 67 | 68 | ### Systemd 69 | If you are running a Linux distro using `systemd`, create the file: `sudo nano /etc/systemd/system/py3-kms.service`, then add the following (change it where needed) and save: 70 | ```systemd 71 | [Unit] 72 | Description=py3-kms 73 | After=network.target 74 | StartLimitIntervalSec=0 75 | 76 | [Service] 77 | Type=simple 78 | Restart=always 79 | RestartSec=1 80 | KillMode=process 81 | User=root 82 | ExecStart=/usr/bin/python3 /py-kms/pykms_Server.py :: 1688 -V DEBUG -F /pykms_logserver.log 83 | 84 | [Install] 85 | WantedBy=multi-user.target 86 | ``` 87 | Check syntax with `sudo systemd-analyze verify py3-kms.service`, correct file permission (if needed) `sudo chmod 644 /etc/systemd/system/py3-kms.service`, then reload systemd manager configuration `sudo systemctl daemon-reload`, 88 | start the daemon `sudo systemctl start py3-kms.service` and view its status `sudo systemctl status py3-kms.service`. Check if daemon is correctly running with `cat /pykms_logserver.log`. Finally a 89 | few generic commands useful for interact with your daemon [here](https://linoxide.com/linux-how-to/enable-disable-services-ubuntu-systemd-upstart/). 90 | 91 | ### Upstart (deprecated) 92 | If you are running a Linux distro using `upstart` (deprecated), create the file: `sudo nano /etc/init/py3-kms.conf`, then add the following (change it where needed) and save: 93 | ``` 94 | description "py3-kms" 95 | author "SystemRage" 96 | env PYTHONPATH=/usr/bin 97 | env PYKMSPATH=/py-kms 98 | env LOGPATH=/pykms_logserver.log 99 | start on runlevel [2345] 100 | stop on runlevel [016] 101 | exec $PYTHONPATH/python3 $PYKMSPATH/pykms_Server.py :: 1688 -V DEBUG -F $LOGPATH 102 | respawn 103 | ``` 104 | Check syntax with `sudo init-checkconf -d /etc/init/py3-kms.conf`, then reload upstart to recognise this process `sudo initctl reload-configuration`. Now start the service `sudo start py3-kms`, and you can see the logfile 105 | stating that your daemon is running: `cat /pykms_logserver.log`. Finally a few generic commands useful for interact with your daemon [here](https://eopio.com/linux-upstart-process-manager/). 106 | 107 | ### Windows 108 | If you are using Windows, to run `pykms_Server.py` as service you need to install [pywin32](https://sourceforge.net/projects/pywin32/), then you can create a file for example named `kms-winservice.py` and put into it this code: 109 | ```python 110 | import win32serviceutil 111 | import win32service 112 | import win32event 113 | import servicemanager 114 | import socket 115 | import subprocess 116 | 117 | class AppServerSvc (win32serviceutil.ServiceFramework): 118 | _svc_name_ = "py-kms" 119 | _svc_display_name_ = "py-kms" 120 | _proc = None 121 | _cmd = ["C:\Windows\Python27\python.exe", "C:\Windows\Python27\py-kms\pykms_Server.py"] # UPDATE THIS - because Python 2.7 is end of life and you will use other parameters anyway 122 | 123 | def __init__(self,args): 124 | win32serviceutil.ServiceFramework.__init__(self,args) 125 | self.hWaitStop = win32event.CreateEvent(None,0,0,None) 126 | socket.setdefaulttimeout(60) 127 | 128 | def SvcStop(self): 129 | self.killproc() 130 | self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 131 | win32event.SetEvent(self.hWaitStop) 132 | 133 | def SvcDoRun(self): 134 | servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, 135 | servicemanager.PYS_SERVICE_STARTED, 136 | (self._svc_name_,'')) 137 | self.main() 138 | 139 | def main(self): 140 | self._proc = subprocess.Popen(self._cmd) 141 | self._proc.wait() 142 | 143 | def killproc(self): 144 | self._proc.kill() 145 | 146 | if __name__ == '__main__': 147 | win32serviceutil.HandleCommandLine(AppServerSvc) 148 | ``` 149 | Now in a command prompt type `C:\Windows\Python27\python.exe kms-winservice.py install` to install the service. Display all the services with `services.msc` and find the service associated with _py-kms_, change the startup type 150 | from `manual` to `auto`. Finally `Start` the service. If this approach fails, you can try to use [Non-Sucking Service Manager](https://nssm.cc/) or Task Scheduler as described [here](https://blogs.esri.com/esri/arcgis/2013/07/30/scheduling-a-scrip/). 151 | 152 | ### Other Platforms 153 | They might be useful to you: 154 | - [FreeNAS](https://github.com/SystemRage/py-kms/issues/56) 155 | - [FreeBSD](https://github.com/SystemRage/py-kms/issues/89) 156 | 157 | ## Manual Execution 158 | 159 | ### Dependencies 160 | - Python 3.x. 161 | - If the `tzlocal` module is installed, the "Request Time" in the verbose output will be converted into local time. Otherwise, it will be in UTC. 162 | - It can use the `sqlite3` module, storing activation data in a database so it can be recalled again. 163 | - Installation example on Ubuntu / Mint (`requirements.txt` is from the sources): 164 | - `sudo apt-get update` 165 | - `sudo apt-get install python3-pip` 166 | - `pip3 install -r requirements.txt` (on Ubuntu Server 22, you'll need `pysqlite3-binary` - see [this issue](https://github.com/Py-KMS-Organization/py-kms/issues/76)) 167 | 168 | ### Startup 169 | A Linux user with `ip addr` command can get his KMS IP (Windows users can try `ipconfig /all`). 170 | ```bash 171 | user@host ~ $ ip addr 172 | 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 173 | link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 174 | inet 127.0.0.1/8 scope host lo 175 | valid_lft forever preferred_lft forever 176 | inet6 ::1/128 scope host 177 | valid_lft forever preferred_lft forever 178 | 2: enp6s0: mtu 1500 qdisc fq_codel state UP group default qlen 1000 179 | link/ether **:**:**:**:**:** brd ff:ff:ff:ff:ff:ff 180 | inet 192.168.1.102/24 brd 192.168.1.255 scope global dynamic noprefixroute enp6s0 181 | valid_lft 860084sec preferred_lft 860084sec 182 | inet6 ****:****:****:****:****:****:****:****/64 scope global dynamic noprefixroute 183 | valid_lft 6653sec preferred_lft 3052sec 184 | inet6 ****::****:****:****:****/64 scope link noprefixroute 185 | valid_lft forever preferred_lft forever 186 | ``` 187 | In the example above is `192.168.1.102` the ip we want to listen on, so it is this command (**note you can omit the ip AND port specification if you just wish to listen on all interfaces with port 1688**): 188 | 189 | ``` 190 | user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py 192.168.1.102 1688 191 | ``` 192 | 193 | To stop `pykms_Server.py`, in the same bash window where code running, simply press `CTRL+C`. 194 | Alternatively, in a new bash window, use `kill ` command (you can type `ps aux` first and have the process ) or `killall `. 195 | 196 | ### Web-Interface 197 | As you may have noticed, the Docker container contains a web-interface, replacing the old GUI. If you want to launch it manually, checkout this [issue discussion](https://github.com/Py-KMS-Organization/py-kms/issues/100#issuecomment-1710827824) to learn more. 198 | 199 | ### Quick Guide 200 | The following are just some brief notes about parameters handling. For a more detailed description see [here](Usage.md). 201 | 202 | - To generate a random HWID use `-w` option: `python3 pykms_Server.py -w RANDOM`. 203 | - To get the HWID from any server use the client, for example type: `python3 pykms_Client.py :: 1688 -m Windows8.1 -V INFO`. 204 | - To change your logfile path use `-F` option, for example: `python3 pykms_Server.py -F /path/to/your/logfile.log -V DEBUG`. 205 | - To view a minimal set of logging information use `-V MININFO` option, for example: `python3 pykms_Server.py -F /path/to/your/logfile.log -V MININFO`. 206 | - To redirect logging on stdout use `-F STDOUT` option, for example: `python3 pykms_Server.py -F STDOUT -V DEBUG`. 207 | - You can create logfile and view logging information on stdout at the same time with `-F FILESTDOUT` option, for example: `python3 pykms_Server.py -F FILESTDOUT /path/to/your/logfile.log -V DEBUG`. 208 | - With `-F STDOUTOFF` you disable all stdout messages (but a logfile will be created), for example: `python3 pykms_Server.py -F STDOUTOFF /path/to/your/logfile.log -V DEBUG`. 209 | - With `-F FILEOFF` you disable logfile creation. 210 | - Select timeout (seconds) for py-kms with `-t0` option, for example `python3 pykms_Server.py -t0 10`. 211 | - Option `-y` enables printing asynchronously of messages (pretty / logging). 212 | -------------------------------------------------------------------------------- /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/ReadtheDocsTemplate.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ReadtheDocsTemplate.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/ReadtheDocsTemplate" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ReadtheDocsTemplate" 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/Troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | Something does not work as expected ? _Before_ you open an issue, please make sure to at least follow these steps to further diagnose or even resolve your problem. 4 | If you not follow this, do not expect that we can or want to help you! 5 | 6 | 7 | * Are you activating a legit Windows copy checked with `sha256`, `md5` or is it maybe a warez torrent version ? 8 | * Did you tried a clean installation (format all) ? You skipped entering any key during installation, turning off internet connection, first activating and then updating Windows (and eventually later upgrading) ? 9 | * Are you activating Windows or Office on a different machine (physical or virtual) where py-kms runs? 10 | * Have you installed all latest packages ? Especially before upgrading ? Are you upgrading using the "Update Assistant"/"Media Creation" tool to switch from Windows 7 / 8 / 8.1 to 10 (for me has always worked) ? 11 | * If isn't a clean install, so far as you have kept activated your Windows copy ? Have you used some other activator (maybe not trusted) that injects or changes .dll files and therefore may have corrupted something ? 12 | * Have you forgot to reactivate at least once before 180 (45 or 30, depending on your version) days ? 13 | * Is your system very tweaked with some service disabled (have you used [O&O Shutup 10](https://www.oo-software.com/en/shutup10) or similar tools) ? 14 | * Have you disabled (or created an exception for) ALL firewalls (Public/Private/Office) / antivirus (Windows defender, etc..), server-side AND client-side ? 15 | * Have you already activated with a OEM/Retail/other license and now you want to activate using `py-kms` ? So, have you switched to volume channel with appropriate [GVLK](Keys.md) ? Make sure you first deleted the previous key (example: [#24 (comment)](https://github.com/SystemRage/py-kms/issues/24#issuecomment-492431436)) ? 16 | * Are you running the commands using the **elevated** command prompt ? 17 | * Are you connecting correctly to your `py-kms` server instance ? 18 | * Have you tried to fix with "Windows Troubleshoot", `sfc /scannow` or other strange Windows tools ? 19 | * If you activated successfully with `py-kms` other Windows stuff, consider it could be an error specific for your PC (so you may need a scented clean installation) ? 20 | * Is your `py-kms` really running ? Already tried to enable debug logs ? 21 | * Did you already searched for your issue [here](https://github.com/SystemRage/py-kms/issues) ? 22 | * Are you running the latest version of `py-kms` ? 23 | * For Office: Did you made sure to use a Office **with** GLVK support ?! 24 | * You found a real bug ? Could you maybe make our life's easier and describe what goes wrong **and** also provide some information about your environment (OS, Python-Version, Docker, Commit-Hashes, Network-Setup) ? 25 | * When you post logs: Please remove personal information (replace IPs with something like `[IP_ADDRESS_A]`)... 26 | 27 | If you have already thought about all of this, your last hope to solve your problem is reading some verse of the Holy Bible of activations: "MDL forums" - otherwise "I don't know !", but try open up an issue anyways :) 28 | -------------------------------------------------------------------------------- /docs/Usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ## Start Parameters 4 | 5 | (pykms-server-py)= 6 | ### pykms_Server.py 7 | Follows a list of usable parameters: 8 | 9 | ip 10 | > Instructs py-kms to listen on _IPADDRESS_ (can be an hostname too). If this option is not specified, _IPADDRESS_ `::` is used. 11 | 12 | port 13 | > Define TCP _PORT_ the KMS service is listening on. Default is 1688. 14 | 15 | -e or --epid 16 | > Enhanced Privacy ID (_EPID_) is a cryptographic scheme for providing anonymous signatures. 17 | Use _EPID_ as Windows _EPID_. If no _EPID_ is specified, a random one will be generated. 18 | 19 | -l or --lcid 20 | > Specify the _LCID_ part of the _EPID_. If an _EPID_ is manually specified, this setting is ignored. Default is 1033 (English - US). 21 | The Language Code Identifier (_LCID_) describes localizable information in Windows. 22 | This structure is used to identify specific languages for the purpose of customizing 23 | software for particular languages and cultures. For example, it can specify the way dates, 24 | times, and numbers are formatted as strings. It can also specify paper sizes and preferred sort order based on language elements. 25 | The _LCID_ must be specified as a decimal number (example: 1049 for "Russian - Russia"). 26 | By default py-kms generates a valid locale ID but this may lead to a value which is unlikely to occur in your country. 27 | You may want to select the locale ID of your country instead. 28 | See [here](https://msdn.microsoft.com/en-us/library/cc233982.aspx) for a list of valid _LCIDs_. 29 | 30 | -w or --hwid 31 | > Use specified _HWID_ for all products. Use `-w RANDOM` to generate a random HWID. Default is random. 32 | Hardware Identification is a security measure used by Microsoft upon the activation of 33 | the Windows operating system. As part of the Product Activation system, a unique 34 | HWID number is generated when the operating system is first installed. The _HWID_ identifies the hardware components that the system 35 | is utilizing, and this number is communicated to Microsoft. 36 | Every 10 days and at every reboot the operating system will generate another _HWID_ number and compare it to the original 37 | to make sure that the operating system is still running on the same device. 38 | If the two _HWID_ numbers differ too much then the operating system will shut down until Microsoft reactivates the product. 39 | The theory behind _HWID_ is to ensure that the operating system is not being used on any device other than the one 40 | for which it was purchased and registered. 41 | HWID must be an 16-character string of hex characters that are interpreted as a series of 8 bytes (big endian). 42 | 43 | -c or --client-count 44 | > Use this flag to specify the current _CLIENTCOUNT_. Default is None. Remember that a number >=25 is 45 | required to enable activation of client OSes while for server OSes and Office >=5. 46 | 47 | -a or --activation-interval 48 | > Instructs clients to retry activation every _ACTIVATIONINTERVAL_ minutes if it was unsuccessful, 49 | e.g. because it could not reach the server. The default is 120 minutes (2 hours). 50 | 51 | -r or --renewal-interval 52 | > Instructs clients to renew activation every _RENEWALINTERVAL_ minutes. The default is 10080 minutes (7 days). 53 | 54 | -s or --sqlite [] 55 | > Use this option to store request information from unique clients in an SQLite database. Deactivated by default. 56 | 57 | -t0 or --timeout-idle 58 | > Maximum inactivity time (in seconds) after which the connection with the client is closed. 59 | Default setting is serve forever (no timeout). 60 | 61 | -t1 or --timeout-sndrcv 62 | > Set the maximum time (in seconds) to wait for sending / receiving a request / response. Default is no timeout. 63 | 64 | -y or --async-msg 65 | > With high levels of logging (e.g hundreds of log statements), in a traditional synchronous log model, 66 | the overhead involved becomes more expensive, so using this option you enable printing (pretty / logging) messages 67 | asynchronously reducing time-consuming. Deactivated by default. 68 | 69 | -V or --loglevel <{CRITICAL, ERROR, WARNING, INFO, DEBUG, MININFO}> 70 | > Use this flag to set a logging loglevel. The default is _ERROR_. 71 | example: 72 | ``` 73 | user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py -V INFO 74 | ``` 75 | creates _pykms_logserver.log_ with these initial messages: 76 | ``` 77 | Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at :: on port 1688. 78 | Mon, 12 Jun 2017 22:09:00 INFO HWID: 364F463A8863D35F 79 | ``` 80 | 81 | -F or --logfile 82 | > Creates a _LOGFILE.log_ logging file. The default is named _pykms_logserver.log_. 83 | example: 84 | ``` 85 | user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py 192.168.1.102 1688 -F ~/path/to/folder/py-kms/newlogfile.log -V INFO -w RANDOM 86 | ``` 87 | creates _newlogfile.log_ with these initial messages: 88 | ``` 89 | Mon, 12 Jun 2017 22:09:00 INFO TCP server listening at 192.168.1.102 on port 1688. 90 | Mon, 12 Jun 2017 22:09:00 INFO HWID: 58C4F4E53AE14224 91 | ``` 92 | 93 | You can also enable other suboptions of `-F` doing what is reported in the following table: 94 | 95 | | command | pretty msg | logging msg | logfile | 96 | | ------------------------- | ---------- | ----------- | ------- | 97 | | `-F ` | ON | OFF | ON | 98 | | `-F STDOUT` | OFF | ON | OFF | 99 | | `-F FILESTDOUT ` | OFF | ON | ON | 100 | | `-F STDOUTOFF ` | OFF | OFF | ON | 101 | | `-F FILEOFF` | ON | OFF | OFF | 102 | 103 | -S or --logsize 104 | > Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default. 105 | 106 | #### subparser `connect` 107 | 108 | -n or --listen <'IP,PORT'> 109 | > Use this option to add multiple listening ip address - port couples. Note the format with the comma between the ip address and the port number. You can use this option more than once. 110 | 111 | -b or --backlog 112 | > Use this option to specify the maximum length of the queue of pending connections, referred to a ip address - port couple. 113 | If placed just after `connect` refers to the main address and all additive couples without `-b` option. Default is 5. 114 | 115 | -u or --no-reuse 116 | > Use this option not to allow binding / listening to the same ip address - port couple specified with `-n`. 117 | If placed just after `connect` refers to the main address and all additive couples without `-u` option. Reusing port is activated by default (except when running inside the Windows Sandbox and the current user is `WDAGUtilityAccount`). 118 | 119 | -d or --dual 120 | > Allows listening to an IPv6 address while also accepting connections via IPv4. If used, it refers to all addresses (main and additional). Activated by default. Pass in "false" or "true" to disable or enable. 121 | 122 | Examples (with fictitious addresses and ports): 123 | 124 | | command | address (main) | backlog (main) | reuse port (main) | address (listen) | backlog (listen) | reuse port (listen) | dualstack (main / listen) | 125 | | ---------------------------------------------------------------------------------------------------- | -------------- | -------------- | ----------------- | ------------------------------------------------ | ---------------- | ------------------- | ------------------------- | 126 | | `python3 pykms_Server.py connect -b 12` | ('::', 1688) | 12 | True | [] | [] | [] | True | 127 | | `python3 pykms_Server.py :: connect -b 12 -u -d yes` | ('::', 1688) | 12 | False | [] | [] | [] | True | 128 | | `python3 pykms_Server.py :: connect -b 12 -u -d false` | ('::', 1688) | 12 | False | [] | [] | [] | False | 129 | | `python3 pykms_Server.py connect -n 1.1.1.1,1699 -b 10` | ('::', 1688) | 5 | True | [('1.1.1.1', 1699)] | [10] | [True] | True | 130 | | `python3 pykms_Server.py :: 1655 connect -n 2001:db8:0:200::7,1699 -d true -b 10 -n 2.2.2.2,1677 -u` | ('::', 1655) | 5 | True | [('2001:db8:0:200::7', 1699), ('2.2.2.2', 1677)] | [10, 5] | [True, False] | True | 131 | | `python3 pykms_Server.py connect -b 12 -u -n 1.1.1.1,1699 -b 10 -n 2.2.2.2,1677 -b 15` | ('::', 1688) | 12 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [10, 15] | [False, False] | True | 132 | | `python3 pykms_Server.py connect -b 12 -n 1.1.1.1,1699 -u -n 2.2.2.2,1677` | ('::', 1688) | 12 | True | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [12, 12] | [False, True] | True | 133 | | `python3 pykms_Server.py connect -d 0 -u -b 8 -n 1.1.1.1,1699 -n 2.2.2.2,1677 -b 12` | ('::', 1688) | 8 | False | [('1.1.1.1', 1699), ('2.2.2.2', 1677)] | [8, 12] | [False, False] | False | 134 | | `python3 pykms_Server.py connect -b 11 -u -n ::,1699 -n 2.2.2.2,1677` | ('::', 1688) | 11 | False | [('::', 1699), ('2.2.2.2', 1677)] | [11, 11] | [False, False] | True | 135 | 136 | ### pykms_Client.py 137 | If _py-kms_ server doesn't works correctly, you can test it with the KMS client `pykms_Client.py`, running on the same machine where you started `pykms_Server.py`. 138 | 139 | For example (in separated bash windows) run these commands: 140 | ``` 141 | user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py -V DEBUG 142 | user@host ~/path/to/folder/py-kms $ python3 pykms_Client.py -V DEBUG 143 | ``` 144 | 145 | If you wish to get KMS server from DNS server: (ie perform a DNS resolution on _vlmcs._tcp.domain.tld, if ever there are several answers, only the first one is selected.). Althought that mode is supposed to be specific to devices connect to an Active Directory domain, setting a fully qualified name and a workgroup may help to use that automatic KMS discovery feature. 146 | ``` 147 | user@host ~/path/to/folder/py-kms $ python3 pykms_Client.py -V DEBUG -F STDOUT -D contoso.com 148 | user@host ~/path/to/folder/py-kms $ python3 pykms_Client.py -V DEBUG -F STDOUT -D contoso.com 149 | ``` 150 | 151 | Or if you want better specify: 152 | ``` 153 | user@host ~/path/to/folder/py-kms $ python3 pykms_Server.py 1688 -V DEBUG 154 | user@host ~/path/to/folder/py-kms $ python3 pykms_Client.py 1688 -V DEBUG 155 | ``` 156 | You can also put further parameters as defined below: 157 | 158 | ip 159 | > Define _IPADDRESS_ (or hostname) of py-kms' KMS Server. This parameter is always required. 160 | 161 | port 162 | > Define TCP _PORT_ the KMS service is listening on. Default is 1688. 163 | 164 | -m or --mode <{WindowsVista, Windows7, Windows8, Windows8.1, Windows10, Office2010, Office2013, Office2016, Office2019}> 165 | > Use this flag to manually specify a Microsoft _PRODUCTNAME_ for testing the KMS server. Default is Windows8.1. 166 | 167 | -c or --cmid 168 | > Use this flag to manually specify a CMID to use. If no CMID is specified, a random one will be generated. 169 | The Microsoft KMS host machine identifies KMS clients with a unique Client Machine ID 170 | (CMID, example: ae3a27d1-b73a-4734-9878-70c949815218). For a KMS client to successfully activate, the KMS server 171 | needs to meet a threshold, which is a minimum count for KMS clients. 172 | Once a KMS server records a count which meets or exceeds threshold, KMS clients will begin to activate successfully. 173 | Each unique CMID recorded by KMS server adds towards the count threshold for KMS clients. This are retained by the KMS server 174 | for a maximum of 30 days after the last activation request with that CMID. Note that duplicate CMID only impacts on KMS server 175 | machine count of client machines. Once KMS server meets minimum threshold, KMS clients will 176 | activate regardless of CMID being unique for a subset of specific machines or not. 177 | 178 | -n or --name 179 | > Use this flag to manually specify an ASCII _MACHINENAME_ to use. If no _MACHINENAME_ is specified a random one will be generated. 180 | 181 | -t0 or --timeout-idle 182 | > Set the maximum time (in seconds) to wait for a connection attempt to KMS server to succeed. Default is no timeout. 183 | 184 | -t1 or --timeout-sndrcv 185 | > Set the maximum time (in seconds) to wait for sending / receiving a request / response. Default is no timeout. 186 | 187 | -y or --async-msg 188 | > Prints pretty / logging messages asynchronously. Deactivated by default. 189 | 190 | -V or --loglevel <{CRITICAL, ERROR, WARNING, INFO, DEBUG, MININFO}> 191 | > Use this flag to set a logging loglevel. The default is _ERROR_. 192 | 193 | -F or --logfile 194 | > Creates a _LOGFILE.log_ logging file. The default is named _pykms_logclient.log_. 195 | You can enable same _pykms_Server.py_ suboptions of `-F`. 196 | 197 | -S or --logsize 198 | > Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default. 199 | 200 | (docker-environment)= 201 | ## Docker Environment 202 | This are the currently used `ENV` statements from the Dockerfile(s). For further references what exactly the parameters mean, please see the start parameters for the [server](#pykms-server-py). 203 | ``` 204 | # IP-address 205 | # The IP address to listen on. The default is "::" (all interfaces). 206 | ENV IP :: 207 | 208 | # TCP-port 209 | # The network port to listen on. The default is "1688". 210 | ENV PORT 1688 211 | 212 | # ePID 213 | # Use this flag to manually specify an ePID to use. If no ePID is specified, a random ePID will be generated. 214 | ENV EPID "" 215 | 216 | # lcid 217 | # Use this flag to manually specify an LCID for use with randomly generated ePIDs. Default is 1033 (en-us). 218 | ENV LCID 1033 219 | 220 | # The current client count 221 | # Use this flag to specify the current client count. Default is 26. 222 | # A number >=25 is required to enable activation of client OSes; for server OSes and Office >=5. 223 | ENV CLIENT_COUNT 26 224 | 225 | # The activation interval (in minutes) 226 | # Use this flag to specify the activation interval (in minutes). Default is 120 minutes (2 hours). 227 | ENV ACTIVATION_INTERVAL 120 228 | 229 | # The renewal interval (in minutes) 230 | # Use this flag to specify the renewal interval (in minutes). Default is 10080 minutes (7 days). 231 | ENV RENEWAL_INTERVAL 10080 232 | 233 | # hwid 234 | # Use this flag to specify a HWID. 235 | # The HWID must be an 16-character string of hex characters. 236 | # The default is "RANDOM" to auto-generate the HWID or type a specific value. 237 | ENV HWID RANDOM 238 | 239 | # log level ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG") 240 | # Use this flag to set a Loglevel. The default is "ERROR". 241 | ENV LOGLEVEL ERROR 242 | 243 | # Log file 244 | # Use this flag to set an output Logfile. The default is "/var/log/pykms_logserver.log". 245 | ENV LOGFILE /var/log/pykms_logserver.log 246 | 247 | # Log file size in MB 248 | # Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default. 249 | ENV LOGSIZE "" 250 | ``` 251 | 252 | ## Activation Procedure 253 | The product asks for a key during installation, so it needs you to enter the GVLK. Then the user can set connection parameters, while KMS server must already be running on server machine. Finally with specific commands, activation occurs automatically and can be extended later every time for another 180 (or 30 or 45) days. 254 | 255 | ### Windows 256 | The `//nologo` option of `cscript` was used only to hide the startup logo. 257 | 258 | ![win1](img/win1.png) 259 | 260 | ![win2](img/win2.png) 261 | 262 | 0. Run a Command Prompt as Administrator (you are directly in `C:\Windows\System32` path). 263 | 1. This is optional, it's for unistalling any existing product key. 264 | 2. Then put in your product's GVLK. 265 | 3. Set connection parameters. 266 | 4. Try online activation, but if that fails with error... 267 | - `0xC004F074` You’ll most likely have to configure your firewall that it accepts incoming connections on TCP port 1688. So for Linux users (server-side with `pykms_Server.py` running): `sudo ufw allow 1688` (revert this rule `sudo ufw delete allow 1688`) should fix that. 268 | - `0xC004F069` Take a look into the [issue here](https://github.com/SystemRage/py-kms/issues/57), it will may help you... 269 | 5. Attempt online activation (now with traffic on 1688 enabled). 270 | 6. View license informations (optional). 271 | 272 | ### Office 273 | Note that you’ll have to install a volume license (VL) version of Office. Office versions downloaded from MSDN and / or Technet are non-VL. 274 | 275 | ![off1](img/off1.png) 276 | 277 | ![off2](img/off2.png) 278 | 279 | ![off3](img/off3.png) 280 | 281 | ![off4](img/off4.png) 282 | 283 | 0. Run a Command Prompt as Administrator and navigate to Office folder `cd C:\ProgramFiles\Microsoft Office\OfficeXX` (64-bit path) or `cd C:\ProgramFiles(x86)\Microsoft Office\OfficeXX` (32-bit path), where XX = `14` for Office 2010, 284 | `15` for Office 2013, `16` for Office 2016 or Office 2019. 285 | 1. As you can see, running `/dstatus`, my Office is expiring (14 days remaining). 286 | 2. Only for example, let's go to uninstall this product. 287 | 3. This is confirmed running `/dstatus` again. 288 | 4. Now i put my product's GVLK (and you your key). 289 | 5. Set the connection parameter KMS server address. 290 | 6. Set the connection parameter KMS server port. 291 | 7. Activate installed Office product key. 292 | 8. View license informations (in my case product is now licensed and remaining grace 180 days as expected). 293 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | ../CHANGELOG.md -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Read the Docs Template documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Aug 26 14:19:49 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | # needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = ['myst_parser', 'sphinx_markdown_tables', 'sphinx_rtd_theme'] 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | # templates_path = ['_templates'] 35 | 36 | # The encoding of source files. 37 | # source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'py-kms' 44 | copyright = u'2020, SystemRage' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | # version = '1.0' 52 | # The full version, including alpha/beta/rc tags. 53 | # release = '1.0' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | # language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | # today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | # today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all 70 | # documents. 71 | # default_role = None 72 | 73 | # If true, '()' will be appended to :func: etc. cross-reference text. 74 | # add_function_parentheses = True 75 | 76 | # If true, the current module name will be prepended to all description 77 | # unit titles (such as .. function::). 78 | # add_module_names = True 79 | 80 | # If true, sectionauthor and moduleauthor directives will be shown in the 81 | # output. They are ignored by default. 82 | # show_authors = False 83 | 84 | # The name of the Pygments (syntax highlighting) style to use. 85 | pygments_style = 'sphinx' 86 | 87 | # A list of ignored prefixes for module index sorting. 88 | # modindex_common_prefix = [] 89 | 90 | # If true, keep warnings as "system message" paragraphs in the built documents. 91 | # keep_warnings = False 92 | 93 | 94 | # -- Options for HTML output ---------------------------------------------- 95 | 96 | # The theme to use for HTML and HTML Help pages. See the documentation for 97 | # a list of builtin themes. 98 | html_theme = 'sphinx_rtd_theme' 99 | 100 | # Theme options are theme-specific and customize the look and feel of a theme 101 | # further. For a list of options available for each theme, see the 102 | # documentation. 103 | # html_theme_options = {} 104 | 105 | # Add any paths that contain custom themes here, relative to this directory. 106 | # html_theme_path = [] 107 | 108 | # The name for this set of Sphinx documents. If None, it defaults to 109 | # " v documentation". 110 | # html_title = None 111 | 112 | # A shorter title for the navigation bar. Default is the same as html_title. 113 | # html_short_title = None 114 | 115 | # The name of an image file (relative to this directory) to place at the top 116 | # of the sidebar. 117 | # html_logo = None 118 | 119 | # The name of an image file (within the static path) to use as favicon of the 120 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 121 | # pixels large. 122 | # html_favicon = None 123 | 124 | # Add any paths that contain custom static files (such as style sheets) here, 125 | # relative to this directory. They are copied after the builtin static files, 126 | # so a file named "default.css" will overwrite the builtin "default.css". 127 | # html_static_path = ['_static'] 128 | 129 | # Add any extra paths that contain custom files (such as robots.txt or 130 | # .htaccess) here, relative to this directory. These files are copied 131 | # directly to the root of the documentation. 132 | # html_extra_path = [] 133 | 134 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 135 | # using the given strftime format. 136 | # html_last_updated_fmt = '%b %d, %Y' 137 | 138 | # If true, SmartyPants will be used to convert quotes and dashes to 139 | # typographically correct entities. 140 | # html_use_smartypants = True 141 | 142 | # Custom sidebar templates, maps document names to template names. 143 | # html_sidebars = {} 144 | 145 | # Additional templates that should be rendered to pages, maps page names to 146 | # template names. 147 | # html_additional_pages = {} 148 | 149 | # If false, no module index is generated. 150 | # html_domain_indices = True 151 | 152 | # If false, no index is generated. 153 | # html_use_index = True 154 | 155 | # If true, the index is split into individual pages for each letter. 156 | # html_split_index = False 157 | 158 | # If true, links to the reST sources are added to the pages. 159 | # html_show_sourcelink = True 160 | 161 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 162 | # html_show_sphinx = True 163 | 164 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 165 | # html_show_copyright = True 166 | 167 | # If true, an OpenSearch description file will be output, and all pages will 168 | # contain a tag referring to it. The value of this option must be the 169 | # base URL from which the finished HTML is served. 170 | # html_use_opensearch = '' 171 | 172 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 173 | # html_file_suffix = None 174 | 175 | # Output file base name for HTML help builder. 176 | htmlhelp_basename = 'py-kms' 177 | 178 | 179 | # -- Options for LaTeX output --------------------------------------------- 180 | 181 | latex_elements = { 182 | # The paper size ('letterpaper' or 'a4paper'). 183 | # 'papersize': 'letterpaper', 184 | 185 | # The font size ('10pt', '11pt' or '12pt'). 186 | # 'pointsize': '10pt', 187 | 188 | # Additional stuff for the LaTeX preamble. 189 | # 'preamble': '', 190 | } 191 | 192 | # Grouping the document tree into LaTeX files. List of tuples 193 | # (source start file, target name, title, 194 | # author, documentclass [howto, manual, or own class]). 195 | latex_documents = [ 196 | ('index', 'py-kms.tex', u'py-kms Documentation', 197 | u'SystemRage', 'manual'), 198 | ] 199 | 200 | # The name of an image file (relative to this directory) to place at the top of 201 | # the title page. 202 | # latex_logo = None 203 | 204 | # For "manual" documents, if this is true, then toplevel headings are parts, 205 | # not chapters. 206 | # latex_use_parts = False 207 | 208 | # If true, show page references after internal links. 209 | # latex_show_pagerefs = False 210 | 211 | # If true, show URL addresses after external links. 212 | # latex_show_urls = False 213 | 214 | # Documents to append as an appendix to all manuals. 215 | # latex_appendices = [] 216 | 217 | # If false, no module index is generated. 218 | # latex_domain_indices = True 219 | 220 | 221 | # -- Options for manual page output --------------------------------------- 222 | 223 | # One entry per manual page. List of tuples 224 | # (source start file, name, description, authors, manual section). 225 | man_pages = [ 226 | ('index', 'py-kms', u'py-kms Documentation', 227 | [u'SystemRage'], 1) 228 | ] 229 | 230 | # If true, show URL addresses after external links. 231 | # man_show_urls = False 232 | 233 | 234 | # -- Options for Texinfo output ------------------------------------------- 235 | 236 | # Grouping the document tree into Texinfo files. List of tuples 237 | # (source start file, target name, title, author, 238 | # dir menu entry, description, category) 239 | texinfo_documents = [ 240 | ('index', 'py-kms', u'py-kms Documentation', 241 | u'SystemRage', 'py-kms', 'KMS Server Emulator written in Python', 242 | 'Miscellaneous'), 243 | ] 244 | 245 | # Documents to append as an appendix to all manuals. 246 | # texinfo_appendices = [] 247 | 248 | # If false, no module index is generated. 249 | # texinfo_domain_indices = True 250 | 251 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 252 | # texinfo_show_urls = 'footnote' 253 | 254 | # If true, do not generate a @detailmenu in the "Top" node's menu. 255 | # texinfo_no_detailmenu = False 256 | -------------------------------------------------------------------------------- /docs/img/off1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Py-KMS-Organization/py-kms/646f4766f4195dbea0695700a7ddaac70a3294f9/docs/img/off1.png -------------------------------------------------------------------------------- /docs/img/off2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Py-KMS-Organization/py-kms/646f4766f4195dbea0695700a7ddaac70a3294f9/docs/img/off2.png -------------------------------------------------------------------------------- /docs/img/off3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Py-KMS-Organization/py-kms/646f4766f4195dbea0695700a7ddaac70a3294f9/docs/img/off3.png -------------------------------------------------------------------------------- /docs/img/off4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Py-KMS-Organization/py-kms/646f4766f4195dbea0695700a7ddaac70a3294f9/docs/img/off4.png -------------------------------------------------------------------------------- /docs/img/webinterface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Py-KMS-Organization/py-kms/646f4766f4195dbea0695700a7ddaac70a3294f9/docs/img/webinterface.png -------------------------------------------------------------------------------- /docs/img/win1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Py-KMS-Organization/py-kms/646f4766f4195dbea0695700a7ddaac70a3294f9/docs/img/win1.png -------------------------------------------------------------------------------- /docs/img/win2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Py-KMS-Organization/py-kms/646f4766f4195dbea0695700a7ddaac70a3294f9/docs/img/win2.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to the py-kms documentation! 2 | ================================================== 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :glob: 9 | 10 | * 11 | -------------------------------------------------------------------------------- /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\complexity.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\complexity.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.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx~=7.2.6 2 | sphinx-rtd-theme~=2.0.0 3 | readthedocs-sphinx-search~=0.3.2 4 | sphinx-markdown-tables~=0.0.17 5 | myst-parser~=2.0.0 6 | -------------------------------------------------------------------------------- /py-kms/pykms_Base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import binascii 4 | import logging 5 | import time 6 | import uuid 7 | 8 | from pykms_Structure import Structure 9 | from pykms_DB2Dict import kmsDB2Dict 10 | from pykms_PidGenerator import epidGenerator 11 | from pykms_Filetimes import filetime_to_dt 12 | from pykms_Sql import sql_update, sql_update_epid 13 | from pykms_Format import justify, byterize, enco, deco, pretty_printer 14 | 15 | #-------------------------------------------------------------------------------------------------------------------------------------------------------- 16 | 17 | loggersrv = logging.getLogger('logsrv') 18 | 19 | class UUID(Structure): 20 | commonHdr = () 21 | structure = ( 22 | ('raw', '16s'), 23 | ) 24 | 25 | def get(self): 26 | return uuid.UUID(bytes_le = enco(str(self), 'latin-1')) 27 | 28 | class kmsBase: 29 | def __init__(self, data, srv_config): 30 | self.data = data 31 | self.srv_config = srv_config 32 | 33 | class kmsRequestStruct(Structure): 34 | commonHdr = () 35 | structure = ( 36 | ('versionMinor', '= RequiredClients: 156 | # fixed to 10 (product server) or 50 (product desktop) 157 | currentClientCount = RequiredClients 158 | if self.srv_config["clientcount"] > RequiredClients: 159 | pretty_printer(log_obj = loggersrv.warning, 160 | put_text = "{reverse}{yellow}{bold}Too many clients ! Fixed with %s{end}" %currentClientCount) 161 | else: 162 | # fixed to 10 (product server) or 50 (product desktop) 163 | currentClientCount = RequiredClients 164 | 165 | 166 | # Get a name for SkuId, AppId. 167 | kmsdb = kmsDB2Dict() 168 | appName, skuName = str(applicationId), str(skuId) 169 | 170 | appitems = kmsdb[2] 171 | for appitem in appitems: 172 | kmsitems = appitem['KmsItems'] 173 | for kmsitem in kmsitems: 174 | skuitems = kmsitem['SkuItems'] 175 | for skuitem in skuitems: 176 | try: 177 | if uuid.UUID(skuitem['Id']) == skuId: 178 | skuName = skuitem['DisplayName'] 179 | break 180 | except: 181 | skuName = skuId 182 | pretty_printer(log_obj = loggersrv.warning, 183 | put_text = "{reverse}{yellow}{bold}Can't find a name for this product !{end}") 184 | 185 | try: 186 | if uuid.UUID(appitem['Id']) == applicationId: 187 | appName = appitem['DisplayName'] 188 | except: 189 | appName = applicationId 190 | pretty_printer(log_obj = loggersrv.warning, 191 | put_text = "{reverse}{yellow}{bold}Can't find a name for this application group !{end}") 192 | 193 | infoDict = { 194 | "machineName" : kmsRequest.getMachineName(), 195 | "clientMachineId" : str(clientMachineId), 196 | "appId" : appName, 197 | "skuId" : skuName, 198 | "licenseStatus" : kmsRequest.getLicenseStatus(), 199 | "requestTime" : int(time.time()), 200 | "kmsEpid" : None 201 | } 202 | 203 | loggersrv.info("Machine Name: %s" % infoDict["machineName"]) 204 | loggersrv.info("Client Machine ID: %s" % infoDict["clientMachineId"]) 205 | loggersrv.info("Application ID: %s" % infoDict["appId"]) 206 | loggersrv.info("SKU ID: %s" % infoDict["skuId"]) 207 | loggersrv.info("License Status: %s" % infoDict["licenseStatus"]) 208 | loggersrv.info("Request Time: %s" % local_dt.strftime('%Y-%m-%d %H:%M:%S %Z (UTC%z)')) 209 | 210 | if self.srv_config['loglevel'] == 'MININFO': 211 | loggersrv.mininfo("", extra = {'host': str(self.srv_config['raddr']), 212 | 'status' : infoDict["licenseStatus"], 213 | 'product' : infoDict["skuId"]}) 214 | # Create database. 215 | if self.srv_config['sqlite']: 216 | sql_update(self.srv_config['sqlite'], infoDict) 217 | 218 | return self.createKmsResponse(kmsRequest, currentClientCount, appName) 219 | 220 | def createKmsResponse(self, kmsRequest, currentClientCount, appName): 221 | response = self.kmsResponseStruct() 222 | response['versionMinor'] = kmsRequest['versionMinor'] 223 | response['versionMajor'] = kmsRequest['versionMajor'] 224 | 225 | if not self.srv_config["epid"]: 226 | response["kmsEpid"] = epidGenerator(kmsRequest['kmsCountedId'].get(), kmsRequest['versionMajor'], 227 | self.srv_config["lcid"]).encode('utf-16le') 228 | else: 229 | response["kmsEpid"] = self.srv_config["epid"].encode('utf-16le') 230 | 231 | response['clientMachineId'] = kmsRequest['clientMachineId'] 232 | # rule: timeserver - 4h <= timeclient <= timeserver + 4h, check if is satisfied (TODO). 233 | response['responseTime'] = kmsRequest['requestTime'] 234 | response['currentClientCount'] = currentClientCount 235 | response['vLActivationInterval'] = self.srv_config["activation"] 236 | response['vLRenewalInterval'] = self.srv_config["renewal"] 237 | 238 | # Update database epid. 239 | if self.srv_config['sqlite']: 240 | sql_update_epid(self.srv_config['sqlite'], kmsRequest, response, appName) 241 | 242 | loggersrv.info("Server ePID: %s" % response["kmsEpid"].decode('utf-16le')) 243 | 244 | return response 245 | 246 | 247 | import pykms_RequestV4, pykms_RequestV5, pykms_RequestV6, pykms_RequestUnknown 248 | 249 | def generateKmsResponseData(data, srv_config): 250 | version = kmsBase.GenericRequestHeader(data)['versionMajor'] 251 | currentDate = time.strftime("%a %b %d %H:%M:%S %Y") 252 | 253 | if version == 4: 254 | loggersrv.info("Received V%d request on %s." % (version, currentDate)) 255 | messagehandler = pykms_RequestV4.kmsRequestV4(data, srv_config) 256 | elif version == 5: 257 | loggersrv.info("Received V%d request on %s." % (version, currentDate)) 258 | messagehandler = pykms_RequestV5.kmsRequestV5(data, srv_config) 259 | elif version == 6: 260 | loggersrv.info("Received V%d request on %s." % (version, currentDate)) 261 | messagehandler = pykms_RequestV6.kmsRequestV6(data, srv_config) 262 | else: 263 | loggersrv.info("Unhandled KMS version V%d." % version) 264 | messagehandler = pykms_RequestUnknown.kmsRequestUnknown(data, srv_config) 265 | 266 | return messagehandler.executeRequestLogic() 267 | -------------------------------------------------------------------------------- /py-kms/pykms_Connect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import socket 5 | import selectors 6 | import ipaddress 7 | import logging 8 | from pykms_Format import pretty_printer 9 | loggersrv = logging.getLogger('logsrv') 10 | 11 | # https://github.com/python/cpython/blob/master/Lib/socket.py 12 | def has_dualstack_ipv6(): 13 | """ Return True if the platform supports creating a SOCK_STREAM socket 14 | which can handle both AF_INET and AF_INET6 (IPv4 / IPv6) connections. 15 | """ 16 | if not socket.has_ipv6 or not hasattr(socket._socket, 'IPPROTO_IPV6') or not hasattr(socket._socket, 'IPV6_V6ONLY'): 17 | return False 18 | try: 19 | with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as sock: 20 | sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) 21 | return True 22 | except socket.error: 23 | return False 24 | 25 | def create_server_sock(address, *, family = socket.AF_INET, backlog = None, reuse_port = False, dualstack_ipv6 = False): 26 | """ Convenience function which creates a SOCK_STREAM type socket 27 | bound to *address* (a 2-tuple (host, port)) and return the socket object. 28 | Internally it takes care of choosing the right address family (IPv4 or IPv6),depending on 29 | the host specified in *address* tuple. 30 | 31 | *family* should be either AF_INET or AF_INET6. 32 | *backlog* is the queue size passed to socket.listen(). 33 | *reuse_port* if True and the platform supports it, we will use the SO_REUSEPORT socket option. 34 | *dualstack_ipv6* if True and the platform supports it, it will create an AF_INET6 socket able to accept both IPv4 or IPv6 connections; 35 | when False it will explicitly disable this option on platforms that enable it by default (e.g. Linux). 36 | """ 37 | if reuse_port and not hasattr(socket._socket, "SO_REUSEPORT"): 38 | pretty_printer(log_obj = loggersrv.warning, put_text = "{reverse}{yellow}{bold}SO_REUSEPORT not supported on this platform - ignoring socket option.{end}") 39 | reuse_port = False 40 | 41 | if dualstack_ipv6: 42 | if not has_dualstack_ipv6(): 43 | raise ValueError("dualstack_ipv6 not supported on this platform") 44 | if family != socket.AF_INET6: 45 | raise ValueError("dualstack_ipv6 requires AF_INET6 family") 46 | 47 | sock = socket.socket(family, socket.SOCK_STREAM) 48 | try: 49 | # Note about Windows. We don't set SO_REUSEADDR because: 50 | # 1) It's unnecessary: bind() will succeed even in case of a 51 | # previous closed socket on the same address and still in 52 | # TIME_WAIT state. 53 | # 2) If set, another socket is free to bind() on the same 54 | # address, effectively preventing this one from accepting 55 | # connections. Also, it may set the process in a state where 56 | # it'll no longer respond to any signals or graceful kills. 57 | # See: msdn2.microsoft.com/en-us/library/ms740621(VS.85).aspx 58 | if os.name not in ('nt', 'cygwin') and hasattr(socket._socket, 'SO_REUSEADDR'): 59 | try: 60 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 61 | except socket.error: 62 | # Fail later on bind(), for platforms which may not 63 | # support this option. 64 | pass 65 | if reuse_port: 66 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 67 | if socket.has_ipv6 and family == socket.AF_INET6: 68 | if dualstack_ipv6: 69 | sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) 70 | elif hasattr(socket._socket, "IPV6_V6ONLY") and hasattr(socket._socket, "IPPROTO_IPV6"): 71 | sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) 72 | try: 73 | sock.bind(address) 74 | except socket.error as err: 75 | msg = '%s (while attempting to bind on address %r)' %(err.strerror, address) 76 | raise socket.error(err.errno, msg) from None 77 | 78 | if backlog is None: 79 | sock.listen() 80 | else: 81 | sock.listen(backlog) 82 | return sock 83 | except socket.error: 84 | sock.close() 85 | raise 86 | 87 | # Giampaolo Rodola' class (license MIT) revisited for py-kms. 88 | # http://code.activestate.com/recipes/578504-server-supporting-ipv4-and-ipv6/ 89 | class MultipleListener(object): 90 | """ Listen on multiple addresses specified as a list of 91 | (`host`, `port`, `backlog`, `reuse_port`) tuples. 92 | Useful to listen on both IPv4 and IPv6 on those systems where a dual stack 93 | is not supported natively (Windows and many UNIXes). 94 | 95 | Calls like settimeout() and setsockopt() will be applied to all sockets. 96 | Calls like gettimeout() or getsockopt() will refer to the first socket in the list. 97 | """ 98 | def __init__(self, addresses = [], want_dual = False): 99 | self.socks, self.sockmap = [], {} 100 | completed = False 101 | self.cant_dual = [] 102 | 103 | try: 104 | for addr in addresses: 105 | addr = self.check(addr) 106 | ip_ver = ipaddress.ip_address(addr[0]) 107 | 108 | if ip_ver.version == 4 and want_dual: 109 | self.cant_dual.append(addr[0]) 110 | 111 | sock = create_server_sock((addr[0], addr[1]), 112 | family = (socket.AF_INET if ip_ver.version == 4 else socket.AF_INET6), 113 | backlog = addr[2], 114 | reuse_port = addr[3], 115 | dualstack_ipv6 = (False if ip_ver.version == 4 else want_dual)) 116 | self.socks.append(sock) 117 | self.sockmap[sock.fileno()] = sock 118 | 119 | completed = True 120 | finally: 121 | if not completed: 122 | self.close() 123 | 124 | def __enter__(self): 125 | return self 126 | 127 | def __exit__(self): 128 | self.close() 129 | 130 | def __repr__(self): 131 | addrs = [] 132 | for sock in self.socks: 133 | try: 134 | addrs.append(sock.getsockname()) 135 | except socket.error: 136 | addrs.append(()) 137 | return "<%s(%r) at %#x>" %(self.__class__.__name__, addrs, id(self)) 138 | 139 | def filenos(self): 140 | """ Return sockets' file descriptors as a list of integers. """ 141 | return list(self.sockmap.keys()) 142 | 143 | def register(self, pollster): 144 | for fd in self.filenos(): 145 | pollster.register(fileobj = fd, events = selectors.EVENT_READ) 146 | 147 | def multicall(self, name, *args, **kwargs): 148 | for sock in self.socks: 149 | meth = getattr(sock, name) 150 | meth(*args, **kwargs) 151 | 152 | def poll(self): 153 | """ Return the first readable fd. """ 154 | if hasattr(selectors, 'PollSelector'): 155 | pollster = selectors.PollSelector 156 | else: 157 | pollster = selectors.SelectSelector 158 | 159 | timeout = self.gettimeout() 160 | 161 | with pollster() as pollster: 162 | self.register(pollster) 163 | fds = pollster.select(timeout) 164 | 165 | if timeout and fds == []: 166 | raise socket.timeout('timed out') 167 | try: 168 | return fds[0][0].fd 169 | except IndexError: 170 | # non-blocking socket 171 | pass 172 | 173 | def accept(self): 174 | """ Accept a connection from the first socket which is ready to do so. """ 175 | fd = self.poll() 176 | sock = (self.sockmap[fd] if fd else self.socks[0]) 177 | return sock.accept() 178 | 179 | def getsockname(self): 180 | """ Return first registered socket's own address. """ 181 | return self.socks[0].getsockname() 182 | 183 | def getsockopt(self, level, optname, buflen = 0): 184 | """ Return first registered socket's options. """ 185 | return self.socks[0].getsockopt(level, optname, buflen) 186 | 187 | def gettimeout(self): 188 | """ Return first registered socket's timeout. """ 189 | return self.socks[0].gettimeout() 190 | 191 | def settimeout(self, timeout): 192 | """ Set timeout for all registered sockets. """ 193 | self.multicall('settimeout', timeout) 194 | 195 | def setblocking(self, flag): 196 | """ Set non-blocking mode for all registered sockets. """ 197 | self.multicall('setblocking', flag) 198 | 199 | def setsockopt(self, level, optname, value): 200 | """ Set option for all registered sockets. """ 201 | self.multicall('setsockopt', level, optname, value) 202 | 203 | def shutdown(self, how): 204 | """ Shut down all registered sockets. """ 205 | self.multicall('shutdown', how) 206 | 207 | def close(self): 208 | """ Close all registered sockets. """ 209 | self.multicall('close') 210 | self.socks, self.sockmap = [], {} 211 | 212 | def check(self, address): 213 | if len(address) == 1: 214 | raise socket.error("missing `host` or `port` parameter.") 215 | if len(address) == 2: 216 | address += (None, True,) 217 | elif len(address) == 3: 218 | address += (True,) 219 | return address 220 | -------------------------------------------------------------------------------- /py-kms/pykms_DB2Dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import xml.etree.ElementTree as ET 5 | 6 | #--------------------------------------------------------------------------------------------------------------------------------------------------------- 7 | 8 | def kmsDB2Dict(): 9 | path = os.path.join(os.path.dirname(__file__), 'KmsDataBase.xml') 10 | root = ET.parse(path).getroot() 11 | 12 | kmsdb, child1, child2, child3 = [ [] for _ in range(4) ] 13 | 14 | ## Get winbuilds. 15 | for winbuild in root.iter('WinBuild'): 16 | child1.append(winbuild.attrib) 17 | 18 | kmsdb.append(child1) 19 | 20 | ## Get csvlkitem data. 21 | child1 = [] 22 | for csvlk in root.iter('CsvlkItem'): 23 | for activ in csvlk.iter('Activate'): 24 | child2.append(activ.attrib['KmsItem']) 25 | csvlk.attrib.update({'Activate' : child2}) 26 | child1.append(csvlk.attrib) 27 | child2 = [] 28 | 29 | kmsdb.append(child1) 30 | 31 | ## Get appitem data. 32 | child1 = [] 33 | for app in root.iter('AppItem'): 34 | for kms in app.iter('KmsItem'): 35 | for sku in kms.iter('SkuItem'): 36 | child3.append(sku.attrib) 37 | kms.attrib.update({'SkuItems' : child3}) 38 | child2.append(kms.attrib) 39 | child3 = [] 40 | 41 | app.attrib.update({'KmsItems' : child2}) 42 | child1.append(app.attrib) 43 | child2 = [] 44 | 45 | kmsdb.append(child1) 46 | 47 | return kmsdb 48 | -------------------------------------------------------------------------------- /py-kms/pykms_Filetimes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2009, David Buxton 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are 8 | # met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 17 | # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 | # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | """Tools to convert between Python datetime instances and Microsoft times. 28 | """ 29 | from datetime import datetime, timedelta, tzinfo 30 | from calendar import timegm 31 | 32 | 33 | # http://support.microsoft.com/kb/167296 34 | # How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME 35 | EPOCH_AS_FILETIME = 116444736000000000 # January 1, 1970 as MS file time 36 | HUNDREDS_OF_NANOSECONDS = 10000000 37 | 38 | 39 | ZERO = timedelta(0) 40 | HOUR = timedelta(hours=1) 41 | 42 | 43 | class UTC(tzinfo): 44 | """UTC""" 45 | def utcoffset(self, dt): 46 | return ZERO 47 | 48 | def tzname(self, dt): 49 | return "UTC" 50 | 51 | def dst(self, dt): 52 | return ZERO 53 | 54 | 55 | utc = UTC() 56 | 57 | 58 | def dt_to_filetime(dt): 59 | """Converts a datetime to Microsoft filetime format. If the object is 60 | time zone-naive, it is forced to UTC before conversion. 61 | 62 | >>> "%.0f" % dt_to_filetime(datetime(2009, 7, 25, 23, 0)) 63 | '128930364000000000' 64 | 65 | >>> "%.0f" % dt_to_filetime(datetime(1970, 1, 1, 0, 0, tzinfo=utc)) 66 | '116444736000000000' 67 | 68 | >>> "%.0f" % dt_to_filetime(datetime(1970, 1, 1, 0, 0)) 69 | '116444736000000000' 70 | 71 | >>> dt_to_filetime(datetime(2009, 7, 25, 23, 0, 0, 100)) 72 | 128930364000001000 73 | """ 74 | if (dt.tzinfo is None) or (dt.tzinfo.utcoffset(dt) is None): 75 | dt = dt.replace(tzinfo=utc) 76 | ft = EPOCH_AS_FILETIME + (timegm(dt.timetuple()) * HUNDREDS_OF_NANOSECONDS) 77 | return ft + (dt.microsecond * 10) 78 | 79 | 80 | def filetime_to_dt(ft): 81 | """Converts a Microsoft filetime number to a Python datetime. The new 82 | datetime object is time zone-naive but is equivalent to tzinfo=utc. 83 | 84 | >>> filetime_to_dt(116444736000000000) 85 | datetime.datetime(1970, 1, 1, 0, 0) 86 | 87 | >>> filetime_to_dt(128930364000000000) 88 | datetime.datetime(2009, 7, 25, 23, 0) 89 | 90 | >>> filetime_to_dt(128930364000001000) 91 | datetime.datetime(2009, 7, 25, 23, 0, 0, 100) 92 | """ 93 | # Get seconds and remainder in terms of Unix epoch 94 | (s, ns100) = divmod(ft - EPOCH_AS_FILETIME, HUNDREDS_OF_NANOSECONDS) 95 | # Convert to datetime object 96 | dt = datetime.utcfromtimestamp(s) 97 | # Add remainder in as microseconds. Python 3.2 requires an integer 98 | dt = dt.replace(microsecond=(ns100 // 10)) 99 | return dt 100 | 101 | 102 | if __name__ == "__main__": 103 | import doctest 104 | 105 | doctest.testmod() 106 | -------------------------------------------------------------------------------- /py-kms/pykms_PidGenerator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import datetime 4 | import random 5 | import time 6 | import uuid 7 | from ast import literal_eval 8 | 9 | from pykms_DB2Dict import kmsDB2Dict 10 | 11 | #--------------------------------------------------------------------------------------------------------------------------------------------------------- 12 | 13 | def epidGenerator(kmsId, version, lcid): 14 | kmsdb = kmsDB2Dict() 15 | winbuilds, csvlkitems, appitems = kmsdb[0], kmsdb[1], kmsdb[2] 16 | hosts, pkeys = [ [] for _ in range(2) ] 17 | 18 | # Product Specific Detection (Get all CSVLK GroupID and PIDRange good for EPID generation), then 19 | # Generate Part 2: Group ID and Product Key ID Range 20 | for csvlkitem in csvlkitems: 21 | try: 22 | if kmsId in [ uuid.UUID(kmsitem) for kmsitem in csvlkitem['Activate'] ]: 23 | pkeys.append( (csvlkitem['GroupId'], csvlkitem['MinKeyId'], csvlkitem['MaxKeyId'], csvlkitem['InvalidWinBuild']) ) 24 | else: 25 | # fallback to Windows Server 2019 parameters. 26 | pkeys.append( ('206', '551000000', '570999999', '[0,1,2]') ) 27 | except IndexError: 28 | # fallback to Windows Server 2019 parameters. 29 | pkeys.append( ('206', '551000000', '570999999', '[0,1,2]') ) 30 | 31 | pkey = random.choice(pkeys) 32 | GroupId, MinKeyId, MaxKeyId, Invalid = int(pkey[0]), int(pkey[1]), int(pkey[2]), literal_eval(pkey[3]) 33 | 34 | # Get all KMS Server Host Builds good for EPID generation, then 35 | # Generate Part 1 & 7: Host Type and KMS Server OS Build 36 | for winbuild in winbuilds: 37 | try: 38 | # Check versus "InvalidWinBuild". 39 | if int(winbuild['WinBuildIndex']) not in Invalid: 40 | hosts.append(winbuild) 41 | except KeyError: 42 | # fallback to Windows Server 2019 parameters. 43 | hosts.append( {'BuildNumber':'17763', 'PlatformId':'3612', 'MinDate':'02/10/2018'} ) 44 | 45 | host = random.choice(hosts) 46 | BuildNumber, PlatformId, MinDate = host['BuildNumber'], host['PlatformId'], host['MinDate'] 47 | 48 | # Generate Part 3 and Part 4: Product Key ID 49 | productKeyID = random.randint(MinKeyId, MaxKeyId) 50 | 51 | # Generate Part 5: License Channel (00=Retail, 01=Retail, 02=OEM, 03=Volume(GVLK,MAK)) - always 03 52 | licenseChannel = 3 53 | 54 | # Generate Part 6: Language - use system default language, 1033 is en-us 55 | languageCode = lcid # (C# CultureInfo.InstalledUICulture.LCID) 56 | 57 | # Generate Part 8: KMS Host Activation Date 58 | d = datetime.datetime.strptime(MinDate, "%d/%m/%Y") 59 | minTime = datetime.date(d.year, d.month, d.day) 60 | 61 | # Generate Year and Day Number 62 | randomDate = datetime.date.fromtimestamp(random.randint(time.mktime(minTime.timetuple()), time.mktime(datetime.datetime.now().timetuple()))) 63 | firstOfYear = datetime.date(randomDate.year, 1, 1) 64 | randomDayNumber = int((time.mktime(randomDate.timetuple()) - time.mktime(firstOfYear.timetuple())) / 86400 + 0.5) 65 | 66 | # Generate the EPID string 67 | result = [] 68 | result.append(str(PlatformId).rjust(5, "0")) 69 | result.append("-") 70 | result.append(str(GroupId).rjust(5, "0")) 71 | result.append("-") 72 | result.append(str(productKeyID // 1000000).rjust(3, "0")) 73 | result.append("-") 74 | result.append(str(productKeyID % 1000000).rjust(6, "0")) 75 | result.append("-") 76 | result.append(str(licenseChannel).rjust(2, "0")) 77 | result.append("-") 78 | result.append(str(languageCode)) 79 | result.append("-") 80 | result.append(str(BuildNumber).rjust(4, "0")) 81 | result.append(".0000-") 82 | result.append(str(randomDayNumber).rjust(3, "0")) 83 | result.append(str(randomDate.year).rjust(4, "0")) 84 | 85 | return "".join(result) 86 | -------------------------------------------------------------------------------- /py-kms/pykms_RequestUnknown.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import struct 4 | 5 | from pykms_Base import kmsBase 6 | from pykms_Misc import ErrorCodes 7 | 8 | #--------------------------------------------------------------------------------------------------------------------------------------------------------- 9 | 10 | class kmsRequestUnknown(kmsBase): 11 | def executeRequestLogic(self): 12 | finalResponse = bytearray() 13 | finalResponse.extend(bytearray(struct.pack('> 4 77 | 78 | # Remainding bytes. 79 | k = messageSize & 0xf 80 | 81 | # Hash. 82 | for i in range(0, j): 83 | xorBuffer(message, i << 4, hashBuffer, 16) 84 | hashBuffer = bytearray(aes.encrypt(hashBuffer, key, len(key))) 85 | 86 | # Bit Padding. 87 | ii = 0 88 | for i in range(j << 4, k + (j << 4)): 89 | lastBlock[ii] = message[i] 90 | ii += 1 91 | lastBlock[k] = 0x80 92 | 93 | xorBuffer(lastBlock, 0, hashBuffer, 16) 94 | hashBuffer = bytearray(aes.encrypt(hashBuffer, key, len(key))) 95 | 96 | return bytes(hashBuffer) 97 | 98 | def generateResponse(self, responseBuffer, thehash): 99 | response = self.ResponseV4() 100 | bodyLength = len(responseBuffer) + len(thehash) 101 | response['bodyLength1'] = bodyLength 102 | response['bodyLength2'] = bodyLength 103 | response['response'] = responseBuffer 104 | response['hash'] = thehash 105 | response['padding'] = bytes(bytearray(self.getPadding(bodyLength))) 106 | 107 | ## Debug stuff. 108 | pretty_printer(num_text = 16, where = "srv") 109 | response = byterize(response) 110 | loggersrv.debug("KMS V4 Response: \n%s\n" % justify(response.dump(print_to_stdout = False))) 111 | loggersrv.debug("KMS V4 Response Bytes: \n%s\n" % justify(deco(binascii.b2a_hex(enco(str(response), 'latin-1')), 'utf-8'))) 112 | 113 | return str(response) 114 | 115 | def generateRequest(self, requestBase): 116 | thehash = self.generateHash(bytearray(enco(str(requestBase), 'latin-1'))) 117 | 118 | request = kmsRequestV4.RequestV4() 119 | bodyLength = len(requestBase) + len(thehash) 120 | request['bodyLength1'] = bodyLength 121 | request['bodyLength2'] = bodyLength 122 | request['request'] = requestBase 123 | request['hash'] = thehash 124 | request['padding'] = bytes(bytearray(self.getPadding(bodyLength))) 125 | 126 | ## Debug stuff. 127 | pretty_printer(num_text = 10, where = "clt") 128 | request = byterize(request) 129 | loggersrv.debug("Request V4 Data: \n%s\n" % justify(request.dump(print_to_stdout = False))) 130 | loggersrv.debug("Request V4: \n%s\n" % justify(deco(binascii.b2a_hex(enco(str(request), 'latin-1')), 'utf-8'))) 131 | 132 | return request 133 | -------------------------------------------------------------------------------- /py-kms/pykms_RequestV5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import logging 4 | import binascii 5 | import hashlib 6 | import random 7 | 8 | import pykms_Aes as aes 9 | from pykms_Base import kmsBase 10 | from pykms_Structure import Structure 11 | from pykms_Format import justify, byterize, enco, deco, pretty_printer 12 | 13 | #-------------------------------------------------------------------------------------------------------------------------------------------------------- 14 | 15 | loggersrv = logging.getLogger('logsrv') 16 | 17 | class kmsRequestV5(kmsBase): 18 | class RequestV5(Structure): 19 | class Message(Structure): 20 | commonHdr = () 21 | structure = ( 22 | ('salt', '16s'), 23 | ('encrypted', '240s'), 24 | ('padding', ':'), 25 | ) 26 | 27 | commonHdr = () 28 | structure = ( 29 | ('bodyLength1', ' 10: # Wait 10 seconds before accepting requests 103 | return 'OK', 200 104 | else: 105 | return 'Not ready', 503 106 | 107 | @app.route('/livez') 108 | def livez(): 109 | try: 110 | _env_check() 111 | return 'OK', 200 # There are no checks for liveness, so we just return OK 112 | except Exception as e: 113 | return f'Whooops! {e}', 503 114 | 115 | @app.route('/license') 116 | def license(): 117 | _increase_serve_count() 118 | with open(os.environ.get('PYKMS_LICENSE_PATH', '../LICENSE'), 'r') as f: 119 | return render_template( 120 | 'license.html', 121 | path='/license/', 122 | license=f.read() 123 | ) 124 | 125 | @app.route('/products') 126 | def products(): 127 | _increase_serve_count() 128 | items, ignored = _get_kms_items_cache() 129 | countProducts = len(items) 130 | countProductsWindows = len([i for i in items if 'windows' in i.lower()]) 131 | countProductsOffice = len([i for i in items if 'office' in i.lower()]) 132 | return render_template( 133 | 'products.html', 134 | path='/products/', 135 | products=items, 136 | filtered=ignored, 137 | count_products=countProducts, 138 | count_products_windows=countProductsWindows, 139 | count_products_office=countProductsOffice 140 | ) 141 | -------------------------------------------------------------------------------- /py-kms/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-kms {% block title %}{% endblock %} 6 | 7 | 24 | 25 | 26 |
27 | {% block content %}{% endblock %} 28 | 29 | {% if path != '/' %} 30 | 35 | {% endif %} 36 |
37 | 38 |
39 |
40 |

41 | py-kms is online since {{ start_time }}. 42 | This instance was accessed {{ get_serve_count() }} times. View this softwares license here. 43 | {% if version_info %} 44 |
This instance is running version "{{ version_info['hash'] }}" from branch "{{ version_info['branch'] }}" of py-kms. 45 | {% endif %} 46 |

47 |
48 |
49 | 50 | 55 | 56 | -------------------------------------------------------------------------------- /py-kms/templates/clients.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}clients{% endblock %} 4 | 5 | {% block style %} 6 | th { 7 | white-space: nowrap; 8 | } 9 | {% endblock %} 10 | 11 | {% block content %} 12 | {% if error %} 13 |
14 |
15 | Whoops! Something went wrong... 16 |
17 |
18 | {{ error }} 19 |
20 |
21 | {% else %} 22 | 48 | 49 |
50 | 51 | {% if clients %} 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | {% for client in clients %} 67 | 68 | 69 | 76 | 77 | 78 | 79 | 80 | 87 | 88 | 89 | {% endfor %} 90 | 91 |
Client IDMachine NameApplication IDSKU IDLicense StatusLast SeenKMS EPIDSeen Count
{{ client.clientMachineId }}
70 | {% if client.machineName | length > 16 %} 71 | {{ client.machineName | truncate(16, True, '...') }} 72 | {% else %} 73 | {{ client.machineName }} 74 | {% endif %} 75 | {{ client.applicationId }}{{ client.skuId }}{{ client.licenseStatus }}{{ client.lastRequestTime }} 81 | {% if client.kmsEpid | length > 16 %} 82 | {{ client.kmsEpid | truncate(16, True, '...') }} 83 | {% else %} 84 | {{ client.kmsEpid }} 85 | {% endif %} 86 | {{ client.requestCount }}
92 | {% else %} 93 |
94 |
95 |

Whoops?

96 |
97 |
98 | This page seems to be empty, because no clients are available. Try to use the server with a compartible client to add it to the database. 99 |
100 |
101 | {% endif %} 102 | {% endif %} 103 | {% endblock %} -------------------------------------------------------------------------------- /py-kms/templates/license.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}license{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
{{ license }}
8 |
9 | {% endblock %} -------------------------------------------------------------------------------- /py-kms/templates/products.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}clients{% endblock %} 4 | 5 | {% block content %} 6 | 32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {% for name, gvlk in products | dictsort %} 44 | {% if gvlk %} 45 | 46 | 47 | 48 | 49 | {% endif %} 50 | {% endfor %} 51 | 52 |
NameGVLK
{{ name }}
{{ gvlk }}
53 | {% endblock %} -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | docker/docker-py3-kms/requirements.txt --------------------------------------------------------------------------------