pax_global_header00006660000000000000000000000064142147505400014514gustar00rootroot0000000000000052 comment=abd2714d59e22b8965e509361d059133e9c30117 python-pam-2.0.2/000077500000000000000000000000001421475054000136115ustar00rootroot00000000000000python-pam-2.0.2/.github/000077500000000000000000000000001421475054000151515ustar00rootroot00000000000000python-pam-2.0.2/.github/workflows/000077500000000000000000000000001421475054000172065ustar00rootroot00000000000000python-pam-2.0.2/.github/workflows/main.yml000066400000000000000000000010351421475054000206540ustar00rootroot00000000000000name: Python package on: [push] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.10"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install tox tox-gh-actions - name: Tox test run: | tox -vv python-pam-2.0.2/.gitignore000066400000000000000000000014501421475054000156010ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ bin/ build/ develop-eggs/ dist/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # Installer logs pip-log.txt pip-delete-this-directory.txt # 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 # Sphinx documentation docs/_build/ # workspace files are user-specific *.sublime-workspace # project files should be checked into the repository, unless a significant # proportion of contributors will probably not be using SublimeText # *.sublime-project #sftp configuration file sftp-config.json python-pam-2.0.2/CODE_OF_CONDUCT.md000066400000000000000000000062201421475054000164100ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at david@blue-labs.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ python-pam-2.0.2/CONTRIBUTING.md000066400000000000000000000004571421475054000160500ustar00rootroot00000000000000Please adhere to PEP 8 as best appropriate. Code committed must be submitted under the existing project license. This project is switching to a git-flow style, please make pull requests against the `develop` branch. Good reading * https://packaging.python.org/en/latest/tutorials/packaging-projects/ python-pam-2.0.2/ChangeLog000066400000000000000000000013601421475054000153630ustar00rootroot00000000000000Start of ChangeLog 2019-11-12 v1.8.5 use pam_set_item() to set PAM_TTY for pam_securetty module add a bunch of tools for code quality refactored the class slightly so the module can be imported and passive at runtime until authentication is actually needed 2018-6-15 v1.8.4 include LICENSE file as some distributions rely on the presence of it rather than extracting from setup.py 2018-3-22 v1.8.3 add a test for the existence libpam.pam_end function 2014-11-17 v1.8.2 add MANIFEST.in so README.md gets included for pypi (pip installs) 2014-8-4 v1.8.1 adapt, add files, package up for PyPi adapt, add files, package up for github adapt, add files, package up for ArchLinux Start of forked copy from Chris AtLee 2011-Dec python-pam-2.0.2/LICENSE000066400000000000000000000020651421475054000146210ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 David Ford Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-pam-2.0.2/MANIFEST.in000066400000000000000000000000421421475054000153430ustar00rootroot00000000000000include README.md include LICENSE python-pam-2.0.2/Makefile000066400000000000000000000021561421475054000152550ustar00rootroot00000000000000VIRTUALENV = $(shell which virtualenv) PYTHONEXEC = python VERSION = `grep VERSION src/pam/version.py | cut -d \' -f2` build: pydeps python -m build clean: rm -rf *.egg-info/ rm -rf .cache/ rm -rf .tox/ rm -rf .coverage rm -rf build rm -rf dist rm -rf htmlcov rm -rf venv find . -type d -name '__pycache__' | xargs rm -rf find . -name "*.pyc" -type f -print0 | xargs -0 /bin/rm -rf compile: . venv/bin/activate; python setup.py build install console: . venv/bin/activate; python coverage: . venv/bin/activate; coverage html current: @echo $(VERSION) deps: . venv/bin/activate; python -m pip install --upgrade -qr requirements.txt install: clean venv deps . venv/bin/activate; pip install --use-pep517 --progress-bar emoji inspectortiger: pydeps . venv/bin/activate; inspectortiger src/pam/ lint: pydeps . venv/bin/activate; python -m flake8 src/pam/ --max-line-length=120 preflight: bandit test pydeps: . venv/bin/activate; \ pip install --upgrade -q pip && \ pip install --upgrade -q pip build test: tox tox: rm -fr .tox . venv/bin/activate; tox venv: $(VIRTUALENV) -p $(PYTHONEXEC) venv python-pam-2.0.2/PKGBUILD000066400000000000000000000012641421475054000147400ustar00rootroot00000000000000# Maintainer: David Ford pkgname=python-pam pkgver=2.0.0rc1 pkgrel=2 pkgdesc="Linux, FreeBSD, etc (any system that uses PAM) PAM module that provides an authenticate function given a username, password, and other optional keywords." arch=('any') url="https://github.com/FirefighterBlu3/python-pam" license=('MIT') depends=('python' 'pam') makedepends=('python-setuptools') options=(!emptydirs) changelog=(ChangeLog) source=(https://pypi.python.org/packages/source/p/${pkgname}/${pkgname}-${pkgver}.tar.gz) md5sums=(db71b6b999246fb05d78ecfbe166629d) package() { cd "$pkgname-$pkgver" python setup.py install --root="$pkgdir/" --optimize=1 } # vim:set ts=2 sw=2 et: python-pam-2.0.2/README.md000066400000000000000000000045551421475054000151010ustar00rootroot00000000000000# python-pam Python pam module supporting py3 (and py2) for Linux type systems (!windows) Commandline example: ```bash [david@Scott python-pam]$ python pam/pam.py Username: david Password: Auth result: Success (0) Pam Environment List item: XDG_SEAT=seat0 Pam Environment item: XDG_SEAT=seat0 Missing Pam Environment item: asdf=None Open session: Success (0) Close session: Success (0) ``` Inline examples: ```python [david@Scott python-pam]$ python Python 3.9.7 (default, Oct 10 2021, 15:13:22) [GCC 11.1.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import pam >>> p = pam.authenticate() >>> p.authenticate('david', 'correctpassword') True >>> p.authenticate('david', 'badpassword') False >>> p.authenticate('david', 'correctpassword', service='login') True >>> p.authenticate('david', 'correctpassword', service='unknownservice') False >>> p.authenticate('david', 'correctpassword', service='login', resetcreds=True) True >>> p.authenticate('david', 'correctpassword', encoding='latin-1') True >>> print('{} {}'.format(p.code, p.reason)) 0 Success >>> p.authenticate('david', 'badpassword') False >>> print('{} {}'.format(p.code, p.reason)) 7 Authentication failure >>> ``` ## Authentication and privileges Please note, python-pam and *all* tools that do authentication follow two rules: * You have root (or privileged access): you can check any account's password for validity * You don't have root: you can only check the validity of the username running the tool If you need to authenticate multiple users, you must use an authentication stack that at some stage has privileged access. On Linux systems one example of doing this is using SSSD. Typical Linux installations check against `/etc/shadow` with `pam_unix.so` which will spawn `/usr/bin/unix_chkpwd` to verify the password. Both of these are intentionally written to meet the above two rules. You can test the functionality of `unix_chkpwd` in the following manner: Replace `good` with the correct password, replace `david` with your appropriate username. ``` ~$ mkfifo /tmp/myfifo ~$ (echo -ne 'good\0' > /tmp/myfifo & /usr/bin/unix_chkpwd david nullok < /tmp/myfifo ) ; echo $? 0 ~$ (echo -ne 'bad\0' > /tmp/myfifo & /usr/bin/unix_chkpwd david nullok < /tmp/myfifo ) ; echo $? 7 ~$ (echo -ne 'good\0' > /tmp/myfifo & /usr/bin/unix_chkpwd someotheruser nullok < /tmp/myfifo ) ; echo $? 9 ``` python-pam-2.0.2/pyproject.toml000066400000000000000000000023501421475054000165250ustar00rootroot00000000000000[build-system] requires = [ 'setuptools>=44', 'wheel>=0.30.0', 'six', ] build-backend = 'setuptools.build_meta' # ignore the tox documentation, it IS NOT supported yet # https://github.com/tox-dev/tox/issues/2148 #[tox] #isolated_build = true [tool.tox] legacy_tox_ini = """ [tox] envlist = py310 isolated_build = true #skipsdist = true [gh-actions] python = 3.7: py37 3.8: py38 3.9: py39 3.10: py310 [testenv] basepython = python3.10 passenv = * deps = bandit flake8 mypy types-six coverage pytest-cov pytest -rrequirements.txt commands = flake8 src/pam/ mypy bandit -r src -c "pyproject.toml" pytest --cov -r w --capture=sys -vvv --cov-report=html """ [tool.bandit] exclude_dirs = ["./venv", "./test", ] recursive = true [tool.mypy] files = ["src/pam/__init__.py", "src/pam/__internals.py"] ignore_missing_imports = true [tool.pytest] python_files = "test_*.py" norecursedirs = ".tox" [tool.coverage.run] branch = true # awkward how I can include "pam" but I have to be incredibly specific when omitting source = ["pam"] omit = ["*/pam/pam.py", "*/pam/version.py",] [tool.coverage.html] directory = "htmlcov" [tool.coverage.report] skip_empty = true fail_under = 100 python-pam-2.0.2/python-pam.sublime-project000066400000000000000000000001111421475054000207240ustar00rootroot00000000000000{ "folders": [ { "follow_symlinks": true, "path": "." } ] } python-pam-2.0.2/requirements.txt000066400000000000000000000000111421475054000170650ustar00rootroot00000000000000six toml python-pam-2.0.2/setup.cfg000066400000000000000000000021621421475054000154330ustar00rootroot00000000000000[metadata] name = python-pam version = attr: pam.version.VERSION author = David Ford author_email = david@blue-labs.org description = Python PAM module using ctypes, py3 long_description = file: README.md long_description_content_type = text/markdown license = License :: OSI Approved :: MIT License url = https://github.com/FirefighterBlu3/python-pam project_urls = Bug Tracker = https://github.com/FirefighterBlu3/python-pam/issues classifiers = Development Status :: 6 - Mature Environment :: Plugins Intended Audience :: Developers Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: MIT License Operating System :: POSIX Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 3 Topic :: Security Topic :: System :: Systems Administration :: Authentication/Directory [options] packages = find: package_dir = = src [options.packages.find] where = src [sdist] keep_temp = 1 [build_ext] debug = 1 [flake8] max-line-length = 120 python-pam-2.0.2/src/000077500000000000000000000000001421475054000144005ustar00rootroot00000000000000python-pam-2.0.2/src/pam/000077500000000000000000000000001421475054000151555ustar00rootroot00000000000000python-pam-2.0.2/src/pam/__init__.py000066400000000000000000000101371421475054000172700ustar00rootroot00000000000000import sys as __sys if __sys.version_info < (3, ): # pragma: no cover print('WARNING, Python 2 is EOL and therefore py2 support in this ' "package is deprecated. It won't be actively checked for" 'correctness') # list all the constants and export them from .__internals import PAM_ACCT_EXPIRED from .__internals import PAM_AUTHINFO_UNAVAIL from .__internals import PAM_AUTHTOK_DISABLE_AGING from .__internals import PAM_AUTHTOK_ERR from .__internals import PAM_ABORT from .__internals import PAM_AUTHTOK_EXPIRED from .__internals import PAM_AUTHTOK_LOCK_BUSY from .__internals import PAM_AUTHTOK_RECOVER_ERR from .__internals import PAM_AUTH_ERR from .__internals import PAM_BAD_ITEM from .__internals import PAM_BUF_ERR from .__internals import PAM_CHANGE_EXPIRED_AUTHTOK from .__internals import PAM_CONV from .__internals import PAM_CONV_ERR from .__internals import PAM_CRED_ERR from .__internals import PAM_CRED_EXPIRED from .__internals import PAM_CRED_INSUFFICIENT from .__internals import PAM_CRED_UNAVAIL from .__internals import PAM_DATA_SILENT from .__internals import PAM_DELETE_CRED from .__internals import PAM_DISALLOW_NULL_AUTHTOK from .__internals import PAM_ERROR_MSG from .__internals import PAM_ESTABLISH_CRED from .__internals import PAM_IGNORE from .__internals import PAM_MAXTRIES from .__internals import PAM_MODULE_UNKNOWN from .__internals import PAM_NEW_AUTHTOK_REQD from .__internals import PAM_NO_MODULE_DATA from .__internals import PAM_OPEN_ERR from .__internals import PAM_PERM_DENIED from .__internals import PAM_PROMPT_ECHO_OFF from .__internals import PAM_PROMPT_ECHO_ON from .__internals import PAM_REFRESH_CRED from .__internals import PAM_REINITIALIZE_CRED from .__internals import PAM_RHOST from .__internals import PAM_RUSER from .__internals import PAM_SERVICE from .__internals import PAM_SERVICE_ERR from .__internals import PAM_SESSION_ERR from .__internals import PAM_SILENT from .__internals import PAM_SUCCESS from .__internals import PAM_SYMBOL_ERR from .__internals import PAM_SYSTEM_ERR from .__internals import PAM_TEXT_INFO from .__internals import PAM_TRY_AGAIN from .__internals import PAM_TTY from .__internals import PAM_USER from .__internals import PAM_USER_PROMPT from .__internals import PAM_USER_UNKNOWN from .__internals import PAM_XDISPLAY from .__internals import PamAuthenticator __all__ = [ 'authenticate', 'pam', 'PAM_ACCT_EXPIRED', 'PAM_AUTHINFO_UNAVAIL', 'PAM_AUTHTOK_DISABLE_AGING', 'PAM_AUTHTOK_ERR', 'PAM_ABORT', 'PAM_AUTHTOK_EXPIRED', 'PAM_AUTHTOK_LOCK_BUSY', 'PAM_AUTHTOK_RECOVER_ERR', 'PAM_AUTH_ERR', 'PAM_BAD_ITEM', 'PAM_BUF_ERR', 'PAM_CHANGE_EXPIRED_AUTHTOK', 'PAM_CONV', 'PAM_CONV_ERR', 'PAM_CRED_ERR', 'PAM_CRED_EXPIRED', 'PAM_CRED_INSUFFICIENT', 'PAM_CRED_UNAVAIL', 'PAM_DATA_SILENT', 'PAM_DELETE_CRED', 'PAM_DISALLOW_NULL_AUTHTOK', 'PAM_ERROR_MSG', 'PAM_ESTABLISH_CRED', 'PAM_IGNORE', 'PAM_MAXTRIES', 'PAM_MODULE_UNKNOWN', 'PAM_NEW_AUTHTOK_REQD', 'PAM_NO_MODULE_DATA', 'PAM_OPEN_ERR', 'PAM_PERM_DENIED', 'PAM_PROMPT_ECHO_OFF', 'PAM_PROMPT_ECHO_ON', 'PAM_REFRESH_CRED', 'PAM_REINITIALIZE_CRED', 'PAM_RHOST', 'PAM_RUSER', 'PAM_SERVICE', 'PAM_SERVICE_ERR', 'PAM_SESSION_ERR', 'PAM_SILENT', 'PAM_SUCCESS', 'PAM_SYMBOL_ERR', 'PAM_SYSTEM_ERR', 'PAM_TEXT_INFO', 'PAM_TRY_AGAIN', 'PAM_TTY', 'PAM_USER', 'PAM_USER_PROMPT', 'PAM_USER_UNKNOWN', 'PAM_XDISPLAY', ] __PA = None def authenticate(username, password, service='login', env=None, call_end=True, encoding='utf-8', resetcreds=True, print_failure_messages=False): global __PA if __PA is None: # pragma: no branch __PA = PamAuthenticator() return __PA.authenticate(username, password, service, env, call_end, encoding, resetcreds, print_failure_messages) # legacy implementations used pam.pam() pam = PamAuthenticator authenticate.__doc__ = PamAuthenticator.authenticate.__doc__ python-pam-2.0.2/src/pam/__internals.py000066400000000000000000000442101421475054000200250ustar00rootroot00000000000000import os import six import sys from ctypes import cdll from ctypes import CFUNCTYPE from ctypes import CDLL from ctypes import POINTER from ctypes import Structure from ctypes import byref from ctypes import cast from ctypes import sizeof from ctypes import py_object from ctypes import c_char from ctypes import c_char_p from ctypes import c_int from ctypes import c_size_t from ctypes import c_void_p from ctypes import memmove from ctypes.util import find_library from typing import Union PAM_ABORT = 26 PAM_ACCT_EXPIRED = 13 PAM_AUTHINFO_UNAVAIL = 9 PAM_AUTHTOK_DISABLE_AGING = 23 PAM_AUTHTOK_ERR = 20 PAM_AUTHTOK_EXPIRED = 27 PAM_AUTHTOK_LOCK_BUSY = 22 PAM_AUTHTOK_RECOVER_ERR = 21 PAM_AUTH_ERR = 7 PAM_BAD_ITEM = 29 PAM_BUF_ERR = 5 PAM_CHANGE_EXPIRED_AUTHTOK = 32 PAM_CONV = 5 PAM_CONV_ERR = 19 PAM_CRED_ERR = 17 PAM_CRED_EXPIRED = 16 PAM_CRED_INSUFFICIENT = 8 PAM_CRED_UNAVAIL = 15 PAM_DATA_SILENT = 1073741824 PAM_DELETE_CRED = 4 PAM_DISALLOW_NULL_AUTHTOK = 1 PAM_ERROR_MSG = 3 PAM_ESTABLISH_CRED = 2 PAM_IGNORE = 25 PAM_MAXTRIES = 11 PAM_MODULE_UNKNOWN = 28 PAM_NEW_AUTHTOK_REQD = 12 PAM_NO_MODULE_DATA = 18 PAM_OPEN_ERR = 1 PAM_PERM_DENIED = 6 PAM_PROMPT_ECHO_OFF = 1 PAM_PROMPT_ECHO_ON = 2 PAM_REFRESH_CRED = 16 PAM_REINITIALIZE_CRED = 8 PAM_RHOST = 4 PAM_RUSER = 8 PAM_SERVICE = 1 PAM_SERVICE_ERR = 3 PAM_SESSION_ERR = 14 PAM_SILENT = 32768 PAM_SUCCESS = 0 PAM_SYMBOL_ERR = 2 PAM_SYSTEM_ERR = 4 PAM_TEXT_INFO = 4 PAM_TRY_AGAIN = 24 PAM_TTY = 3 PAM_USER = 2 PAM_USER_PROMPT = 9 PAM_USER_UNKNOWN = 10 PAM_XDISPLAY = 11 __all__ = ('PAM_ABORT', 'PAM_ACCT_EXPIRED', 'PAM_AUTHINFO_UNAVAIL', 'PAM_AUTHTOK_DISABLE_AGING', 'PAM_AUTHTOK_ERR', 'PAM_AUTHTOK_EXPIRED', 'PAM_AUTHTOK_LOCK_BUSY', 'PAM_AUTHTOK_RECOVER_ERR', 'PAM_AUTH_ERR', 'PAM_BAD_ITEM', 'PAM_BUF_ERR', 'PAM_CHANGE_EXPIRED_AUTHTOK', 'PAM_CONV', 'PAM_CONV_ERR', 'PAM_CRED_ERR', 'PAM_CRED_EXPIRED', 'PAM_CRED_INSUFFICIENT', 'PAM_CRED_UNAVAIL', 'PAM_DATA_SILENT', 'PAM_DELETE_CRED', 'PAM_DISALLOW_NULL_AUTHTOK', 'PAM_ERROR_MSG', 'PAM_ESTABLISH_CRED', 'PAM_IGNORE', 'PAM_MAXTRIES', 'PAM_MODULE_UNKNOWN', 'PAM_NEW_AUTHTOK_REQD', 'PAM_NO_MODULE_DATA', 'PAM_OPEN_ERR', 'PAM_PERM_DENIED', 'PAM_PROMPT_ECHO_OFF', 'PAM_PROMPT_ECHO_ON', 'PAM_REFRESH_CRED', 'PAM_REINITIALIZE_CRED', 'PAM_RHOST', 'PAM_RUSER', 'PAM_SERVICE', 'PAM_SERVICE_ERR', 'PAM_SESSION_ERR', 'PAM_SILENT', 'PAM_SUCCESS', 'PAM_SYMBOL_ERR', 'PAM_SYSTEM_ERR', 'PAM_TEXT_INFO', 'PAM_TRY_AGAIN', 'PAM_TTY', 'PAM_USER', 'PAM_USER_PROMPT', 'PAM_USER_UNKNOWN', 'PamAuthenticator') class PamHandle(Structure): """wrapper class for pam_handle_t pointer""" _fields_ = [("handle", c_void_p)] def __init__(self): super().__init__() self.handle = 0 def __repr__(self): return f"" class PamMessage(Structure): """wrapper class for pam_message structure""" _fields_ = [("msg_style", c_int), ("msg", c_char_p)] def __repr__(self): return "" % (self.msg_style, self.msg) class PamResponse(Structure): """wrapper class for pam_response structure""" _fields_ = [("resp", c_char_p), ("resp_retcode", c_int)] def __repr__(self): return "" % (self.resp_retcode, self.resp) conv_func = CFUNCTYPE(c_int, c_int, POINTER(POINTER(PamMessage)), POINTER(POINTER(PamResponse)), c_void_p) def my_conv(n_messages, messages, p_response, libc, msg_list: list, password: bytes, encoding: str): """Simple conversation function that responds to any prompt where the echo is off with the supplied password""" # Create an array of n_messages response objects calloc = libc.calloc calloc.restype = c_void_p calloc.argtypes = [c_size_t, c_size_t] cpassword = c_char_p(password) ''' PAM_PROMPT_ECHO_OFF = 1 PAM_PROMPT_ECHO_ON = 2 PAM_ERROR_MSG = 3 PAM_TEXT_INFO = 4 ''' addr = calloc(n_messages, sizeof(PamResponse)) response = cast(addr, POINTER(PamResponse)) p_response[0] = response for i in range(n_messages): message = messages[i].contents.msg if sys.version_info >= (3,): # pragma: no branch message = message.decode(encoding) msg_list.append(message) if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF: if i == 0: dst = calloc(len(password)+1, sizeof(c_char)) memmove(dst, cpassword, len(password)) response[i].resp = dst else: # void out the message response[i].resp = None response[i].resp_retcode = 0 return PAM_SUCCESS class PamConv(Structure): """wrapper class for pam_conv structure""" _fields_ = [("conv", conv_func), ("appdata_ptr", c_void_p)] class PamAuthenticator: code = 0 reason = None # type: Union[str, bytes, None] def __init__(self): # use a trick of dlopen(), this effectively becomes # dlopen("", ...) which opens our own executable. since 'python' has # a libc dependency, this means libc symbols are already available # to us # libc = CDLL(find_library("c")) libc = cdll.LoadLibrary(None) self.libc = libc libpam = CDLL(find_library("pam")) libpam_misc = CDLL(find_library("pam_misc")) self.handle = None self.messages = [] self.calloc = libc.calloc self.calloc.restype = c_void_p self.calloc.argtypes = [c_size_t, c_size_t] # bug #6 (@NIPE-SYSTEMS), some libpam versions don't include this # function if hasattr(libpam, 'pam_end'): # pragma: no branch self.pam_end = libpam.pam_end self.pam_end.restype = c_int self.pam_end.argtypes = [PamHandle, c_int] self.pam_start = libpam.pam_start self.pam_start.restype = c_int self.pam_start.argtypes = [c_char_p, c_char_p, POINTER(PamConv), POINTER(PamHandle)] self.pam_acct_mgmt = libpam.pam_acct_mgmt self.pam_acct_mgmt.restype = c_int self.pam_acct_mgmt.argtypes = [PamHandle, c_int] self.pam_set_item = libpam.pam_set_item self.pam_set_item.restype = c_int self.pam_set_item.argtypes = [PamHandle, c_int, c_void_p] self.pam_setcred = libpam.pam_setcred self.pam_strerror = libpam.pam_strerror self.pam_strerror.restype = c_char_p self.pam_strerror.argtypes = [PamHandle, c_int] self.pam_authenticate = libpam.pam_authenticate self.pam_authenticate.restype = c_int self.pam_authenticate.argtypes = [PamHandle, c_int] self.pam_open_session = libpam.pam_open_session self.pam_open_session.restype = c_int self.pam_open_session.argtypes = [PamHandle, c_int] self.pam_close_session = libpam.pam_close_session self.pam_close_session.restype = c_int self.pam_close_session.argtypes = [PamHandle, c_int] self.pam_putenv = libpam.pam_putenv self.pam_putenv.restype = c_int self.pam_putenv.argtypes = [PamHandle, c_char_p] if libpam_misc._name: # pragma: no branch self.pam_misc_setenv = libpam_misc.pam_misc_setenv self.pam_misc_setenv.restype = c_int self.pam_misc_setenv.argtypes = [PamHandle, c_char_p, c_char_p, c_int] self.pam_getenv = libpam.pam_getenv self.pam_getenv.restype = c_char_p self.pam_getenv.argtypes = [PamHandle, c_char_p] self.pam_getenvlist = libpam.pam_getenvlist self.pam_getenvlist.restype = POINTER(c_char_p) self.pam_getenvlist.argtypes = [PamHandle] def authenticate( self, username, # type: Union[str, bytes] password, # type: Union[str, bytes] service='login', # type: Union[str, bytes] env=None, # type: dict call_end=True, # type: bool encoding='utf-8', # type: str resetcreds=True, # type: bool print_failure_messages=False # type: bool ): # type: (...) -> bool """username and password authentication for the given service. Returns True for success, or False for failure. self.code (integer) and self.reason (string) are always stored and may be referenced for the reason why authentication failed. 0/'Success' will be stored for success. Python3 expects bytes() for ctypes inputs. This function will make necessary conversions using the supplied encoding. Args: username (str): username to authenticate password (str): password in plain text service (str): PAM service to authenticate against, defaults to 'login' env (dict): Pam environment variables call_end (bool): call the pam_end() function after (default true) print_failure_messages (bool): Print messages on failure Returns: success: PAM_SUCCESS failure: False """ @conv_func def __conv(n_messages, messages, p_response, app_data): pyob = cast(app_data, py_object).value msg_list = pyob.get('msgs') password = pyob.get('password') encoding = pyob.get('encoding') return my_conv(n_messages, messages, p_response, self.libc, msg_list, password, encoding) if isinstance(username, six.text_type): username = username.encode(encoding) if isinstance(password, six.text_type): password = password.encode(encoding) if isinstance(service, six.text_type): service = service.encode(encoding) if b'\x00' in username or b'\x00' in password or b'\x00' in service: self.code = PAM_SYSTEM_ERR self.reason = ('none of username, password, or service may contain' ' NUL') raise ValueError(self.reason) # do this up front so we can safely throw an exception if there's # anything wrong with it app_data = {'msgs': self.messages, 'password': password, 'encoding': encoding} conv = PamConv(__conv, c_void_p.from_buffer(py_object(app_data))) self.handle = PamHandle() retval = self.pam_start(service, username, byref(conv), byref(self.handle)) if retval != PAM_SUCCESS: # pragma: no cover # This is not an authentication error, something has gone wrong # starting up PAM self.code = retval self.reason = ("pam_start() failed: %s" % self.pam_strerror(self.handle, retval)) return False # set the TTY, required when pam_securetty is used and the username # root is used note: this is only needed WHEN the pam_securetty.so # module is used; for checking /etc/securetty for allowing root # logins. if your application doesn't use a TTY or your pam setup # doesn't involve pam_securetty for this auth path, don't worry # about it # # if your app isn't authenticating root with the right password, you # may not have the appropriate list of TTYs in /etc/securetty and/or # the correct configuration in /etc/pam.d/* # # if X $DISPLAY is set, use it - otherwise if we have a STDIN tty, # get it ctty = os.environ.get('DISPLAY') if not ctty and os.isatty(0): ctty = os.ttyname(0) # ctty can be invalid if no tty is being used if ctty: # pragma: no branch (we don't test a void tty yet) ctty_p = c_char_p(ctty.encode(encoding)) retval = self.pam_set_item(self.handle, PAM_TTY, ctty_p) retval = self.pam_set_item(self.handle, PAM_XDISPLAY, ctty_p) # Set the environment variables if they were supplied if env: if not isinstance(env, dict): raise TypeError('"env" must be a dict') for key, value in env.items(): if isinstance(key, bytes) and b'\x00' in key: raise ValueError('"env{}" key cannot contain NULLs') if isinstance(value, bytes) and b'\x00' in value: raise ValueError('"env{}" value cannot contain NULLs') name_value = "{}={}".format(key, value) retval = self.putenv(name_value, encoding) auth_success = self.pam_authenticate(self.handle, 0) if auth_success == PAM_SUCCESS: auth_success = self.pam_acct_mgmt(self.handle, 0) if auth_success == PAM_SUCCESS and resetcreds: auth_success = self.pam_setcred(self.handle, PAM_REINITIALIZE_CRED) # store information to inform the caller why we failed self.code = auth_success self.reason = self.pam_strerror(self.handle, auth_success) if sys.version_info >= (3,): # pragma: no branch (we don't test non-py3 versions) self.reason = self.reason.decode(encoding) # type: ignore if call_end and hasattr(self, 'pam_end'): # pragma: no branch self.pam_end(self.handle, auth_success) self.handle = None if print_failure_messages and self.code != PAM_SUCCESS: # pragma: no cover print(f"Failure: {self.reason}") return auth_success == PAM_SUCCESS def end(self): """A direct call to pam_end() Returns: Linux-PAM return value as int """ if not self.handle or not hasattr(self, 'pam_end'): return PAM_SYSTEM_ERR retval = self.pam_end(self.handle, self.code) self.handle = None return retval def open_session(self, encoding='utf-8'): """Call pam_open_session as required by the pam_api Returns: Linux-PAM return value as int """ if not self.handle: return PAM_SYSTEM_ERR retval = self.pam_open_session(self.handle, 0) self.code = retval self.reason = self.pam_strerror(self.handle, retval) if sys.version_info >= (3,): # pragma: no branch self.reason = self.reason.decode(encoding) return retval def close_session(self, encoding='utf-8'): """Call pam_close_session as required by the pam_api Returns: Linux-PAM return value as int """ if not self.handle: return PAM_SYSTEM_ERR retval = self.pam_close_session(self.handle, 0) self.code = retval self.reason = self.pam_strerror(self.handle, retval) if sys.version_info >= (3,): # pragma: no branch self.reason = self.reason.decode(encoding) return retval def misc_setenv(self, name, value, readonly, encoding='utf-8'): """A wrapper for the pam_misc_setenv function Args: name: key name of the environment variable value: the value of the environment variable Returns: Linux-PAM return value as int """ if not self.handle or not hasattr(self, "pam_misc_setenv"): return PAM_SYSTEM_ERR return self.pam_misc_setenv(self.handle, name.encode(encoding), value.encode(encoding), readonly) def putenv(self, name_value, encoding='utf-8'): """A wrapper for the pam_putenv function Args: name_value: environment variable in the format KEY=VALUE Without an '=' delete the corresponding variable Returns: Linux-PAM return value as int """ if not self.handle: return PAM_SYSTEM_ERR name_value = name_value.encode(encoding) retval = self.pam_putenv(self.handle, name_value) if retval != PAM_SUCCESS: raise Exception(self.pam_strerror(self.handle, retval)) return retval def getenv(self, key, encoding='utf-8'): """A wrapper for the pam_getenv function Args: key name of the environment variable Returns: value of the environment variable or None on error """ if not self.handle: return PAM_SYSTEM_ERR # can't happen unless someone is using internals directly if sys.version_info >= (3, ): # pragma: no branch if isinstance(key, six.text_type): # pragma: no branch key = key.encode(encoding) value = self.pam_getenv(self.handle, key) if isinstance(value, type(None)): return if isinstance(value, int): # pragma: no cover raise Exception(self.pam_strerror(self.handle, value)) if sys.version_info >= (3,): # pragma: no branch value = value.decode(encoding) return value def getenvlist(self, encoding='utf-8'): """A wrapper for the pam_getenvlist function Returns: environment as python dictionary """ if not self.handle: return PAM_SYSTEM_ERR env_list = self.pam_getenvlist(self.handle) env_count = 0 pam_env_items = {} while True: try: item = env_list[env_count] except IndexError: # pragma: no cover break if not item: # end of the list break env_item = item if sys.version_info >= (3,): # pragma: no branch env_item = env_item.decode(encoding) try: pam_key, pam_value = env_item.split("=", 1) except ValueError: # pragma: no cover # Incorrectly formatted envlist item pass else: pam_env_items[pam_key] = pam_value env_count += 1 return pam_env_items python-pam-2.0.2/src/pam/pam.py000066400000000000000000000047601421475054000163130ustar00rootroot00000000000000# Now owned and maintained by David Ford, # # (c) 2007 Chris AtLee # Licensed under the MIT license: # http://www.opensource.org/licenses/mit-license.php # # Original author: Chris AtLee # # Modified by David Ford, 2011-12-6 # added py3 support and encoding # added pam_end # added pam_setcred to reset credentials after seeing Leon Walker's remarks # added byref as well # use readline to prestuff the getuser input # # Modified by Laurie Reeves, 2020-02-14 # added opening and closing the pam session # added setting and reading the pam environment variables # added setting the "misc" pam environment # added saving the messages passed back in the conversation function ''' PAM module for python This is a legacy file, it is not used. Here for example. Provides an authenticate function that will allow the caller to authenticate a user against the Pluggable Authentication Modules (PAM) on the system. Implemented using ctypes, so no compilation is necessary. ''' import six import __internals if __name__ == "__main__": # pragma: no cover import readline import getpass def input_with_prefill(prompt, text): def hook(): readline.insert_text(text) readline.redisplay() readline.set_pre_input_hook(hook) result = six.moves.input(prompt) # nosec (bandit; python2) readline.set_pre_input_hook() return result __pam = __internals.PamAuthenticator() username = input_with_prefill('Username: ', getpass.getuser()) # enter a valid username and an invalid/valid password, to verify both # failure and success result = __pam.authenticate(username, getpass.getpass(), env={"XDG_SEAT": "seat0"}, call_end=False) print('Auth result: {} ({})'.format(__pam.reason, __pam.code)) env_list = __pam.getenvlist() for key, value in env_list.items(): print("Pam Environment List item: {}={}".format(key, value)) key = "XDG_SEAT" value = __pam.getenv(key) print("Pam Environment item: {}={}".format(key, value)) if __pam.code == __internals.PAM_SUCCESS: result = __pam.open_session() print('Open session: {} ({})'.format(__pam.reason, __pam.code)) if __pam.code == __internals.PAM_SUCCESS: result = __pam.close_session() print('Close session: {} ({})'.format(__pam.reason, __pam.code)) else: __pam.end() else: __pam.end() python-pam-2.0.2/src/pam/version.py000066400000000000000000000001361421475054000172140ustar00rootroot00000000000000VERSION = '2.0.2' AUTHOR = 'David Ford ' RELEASED = '2022 March 17' python-pam-2.0.2/tests/000077500000000000000000000000001421475054000147535ustar00rootroot00000000000000python-pam-2.0.2/tests/__init__.py000066400000000000000000000000001421475054000170520ustar00rootroot00000000000000python-pam-2.0.2/tests/test_internals.py000066400000000000000000000315311421475054000203660ustar00rootroot00000000000000import os import pytest # from pytest import monkeypatch from ctypes import cdll from ctypes import c_void_p from ctypes import pointer from pam.__internals import PAM_SYSTEM_ERR from pam.__internals import PAM_SUCCESS from pam.__internals import PAM_SESSION_ERR from pam.__internals import PAM_AUTH_ERR from pam.__internals import PAM_USER_UNKNOWN from pam.__internals import PAM_PROMPT_ECHO_OFF from pam.__internals import PAM_PROMPT_ECHO_ON from pam.__internals import PamConv from pam.__internals import PamHandle from pam.__internals import PamMessage from pam.__internals import PamResponse from pam.__internals import PamAuthenticator from pam.__internals import my_conv class MockPam: def __init__(self, og): self.og = og self.og_pam_start = og.pam_start self.PA_authenticate = og.authenticate self.username = None self.password = None def authenticate(self, *args, **kwargs): if len(args) > 0: self.username = args[0] if len(args) > 1: self.password = args[1] self.service = kwargs.get('service') return self.PA_authenticate(*args, **kwargs) def pam_start(self, service, username, conv, handle): rv = self.og_pam_start(service, username, conv, handle) return rv def pam_authenticate(self, handle, flags): if isinstance(self.username, str): self.username = self.username.encode() if isinstance(self.password, str): self.password = self.password.encode() if self.username == b'good_username' and self.password == b'good_password': return PAM_SUCCESS if self.username == b'unknown_username': return PAM_USER_UNKNOWN return PAM_AUTH_ERR def pam_acct_mgmt(self, handle, flags): # we don't test anything here (yet) return PAM_SUCCESS @pytest.fixture def pam_obj(request, monkeypatch): obj = PamAuthenticator() MP = MockPam(obj) monkeypatch.setattr(obj, 'authenticate', MP.authenticate) monkeypatch.setattr(obj, 'pam_start', MP.pam_start) monkeypatch.setattr(obj, 'pam_authenticate', MP.pam_authenticate) monkeypatch.setattr(obj, 'pam_acct_mgmt', MP.pam_acct_mgmt) yield obj def test_PamHandle__void0(): x = PamHandle() assert x.handle == c_void_p(0).value def test_PamHandle__repr(): x = PamHandle() assert '' == repr(x) def test_PamMessage__repr(): x = PamMessage() x.msg_style = 1 x.msg = b'1' str(x) assert "" == repr(x) def test_PamResponse__repr(): x = PamResponse() assert "" == repr(x) def test_PamAuthenticator__setup(): x = PamAuthenticator() assert hasattr(x, 'reason') def test_PamAuthenticator__requires_username_password(pam_obj): with pytest.raises(TypeError): pam_obj.authenticate() def test_PamAuthenticator__requires_username_no_nulls(pam_obj): with pytest.raises(ValueError): pam_obj.authenticate(b'username\x00', b'password') def test_PamAuthenticator__requires_password_no_nulls(pam_obj): with pytest.raises(ValueError): pam_obj.authenticate(b'username', b'password\x00') def test_PamAuthenticator__requires_service_no_nulls(pam_obj): with pytest.raises(ValueError): pam_obj.authenticate(b'username', b'password', b'service\x00') # TEST_* require a valid account def test_PamAuthenticator__normal_success(pam_obj): rv = pam_obj.authenticate('good_username', 'good_password') assert True is rv def test_PamAuthenticator__normal_password_failure(pam_obj): rv = pam_obj.authenticate('good_username', 'bad_password') assert False is rv assert PAM_AUTH_ERR == pam_obj.code def test_PamAuthenticator__normal_unknown_username(pam_obj): rv = pam_obj.authenticate('unknown_username', '') assert False is rv assert PAM_USER_UNKNOWN == pam_obj.code def test_PamAuthenticator__unset_DISPLAY(pam_obj): os.environ['DISPLAY'] = '' rv = pam_obj.authenticate('good_username', 'good_password') assert True is rv assert PAM_SUCCESS == pam_obj.code def test_PamAuthenticator__env_requires_dict(pam_obj): with pytest.raises(TypeError): pam_obj.authenticate('good_username', 'good_password', env='value') def test_PamAuthenticator__env_requires_key_no_nulls(pam_obj): with pytest.raises(ValueError): pam_obj.authenticate('good_username', 'good_password', env={b'\x00invalid_key': b'value'}) def test_PamAuthenticator__env_requires_value_no_nulls(pam_obj): with pytest.raises(ValueError): pam_obj.authenticate('good_username', 'good_password', env={b'key': b'\x00invalid_value'}) def test_PamAuthenticator__env_set(pam_obj): rv = pam_obj.authenticate('good_username', 'good_password', env={'key': b'value'}) assert True is rv assert PAM_SUCCESS == pam_obj.code def test_PamAuthenticator__putenv_incomplete_setup(pam_obj): pam_obj.handle = None pam_obj.putenv('NAME=SomeValue') rv = pam_obj.getenv('NAME') assert PAM_SYSTEM_ERR == rv def test_PamAuthenticator__putenv(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) pam_obj.putenv('NAME=SomeValue') rv = pam_obj.getenv('NAME') assert 'SomeValue' == rv def test_PamAuthenticator__putenv_bad_key(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) with pytest.raises(Exception): pam_obj.putenv('NAME\0=SomeValue') def test_PamAuthenticator__putenv_missing_key_delete(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) with pytest.raises(Exception): pam_obj.putenv('NAME') def test_PamAuthenticator__getenv_missing_key(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) pam_obj.putenv('NAME=Foo') pam_obj.putenv('NAME') rv = pam_obj.getenv('NAME') assert rv is None def test_PamAuthenticator__getenv_missing_value(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) pam_obj.putenv('NAME=') rv = pam_obj.getenv('NAME') assert '' == rv def test_PamAuthenticator__getenv(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) pam_obj.putenv('NAME=foo') rv = pam_obj.getenv('NAME') assert 'foo' == rv def test_PamAuthenticator__getenv_stutter(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) pam_obj.putenv('NAME=NAME=foo') rv = pam_obj.getenv('NAME') assert 'NAME=foo' == rv def test_PamAuthenticator__getenvlist_incomplete_setup(pam_obj): pam_obj.handle = None rv = pam_obj.getenvlist() assert PAM_SYSTEM_ERR == rv def test_PamAuthenticator__getenvlist(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) pam_obj.putenv('A=b') pam_obj.putenv('C=d') rv = pam_obj.getenvlist() assert {'A': 'b', 'C': 'd'} == rv def test_PamAuthenticator__getenvlist_missing_value(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) pam_obj.putenv('A=b') pam_obj.putenv('C=') rv = pam_obj.getenvlist() assert {'A': 'b', 'C': ''} == rv def test_PamAuthenticator__misc_setenv_incomplete_setup(pam_obj): pam_obj.handle = None rv = pam_obj.misc_setenv('NAME', 'SomeValue', False) assert PAM_SYSTEM_ERR == rv def test_PamAuthenticator__misc_setenv(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) rv = pam_obj.misc_setenv('NAME', 'SomeValue', False) assert PAM_SUCCESS == rv def test_PamAuthenticator__pam_end_incomplete_setup(pam_obj): pam_obj.handle = None rv = pam_obj.end() assert PAM_SYSTEM_ERR == rv def test_PamAuthenticator__pam_end(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) rv = pam_obj.end() assert PAM_SUCCESS == rv def test_PamAuthenticator__open_session_incomplete_setup(pam_obj): pam_obj.handle = None rv = pam_obj.open_session() assert PAM_SYSTEM_ERR == rv def test_PamAuthenticator__open_session_unauthenticated(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) rv = pam_obj.open_session() assert PAM_SESSION_ERR == rv def test_PamAuthenticator__close_session_incomplete_setup(pam_obj): pam_obj.handle = None rv = pam_obj.close_session() assert PAM_SYSTEM_ERR == rv def test_PamAuthenticator__close_session_unauthenticated(pam_obj): pam_obj.handle = PamHandle() pam_conv = PamConv() pam_obj.pam_start(b'', b'', pam_conv, pam_obj.handle) rv = pam_obj.close_session() assert PAM_SESSION_ERR == rv def test_PamAuthenticator__conversation_callback_prompt_echo_off(pam_obj): '''Verify that the password is stuffed into the pp_response structure and the response code is set to zero ''' n_messages = 1 messages = PamMessage(PAM_PROMPT_ECHO_OFF, b'Password: ') pp_messages = pointer(pointer(messages)) response = PamResponse(b'overwrite', -1) pp_response = pointer(pointer(response)) encoding = 'utf-8' password = b'blank' msg_list = [] libc = cdll.LoadLibrary(None) rv = my_conv(n_messages, pp_messages, pp_response, libc, msg_list, password, encoding) assert b'blank' == pp_response.contents.contents.resp assert 0 == pp_response.contents.contents.resp_retcode assert PAM_SUCCESS == rv def test_PamAuthenticator__conversation_callback_prompt_echo_on(pam_obj): '''Verify that the stuffed PamResponse "overwrite" is copied into the output and the resp_retcode is set to zero ''' n_messages = 1 messages = PamMessage(PAM_PROMPT_ECHO_ON, b'Password: ') pp_messages = pointer(pointer(messages)) response = PamResponse(b'overwrite', -1) pp_response = pointer(pointer(response)) encoding = 'utf-8' password = b'blank' msg_list = [] libc = cdll.LoadLibrary(None) rv = my_conv(n_messages, pp_messages, pp_response, libc, msg_list, password, encoding) assert None is pp_response.contents.contents.resp assert 0 == pp_response.contents.contents.resp_retcode assert PAM_SUCCESS == rv def test_PamAuthenticator__conversation_callback_multimessage_OFF_ON(pam_obj): '''Verify that the stuffed PamResponse "overwrite" is copied into the output and the resp_retcode is set to zero ''' n_messages = 2 msg1 = PamMessage(PAM_PROMPT_ECHO_OFF, b'overwrite with PAM_PROMPT_ECHO_OFF') msg2 = PamMessage(PAM_PROMPT_ECHO_ON, b'overwrite with PAM_PROMPT_ECHO_ON') ptr1 = pointer(msg1) ptr2 = pointer(msg2) ptrs = pointer(ptr1) ptrs[1] = ptr2 pp_messages = pointer(ptrs[0]) response = PamResponse(b'overwrite', -1) pp_response = pointer(pointer(response)) encoding = 'utf-8' password = b'blank' msg_list = [] libc = cdll.LoadLibrary(None) rv = my_conv(n_messages, pp_messages, pp_response, libc, msg_list, password, encoding) assert b'blank' == pp_response.contents.contents.resp assert 0 == pp_response.contents.contents.resp_retcode assert PAM_SUCCESS == rv def test_PamAuthenticator__conversation_callback_multimessage_ON_OFF(pam_obj): '''Verify that the stuffed PamResponse "overwrite" is copied into the output and the resp_retcode is set to zero ''' n_messages = 2 msg1 = PamMessage(PAM_PROMPT_ECHO_ON, b'overwrite with PAM_PROMPT_ECHO_ON') msg2 = PamMessage(PAM_PROMPT_ECHO_OFF, b'overwrite with PAM_PROMPT_ECHO_OFF') ptr1 = pointer(msg1) ptr2 = pointer(msg2) ptrs = pointer(ptr1) ptrs[1] = ptr2 pp_messages = pointer(ptrs[0]) response = PamResponse(b'overwrite', -1) pp_response = pointer(pointer(response)) encoding = 'utf-8' password = b'blank' msg_list = [] libc = cdll.LoadLibrary(None) rv = my_conv(n_messages, pp_messages, pp_response, libc, msg_list, password, encoding) assert None is pp_response.contents.contents.resp assert 0 == pp_response.contents.contents.resp_retcode assert PAM_SUCCESS == rv python-pam-2.0.2/tests/test_pam.py000066400000000000000000000001271421475054000171410ustar00rootroot00000000000000from pam import authenticate def test_PamAuthenticator(): authenticate('a', 'b')