python-pam 2.0.0__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_pam-2.1.0/PKG-INFO +137 -0
- python-pam-2.0.0/src/python_pam.egg-info/PKG-INFO → python_pam-2.1.0/README.md +55 -34
- python_pam-2.1.0/pyproject.toml +92 -0
- {python-pam-2.0.0/src → python_pam-2.1.0/python-pam}/pam/__init__.py +43 -12
- python_pam-2.1.0/python-pam/pam/__internals.py +673 -0
- {python-pam-2.0.0/src → python_pam-2.1.0/python-pam}/pam/pam.py +20 -13
- python_pam-2.1.0/python-pam/pam/version.py +4 -0
- python-pam-2.0.0/MANIFEST.in +0 -2
- python-pam-2.0.0/PKG-INFO +0 -100
- python-pam-2.0.0/README.md +0 -72
- python-pam-2.0.0/pyproject.toml +0 -64
- python-pam-2.0.0/setup.cfg +0 -48
- python-pam-2.0.0/src/pam/__internals.py +0 -548
- python-pam-2.0.0/src/pam/version.py +0 -3
- python-pam-2.0.0/src/python_pam.egg-info/SOURCES.txt +0 -13
- python-pam-2.0.0/src/python_pam.egg-info/dependency_links.txt +0 -1
- python-pam-2.0.0/src/python_pam.egg-info/top_level.txt +0 -1
- {python-pam-2.0.0 → python_pam-2.1.0}/LICENSE +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-pam
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: David Ford
|
|
7
|
+
Author-email: david@blue-labs.org
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# python-pam
|
|
17
|
+
|
|
18
|
+
[](https://scorecard.dev/viewer/?uri=github.com/FirefighterBlu3/python-pam)
|
|
19
|
+
[](https://www.bestpractices.dev/projects/13794)
|
|
20
|
+
|
|
21
|
+
Python pam module supporting py3 for Linux type systems (!windows)
|
|
22
|
+
|
|
23
|
+
## Security
|
|
24
|
+
|
|
25
|
+
See [SECURITY.md](SECURITY.md) for supported versions and how to report vulnerabilities.
|
|
26
|
+
|
|
27
|
+
## Threading and concurrency
|
|
28
|
+
|
|
29
|
+
`pam.authenticate()` is safe to call from many threads at once. Each call uses
|
|
30
|
+
its own PAM handle; libpam ctypes bindings are loaded once and shared (no global
|
|
31
|
+
lock on the auth path).
|
|
32
|
+
|
|
33
|
+
Do **not** share a single `PamAuthenticator` / `pam.pam()` instance across threads
|
|
34
|
+
without external synchronization. That object owns mutable PAM session state
|
|
35
|
+
(`handle`, `code`, `reason`, `messages`). For sessions (`call_end=False`), keep
|
|
36
|
+
one instance per thread (or serialize access).
|
|
37
|
+
|
|
38
|
+
High-QPS login APIs should use:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import pam
|
|
42
|
+
|
|
43
|
+
if pam.authenticate(username, password, service='myapp'):
|
|
44
|
+
...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Credentials (`resetcreds`)
|
|
48
|
+
|
|
49
|
+
After a successful `pam_authenticate` + `pam_acct_mgmt`, `authenticate()` calls
|
|
50
|
+
`pam_setcred(..., PAM_REINITIALIZE_CRED)` when `resetcreds=True` (the default).
|
|
51
|
+
|
|
52
|
+
**Keep the default (`True`)** when this process is acting like a login / credential
|
|
53
|
+
handoff: modules may establish or refresh credentials (e.g. Kerberos), and you care
|
|
54
|
+
about that step succeeding as part of auth.
|
|
55
|
+
|
|
56
|
+
**Set `resetcreds=False`** when you only need to verify a username/password (typical
|
|
57
|
+
web/API “is this password valid?” checks). You are not assuming the user’s identity
|
|
58
|
+
or opening a session; skipping setcred avoids extra module work and avoids treating a
|
|
59
|
+
setcred failure as an authentication failure.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
# Password check only
|
|
63
|
+
pam.authenticate(user, password, service='myapp', resetcreds=False)
|
|
64
|
+
|
|
65
|
+
# Login-style / credential-aware stack (default)
|
|
66
|
+
pam.authenticate(user, password, service='login')
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Examples
|
|
70
|
+
|
|
71
|
+
Commandline example:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
[david@Scott python-pam]$ python pam/pam.py
|
|
75
|
+
Username: david
|
|
76
|
+
Password:
|
|
77
|
+
Auth result: Success (0)
|
|
78
|
+
Pam Environment List item: XDG_SEAT=seat0
|
|
79
|
+
Pam Environment item: XDG_SEAT=seat0
|
|
80
|
+
Missing Pam Environment item: asdf=None
|
|
81
|
+
Open session: Success (0)
|
|
82
|
+
Close session: Success (0)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Inline examples:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
>>> import pam
|
|
89
|
+
>>> pam.authenticate('david', 'correctpassword')
|
|
90
|
+
True
|
|
91
|
+
>>> p = pam.pam()
|
|
92
|
+
>>> p.authenticate('david', 'correctpassword')
|
|
93
|
+
True
|
|
94
|
+
>>> p.authenticate('david', 'badpassword')
|
|
95
|
+
False
|
|
96
|
+
>>> p.authenticate('david', 'correctpassword', service='login')
|
|
97
|
+
True
|
|
98
|
+
>>> p.authenticate('david', 'correctpassword', service='unknownservice')
|
|
99
|
+
False
|
|
100
|
+
>>> p.authenticate('david', 'correctpassword', service='login', resetcreds=True)
|
|
101
|
+
True
|
|
102
|
+
>>> p.authenticate('david', 'correctpassword', encoding='latin-1')
|
|
103
|
+
True
|
|
104
|
+
>>> print('{} {}'.format(p.code, p.reason))
|
|
105
|
+
0 Success
|
|
106
|
+
>>> p.authenticate('david', 'badpassword')
|
|
107
|
+
False
|
|
108
|
+
>>> print('{} {}'.format(p.code, p.reason))
|
|
109
|
+
7 Authentication failure
|
|
110
|
+
>>>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Authentication and privileges
|
|
114
|
+
Please note, python-pam and *all* tools that do authentication follow two rules:
|
|
115
|
+
|
|
116
|
+
* You have root (or privileged access): you can check any account's password for validity
|
|
117
|
+
* You don't have root: you can only check the validity of the username running the tool
|
|
118
|
+
|
|
119
|
+
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.
|
|
120
|
+
|
|
121
|
+
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:
|
|
122
|
+
|
|
123
|
+
Replace `good` with the correct password, replace `david` with your appropriate username.
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
~$ mkfifo /tmp/myfifo
|
|
127
|
+
|
|
128
|
+
~$ (echo -ne 'good\0' > /tmp/myfifo & /usr/bin/unix_chkpwd david nullok < /tmp/myfifo ) ; echo $?
|
|
129
|
+
0
|
|
130
|
+
|
|
131
|
+
~$ (echo -ne 'bad\0' > /tmp/myfifo & /usr/bin/unix_chkpwd david nullok < /tmp/myfifo ) ; echo $?
|
|
132
|
+
7
|
|
133
|
+
|
|
134
|
+
~$ (echo -ne 'good\0' > /tmp/myfifo & /usr/bin/unix_chkpwd someotheruser nullok < /tmp/myfifo ) ; echo $?
|
|
135
|
+
9
|
|
136
|
+
```
|
|
137
|
+
|
|
@@ -1,32 +1,57 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: python-pam
|
|
3
|
-
Version: 2.0.0
|
|
4
|
-
Summary: Python PAM module using ctypes, py3
|
|
5
|
-
Home-page: https://github.com/FirefighterBlu3/python-pam
|
|
6
|
-
Author: David Ford
|
|
7
|
-
Author-email: david@blue-labs.org
|
|
8
|
-
License: License :: OSI Approved :: MIT License
|
|
9
|
-
Project-URL: Bug Tracker, https://github.com/FirefighterBlu3/python-pam/issues
|
|
10
|
-
Platform: UNKNOWN
|
|
11
|
-
Classifier: Development Status :: 6 - Mature
|
|
12
|
-
Classifier: Environment :: Plugins
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: Intended Audience :: Information Technology
|
|
15
|
-
Classifier: Intended Audience :: System Administrators
|
|
16
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
-
Classifier: Operating System :: POSIX
|
|
18
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
-
Classifier: Programming Language :: Python
|
|
20
|
-
Classifier: Programming Language :: Python :: 2
|
|
21
|
-
Classifier: Programming Language :: Python :: 3
|
|
22
|
-
Classifier: Topic :: Security
|
|
23
|
-
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
|
|
24
|
-
Description-Content-Type: text/markdown
|
|
25
|
-
License-File: LICENSE
|
|
26
|
-
|
|
27
1
|
# python-pam
|
|
28
2
|
|
|
29
|
-
|
|
3
|
+
[](https://scorecard.dev/viewer/?uri=github.com/FirefighterBlu3/python-pam)
|
|
4
|
+
[](https://www.bestpractices.dev/projects/13794)
|
|
5
|
+
|
|
6
|
+
Python pam module supporting py3 for Linux type systems (!windows)
|
|
7
|
+
|
|
8
|
+
## Security
|
|
9
|
+
|
|
10
|
+
See [SECURITY.md](SECURITY.md) for supported versions and how to report vulnerabilities.
|
|
11
|
+
|
|
12
|
+
## Threading and concurrency
|
|
13
|
+
|
|
14
|
+
`pam.authenticate()` is safe to call from many threads at once. Each call uses
|
|
15
|
+
its own PAM handle; libpam ctypes bindings are loaded once and shared (no global
|
|
16
|
+
lock on the auth path).
|
|
17
|
+
|
|
18
|
+
Do **not** share a single `PamAuthenticator` / `pam.pam()` instance across threads
|
|
19
|
+
without external synchronization. That object owns mutable PAM session state
|
|
20
|
+
(`handle`, `code`, `reason`, `messages`). For sessions (`call_end=False`), keep
|
|
21
|
+
one instance per thread (or serialize access).
|
|
22
|
+
|
|
23
|
+
High-QPS login APIs should use:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import pam
|
|
27
|
+
|
|
28
|
+
if pam.authenticate(username, password, service='myapp'):
|
|
29
|
+
...
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Credentials (`resetcreds`)
|
|
33
|
+
|
|
34
|
+
After a successful `pam_authenticate` + `pam_acct_mgmt`, `authenticate()` calls
|
|
35
|
+
`pam_setcred(..., PAM_REINITIALIZE_CRED)` when `resetcreds=True` (the default).
|
|
36
|
+
|
|
37
|
+
**Keep the default (`True`)** when this process is acting like a login / credential
|
|
38
|
+
handoff: modules may establish or refresh credentials (e.g. Kerberos), and you care
|
|
39
|
+
about that step succeeding as part of auth.
|
|
40
|
+
|
|
41
|
+
**Set `resetcreds=False`** when you only need to verify a username/password (typical
|
|
42
|
+
web/API “is this password valid?” checks). You are not assuming the user’s identity
|
|
43
|
+
or opening a session; skipping setcred avoids extra module work and avoids treating a
|
|
44
|
+
setcred failure as an authentication failure.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# Password check only
|
|
48
|
+
pam.authenticate(user, password, service='myapp', resetcreds=False)
|
|
49
|
+
|
|
50
|
+
# Login-style / credential-aware stack (default)
|
|
51
|
+
pam.authenticate(user, password, service='login')
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Examples
|
|
30
55
|
|
|
31
56
|
Commandline example:
|
|
32
57
|
|
|
@@ -45,12 +70,10 @@ Close session: Success (0)
|
|
|
45
70
|
Inline examples:
|
|
46
71
|
|
|
47
72
|
```python
|
|
48
|
-
[david@Scott python-pam]$ python
|
|
49
|
-
Python 3.9.7 (default, Oct 10 2021, 15:13:22)
|
|
50
|
-
[GCC 11.1.0] on linux
|
|
51
|
-
Type "help", "copyright", "credits" or "license" for more information.
|
|
52
73
|
>>> import pam
|
|
53
|
-
>>>
|
|
74
|
+
>>> pam.authenticate('david', 'correctpassword')
|
|
75
|
+
True
|
|
76
|
+
>>> p = pam.pam()
|
|
54
77
|
>>> p.authenticate('david', 'correctpassword')
|
|
55
78
|
True
|
|
56
79
|
>>> p.authenticate('david', 'badpassword')
|
|
@@ -96,5 +119,3 @@ Replace `good` with the correct password, replace `david` with your appropriate
|
|
|
96
119
|
~$ (echo -ne 'good\0' > /tmp/myfifo & /usr/bin/unix_chkpwd someotheruser nullok < /tmp/myfifo ) ; echo $?
|
|
97
120
|
9
|
|
98
121
|
```
|
|
99
|
-
|
|
100
|
-
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "python-pam"
|
|
3
|
+
version = "2.1.0"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = [{name = "David Ford", email = "david@blue-labs.org"}]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.11"
|
|
8
|
+
dependencies = []
|
|
9
|
+
|
|
10
|
+
#[project.scripts]
|
|
11
|
+
#lint = "pylint"
|
|
12
|
+
#test = "pytest"
|
|
13
|
+
|
|
14
|
+
[tool.poetry]
|
|
15
|
+
packages = [{include = "pam", from = "python-pam"}]
|
|
16
|
+
|
|
17
|
+
[tool.poetry.group.dev.dependencies]
|
|
18
|
+
pytest = "^9.1.1"
|
|
19
|
+
pytest-cov = "^7.1.0"
|
|
20
|
+
pylint = "^4.0.8"
|
|
21
|
+
tox = "^4.61.4"
|
|
22
|
+
bandit = "*"
|
|
23
|
+
mypy = ">=2.3.1,<3"
|
|
24
|
+
|
|
25
|
+
[tool.poetry.group.test.dependencies]
|
|
26
|
+
pytest = "^9.1.1"
|
|
27
|
+
pytest-cov = "^7.1.0"
|
|
28
|
+
pytest-env = "^1.7.1"
|
|
29
|
+
tox = ">=4.61.4,<5"
|
|
30
|
+
flake8 = "*"
|
|
31
|
+
pylint = ">=4.0.8,<5"
|
|
32
|
+
|
|
33
|
+
[tool.coverage.run]
|
|
34
|
+
source = ["pam"]
|
|
35
|
+
omit = ["*/tests/*", "*/test_*.py"]
|
|
36
|
+
|
|
37
|
+
[tool.coverage.report]
|
|
38
|
+
precision = 2
|
|
39
|
+
show_missing = true
|
|
40
|
+
skip_covered = false
|
|
41
|
+
fail_under = 85.0
|
|
42
|
+
|
|
43
|
+
[tool.bandit]
|
|
44
|
+
exclude_dirs = ["./venv", "./test", ]
|
|
45
|
+
recursive = true
|
|
46
|
+
|
|
47
|
+
[tool.pylint.main]
|
|
48
|
+
jobs = 0
|
|
49
|
+
|
|
50
|
+
[tool.pylint.format]
|
|
51
|
+
max-line-length = 120
|
|
52
|
+
|
|
53
|
+
[tool.pylint."messages control"]
|
|
54
|
+
disable = [
|
|
55
|
+
"too-many-arguments",
|
|
56
|
+
"too-many-positional-arguments",
|
|
57
|
+
"too-many-branches",
|
|
58
|
+
"too-many-statements",
|
|
59
|
+
"too-many-return-statements",
|
|
60
|
+
"too-many-locals",
|
|
61
|
+
"too-many-instance-attributes",
|
|
62
|
+
"too-few-public-methods",
|
|
63
|
+
"wrong-import-position", # Imports are intentionally after version check
|
|
64
|
+
# ctypes symbols are assigned at runtime in PamAuthenticator._ensure_libs
|
|
65
|
+
"not-callable",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[tool.mypy]
|
|
69
|
+
python_version = "3.12"
|
|
70
|
+
warn_return_any = true
|
|
71
|
+
warn_unused_configs = true
|
|
72
|
+
disallow_untyped_defs = false
|
|
73
|
+
disallow_incomplete_defs = false
|
|
74
|
+
check_untyped_defs = true
|
|
75
|
+
no_implicit_optional = true
|
|
76
|
+
warn_redundant_casts = true
|
|
77
|
+
warn_unused_ignores = true
|
|
78
|
+
warn_no_return = true
|
|
79
|
+
strict_equality = true
|
|
80
|
+
ignore_missing_imports = true
|
|
81
|
+
files = ["python-pam/pam"]
|
|
82
|
+
|
|
83
|
+
[[tool.mypy.overrides]]
|
|
84
|
+
module = [
|
|
85
|
+
"ctypes.*",
|
|
86
|
+
"ctypes.util.*",
|
|
87
|
+
]
|
|
88
|
+
ignore_missing_imports = true
|
|
89
|
+
|
|
90
|
+
[build-system]
|
|
91
|
+
requires = ["poetry-core"]
|
|
92
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
"""PAM (Pluggable Authentication Modules) interface for Python.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
This module provides a Python interface to Linux-PAM, allowing authentication
|
|
4
|
+
against system PAM services.
|
|
5
|
+
"""
|
|
6
|
+
import sys as __sys
|
|
7
7
|
|
|
8
8
|
# list all the constants and export them
|
|
9
9
|
from .__internals import PAM_ACCT_EXPIRED
|
|
@@ -58,6 +58,9 @@ from .__internals import PAM_USER_UNKNOWN
|
|
|
58
58
|
from .__internals import PAM_XDISPLAY
|
|
59
59
|
from .__internals import PamAuthenticator
|
|
60
60
|
|
|
61
|
+
if __sys.version_info < (3, ): # pragma: no cover
|
|
62
|
+
print('WARNING, Python 2 is EOL not supported')
|
|
63
|
+
|
|
61
64
|
__all__ = [
|
|
62
65
|
'authenticate',
|
|
63
66
|
'pam',
|
|
@@ -113,17 +116,45 @@ __all__ = [
|
|
|
113
116
|
'PAM_XDISPLAY',
|
|
114
117
|
]
|
|
115
118
|
|
|
116
|
-
__PA = None
|
|
117
119
|
|
|
120
|
+
def authenticate(
|
|
121
|
+
username: str | bytes,
|
|
122
|
+
password: str | bytes,
|
|
123
|
+
service: str | bytes = 'login',
|
|
124
|
+
env: dict[str, str] | None = None,
|
|
125
|
+
call_end: bool = True,
|
|
126
|
+
encoding: str = 'utf-8',
|
|
127
|
+
resetcreds: bool = True,
|
|
128
|
+
print_failure_messages: bool = False,
|
|
129
|
+
) -> bool:
|
|
130
|
+
"""Authenticate a user against PAM.
|
|
131
|
+
|
|
132
|
+
Creates a fresh ``PamAuthenticator`` for each call so concurrent use from
|
|
133
|
+
multiple threads is safe. libpam ctypes bindings are loaded once and shared.
|
|
134
|
+
|
|
135
|
+
For result codes after auth, use ``PamAuthenticator`` directly::
|
|
118
136
|
|
|
119
|
-
|
|
120
|
-
|
|
137
|
+
pa = pam.pam()
|
|
138
|
+
ok = pa.authenticate(user, password)
|
|
139
|
+
print(pa.code, pa.reason)
|
|
121
140
|
|
|
122
|
-
|
|
123
|
-
|
|
141
|
+
Args:
|
|
142
|
+
username: Username to authenticate
|
|
143
|
+
password: Password in plain text
|
|
144
|
+
service: PAM service to authenticate against (default: 'login')
|
|
145
|
+
env: Dictionary of environment variables to set
|
|
146
|
+
call_end: Whether to call pam_end() after authentication (default: True)
|
|
147
|
+
encoding: Encoding to use for string conversions (default: 'utf-8')
|
|
148
|
+
resetcreds: Whether to reset credentials after authentication (default: True)
|
|
149
|
+
print_failure_messages: Whether to print failure messages (default: False)
|
|
124
150
|
|
|
125
|
-
|
|
151
|
+
Returns:
|
|
152
|
+
bool: True if authentication succeeded, False otherwise
|
|
153
|
+
"""
|
|
154
|
+
return PamAuthenticator().authenticate(
|
|
155
|
+
username, password, service, env, call_end, encoding, resetcreds, print_failure_messages,
|
|
156
|
+
)
|
|
126
157
|
|
|
127
158
|
|
|
128
159
|
# legacy implementations used pam.pam()
|
|
129
|
-
pam =
|
|
160
|
+
pam = PamAuthenticator # noqa: N816, C0103
|