diff --git a/.github/workflows/_crypt.yml b/.github/workflows/_crypt.yml new file mode 100644 index 0000000..adbd587 --- /dev/null +++ b/.github/workflows/_crypt.yml @@ -0,0 +1,158 @@ +# This file is autogenerated by maturin v1.7.4 +# To update, run +# +# maturin generate-ci github +# +name: native _crypt + +on: + push: + branches: + - main + tags: + - '*' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-latest + target: x86_64 + - runner: ubuntu-latest + target: x86 + - runner: ubuntu-latest + target: aarch64 + - runner: ubuntu-latest + target: armv7 + - runner: ubuntu-latest + target: s390x + - runner: ubuntu-latest + target: ppc64le + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.13 + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + working-directory: ./_crypt + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + musllinux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-latest + target: x86_64 + - runner: ubuntu-latest + target: x86 + - runner: ubuntu-latest + target: aarch64 + - runner: ubuntu-latest + target: armv7 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + working-directory: ./_crypt + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + manylinux: musllinux_1_2 + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-musllinux-${{ matrix.platform.target }} + path: dist + + macos: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: macos-12 + target: x86_64 + - runner: macos-14 + target: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + working-directory: ./_crypt + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.platform.target }} + path: _crypt/dist + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + rust-toolchain: 1.82.0 + working-directory: ./_crypt + command: sdist + args: --out dist + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: wheels-sdist + path: _crypt/dist + + release: + name: Release + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + needs: [linux, musllinux, macos, sdist] + permissions: + # Use to sign the release artifacts + id-token: write + # Used to upload release artifacts + contents: write + # Used to generate artifact attestation + attestations: write + steps: + - uses: actions/download-artifact@v4 + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v1 + with: + subject-path: 'wheels-*/*' + - name: Publish to PyPI + if: "startsWith(github.ref, 'refs/tags/')" + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --non-interactive --skip-existing wheels-*/* diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b7adcdf..f2d9d0f 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -4,6 +4,8 @@ on: push: branches: - main + tags: + - '*' pull_request: ~ concurrency: diff --git a/.gitignore b/.gitignore index 061058e..18cc0f6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ __pycache__ */dist */*.egg-info +.python-version diff --git a/_crypt/.gitignore b/_crypt/.gitignore new file mode 100644 index 0000000..c8f0442 --- /dev/null +++ b/_crypt/.gitignore @@ -0,0 +1,72 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version diff --git a/_crypt/Cargo.lock b/_crypt/Cargo.lock new file mode 100644 index 0000000..13c8b04 --- /dev/null +++ b/_crypt/Cargo.lock @@ -0,0 +1,171 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "deprecated-_crypt-alternative" +version = "0.1.0" +dependencies = [ + "pyo3", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" diff --git a/_crypt/Cargo.toml b/_crypt/Cargo.toml new file mode 100644 index 0000000..56c4a61 --- /dev/null +++ b/_crypt/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "deprecated-_crypt-alternative" +description = "Python deprecated library `_crypt` replacement." +edition = "2021" +version = "0.1.0" +authors = ["Jeong, YunWon "] +repository = "https://github.com/RustPython/RustPython" +license = "LGPL-2.1" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "_crypt" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { version = "0.22.0", features = ["abi3-py39"] } diff --git a/_crypt/pyproject.toml b/_crypt/pyproject.toml new file mode 100644 index 0000000..45102a2 --- /dev/null +++ b/_crypt/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["maturin>=1.7,<2.0"] +build-backend = "maturin" + +[project] +name = "deprecated-_crypt_alternative" +requires-python = ">=3.13" # Use standard _crypt module for lower versions +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] +[tool.maturin] +features = ["pyo3/extension-module"] diff --git a/_crypt/src/lib.rs b/_crypt/src/lib.rs new file mode 100644 index 0000000..9ae2855 --- /dev/null +++ b/_crypt/src/lib.rs @@ -0,0 +1,46 @@ +use std::ffi::CStr; +use pyo3::{prelude::*, ffi::{PyExc_OSError, PyErr_SetFromErrno}, types::PyString, exceptions::PyValueError}; + +// Future Rust versions will support `unsafe extern` +// unsafe +extern "C" { + #[link_name = "crypt"] + fn extern_crypt(key: *const u8, salt: *const u8) -> *const u8; +} + +/// Hashes the concatenation of a word and a salt using SHA-256. +#[pyfunction] +fn crypt(word: &Bound, salt: &Bound) -> PyResult { + let word = word.encode_utf8()?; + let salt = salt.encode_utf8()?; + if word.as_bytes().contains(&b'\0') || salt.as_bytes().contains(&b'\0') { + return Err(PyValueError::new_err("embedded null character")); + } + let crypt_result = unsafe { + let word = word.as_bytes(); + let salt = salt.as_bytes(); + let result = extern_crypt(word.as_ptr(), salt.as_ptr()); + if result.is_null() { + let err_ptr = PyErr_SetFromErrno(PyExc_OSError); + let py = Python::assume_gil_acquired(); + return Err(PyErr::from_value_bound(Bound::from_owned_ptr_or_err(py, err_ptr)?)); + } + CStr::from_ptr(result as *const _).to_str().expect("OS crypt returns weird values") + }; + Ok(crypt_result.to_string()) +} + +/// A Python module implemented in Rust. +#[pymodule] +fn _crypt(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(crypt, m)?)?; + Ok(()) +} + +#[test] +fn test_extern_crypt() { + let key = c""; + let salt = c"$6$V.wBvD6qcC/2U9B/"; + let r = unsafe { extern_crypt(key.as_ptr() as *const u8, salt.as_ptr() as *const u8) }; + assert_ne!(r, std::ptr::null()); +} \ No newline at end of file diff --git a/crypt/crypt/__init__.py b/crypt/crypt/__init__.py index debb895..c51a12c 100644 --- a/crypt/crypt/__init__.py +++ b/crypt/crypt/__init__.py @@ -8,7 +8,11 @@ if _sys.platform == 'win32': raise ImportError("The crypt module is not supported on Windows") else: - raise ImportError("The required _crypt module was not built as part of CPython") + # python-deadlib: Add a note for deprecated-_crypt-alternative + # raise ImportError("The required _crypt module was not built as part of CPython") + raise ImportError( + "The required _crypt module is no longer a part of CPython. " + "`deprecated-_crypt-alternative` can be used as a replacement.") import errno import string as _string diff --git a/crypt/pyproject.toml b/crypt/pyproject.toml index 8856258..ed3ef77 100644 --- a/crypt/pyproject.toml +++ b/crypt/pyproject.toml @@ -21,5 +21,8 @@ find = {include = ["crypt*"]} "Homepage" = "https://github.com/youknowone/python-deadlib" [build-system] -requires = ["setuptools>=75.0"] +requires = [ + "setuptools>=75.0", + "deprecated-_crypt-alternative>=0.1.0", +] build-backend = "setuptools.build_meta"