Skip to content

Commit 39b3677

Browse files
committed
Add gss_localname and friends
* gss_authorize_localname * gss_localname * gss_pname_to_uid * gss_userok Fixes #49 Signed-off-by: Jacob Henner <code@ventricle.us>
1 parent 5acb5b1 commit 39b3677

7 files changed

Lines changed: 356 additions & 0 deletions

File tree

gssapi/raw/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,10 @@
149149
from gssapi.raw.ext_set_cred_opt import * # noqa
150150
except ImportError:
151151
pass
152+
153+
# optional localname support
154+
try:
155+
from gssapi.raw.ext_localname import * # noqa
156+
from gssapi.raw.ext_localname_attr import * # noqa
157+
except ImportError:
158+
pass

gssapi/raw/ext_localname.pyi

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import typing as t
2+
3+
if t.TYPE_CHECKING:
4+
from gssapi.raw.names import Name
5+
from gssapi.raw.oids import OID
6+
7+
8+
def localname(
9+
name: "Name",
10+
mech: t.Optional["OID"] = None,
11+
) -> bytes:
12+
"""Get the local name for a GSSAPI name.
13+
14+
This method determines the local name associated with a GSSAPI
15+
name, optionally for a given mechanism.
16+
17+
Args:
18+
name (Name): the GSSAPI name to map to a local name
19+
mech (~gssapi.OID): the mechanism to use for the mapping
20+
(or None for the default)
21+
22+
Returns:
23+
bytes: the local name
24+
25+
Raises:
26+
~gssapi.exceptions.GSSError
27+
"""
28+
29+
30+
def userok(
31+
name: "Name",
32+
username: t.Union[bytes, str],
33+
) -> bool:
34+
"""Determine whether a GSSAPI name is authorized to act as a local user.
35+
36+
This method determines whether a given GSSAPI name is authorized
37+
to act as the given local username. This is a simple wrapper
38+
around :func:`authorize_localname` that only supports system
39+
usernames as local names.
40+
41+
Args:
42+
name (Name): the GSSAPI name to check
43+
username (Union[bytes, str]): the local username to check against
44+
45+
Returns:
46+
bool: whether or not the name is authorized to act as the user
47+
"""
48+
49+
50+
def authorize_localname(
51+
name: "Name",
52+
user: "Name",
53+
) -> bool:
54+
"""Determine whether a GSSAPI name is authorized to act as a local name.
55+
56+
This method determines whether a given GSSAPI name is authorized
57+
to act as the given local name.
58+
59+
Args:
60+
name (Name): the mechanism name to check
61+
user (Name): the local name to check against
62+
63+
Returns:
64+
bool: whether or not the name is authorized
65+
66+
Raises:
67+
~gssapi.exceptions.GSSError
68+
"""
69+
70+
71+
def pname_to_uid(
72+
name: "Name",
73+
mech: t.Optional["OID"] = None,
74+
) -> int:
75+
"""Get the local UID for a GSSAPI name.
76+
77+
This method determines the local UID associated with a GSSAPI
78+
name, optionally for a given mechanism.
79+
80+
Note:
81+
This function is not available on Windows.
82+
83+
Args:
84+
name (Name): the GSSAPI name to map to a local UID
85+
mech (~gssapi.OID): the mechanism to use for the mapping
86+
(or None for the default)
87+
88+
Returns:
89+
int: the local UID
90+
91+
Raises:
92+
~gssapi.exceptions.GSSError
93+
"""

gssapi/raw/ext_localname.pyx

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
GSSAPI="BASE" # This ensures that a full module is generated by Cython
2+
3+
from gssapi.raw.cython_types cimport *
4+
from gssapi.raw.names cimport Name
5+
from gssapi.raw.oids cimport OID
6+
7+
from gssapi.raw.misc import GSSError
8+
from gssapi import _utils
9+
10+
from posix.types cimport uid_t
11+
12+
cdef extern from "python_gssapi_ext.h":
13+
OM_uint32 gss_localname(OM_uint32 *minor,
14+
const gss_name_t name,
15+
const gss_OID mech_type,
16+
gss_buffer_t localname) nogil
17+
18+
int gss_userok(const gss_name_t name,
19+
const char *username) nogil
20+
21+
OM_uint32 gss_authorize_localname(OM_uint32 *minor,
22+
const gss_name_t name,
23+
const gss_name_t user) nogil
24+
25+
OM_uint32 gss_pname_to_uid(OM_uint32 *minor,
26+
const gss_name_t name,
27+
const gss_OID mech_type,
28+
uid_t *uid_out) nogil
29+
30+
31+
def localname(Name name not None, OID mech=None):
32+
"""Get the local name for a GSSAPI name.
33+
34+
This method determines the local name associated with a GSSAPI
35+
name, optionally for a given mechanism.
36+
37+
Args:
38+
name (Name): the GSSAPI name to map to a local name
39+
mech (~gssapi.OID): the mechanism to use for the mapping
40+
(or None for the default)
41+
42+
Returns:
43+
bytes: the local name
44+
45+
Raises:
46+
~gssapi.exceptions.GSSError
47+
"""
48+
cdef gss_OID m = GSS_C_NO_OID
49+
if mech is not None:
50+
m = &mech.raw_oid
51+
52+
cdef gss_buffer_desc output = gss_buffer_desc(0, NULL)
53+
54+
cdef OM_uint32 maj_stat, min_stat
55+
56+
with nogil:
57+
maj_stat = gss_localname(&min_stat, name.raw_name, m, &output)
58+
59+
if maj_stat == GSS_S_COMPLETE:
60+
py_output = (<char*>output.value)[:output.length]
61+
gss_release_buffer(&min_stat, &output)
62+
return py_output
63+
else:
64+
raise GSSError(maj_stat, min_stat)
65+
66+
67+
def userok(Name name not None, username not None):
68+
"""Determine whether a GSSAPI name is authorized to act as a local user.
69+
70+
This method determines whether a given GSSAPI name is authorized
71+
to act as the given local username. This is a simple wrapper
72+
around :func:`authorize_localname` that only supports system
73+
usernames as local names.
74+
75+
Args:
76+
name (Name): the GSSAPI name to check
77+
username (Union[bytes, str]): the local username to check against
78+
79+
Returns:
80+
bool: whether or not the name is authorized to act as the user
81+
"""
82+
cdef int res
83+
84+
if isinstance(username, str):
85+
username = username.encode(_utils._get_encoding())
86+
87+
cdef char *c_username = username
88+
89+
with nogil:
90+
res = gss_userok(name.raw_name, c_username)
91+
92+
return res == 1
93+
94+
95+
def authorize_localname(Name name not None, Name user not None):
96+
"""Determine whether a GSSAPI name is authorized to act as a local name.
97+
98+
This method determines whether a given GSSAPI name is authorized
99+
to act as the given local name.
100+
101+
Args:
102+
name (Name): the mechanism name to check
103+
user (Name): the local name to check against
104+
105+
Returns:
106+
bool: whether or not the name is authorized
107+
108+
Raises:
109+
~gssapi.exceptions.GSSError
110+
"""
111+
cdef OM_uint32 maj_stat, min_stat
112+
113+
with nogil:
114+
maj_stat = gss_authorize_localname(&min_stat, name.raw_name,
115+
user.raw_name)
116+
117+
if maj_stat == GSS_S_COMPLETE:
118+
return True
119+
else:
120+
raise GSSError(maj_stat, min_stat)
121+
122+
123+
def pname_to_uid(Name name not None, OID mech=None):
124+
"""Get the local UID for a GSSAPI name.
125+
126+
This method determines the local UID associated with a GSSAPI
127+
name, optionally for a given mechanism.
128+
129+
Note:
130+
This function is not available on Windows.
131+
132+
Args:
133+
name (Name): the GSSAPI name to map to a local UID
134+
mech (~gssapi.OID): the mechanism to use for the mapping
135+
(or None for the default)
136+
137+
Returns:
138+
int: the local UID
139+
140+
Raises:
141+
~gssapi.exceptions.GSSError
142+
"""
143+
cdef gss_OID m = GSS_C_NO_OID
144+
if mech is not None:
145+
m = &mech.raw_oid
146+
147+
cdef uid_t uid_out
148+
cdef OM_uint32 maj_stat, min_stat
149+
150+
with nogil:
151+
maj_stat = gss_pname_to_uid(&min_stat, name.raw_name, m, &uid_out)
152+
153+
if maj_stat == GSS_S_COMPLETE:
154+
return uid_out
155+
else:
156+
raise GSSError(maj_stat, min_stat)

gssapi/raw/ext_localname_attr.pyi

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
ATTR_LOCAL_LOGIN_USER: bytes
2+
"""The attribute name for the local login username.
3+
4+
This can be used with RFC 6680 :func:`~gssapi.raw.ext_rfc6680.get_name_attribute`
5+
and :func:`~gssapi.raw.ext_rfc6680.set_name_attribute` to retrieve or set the
6+
local login username for a GSSAPI name.
7+
"""

gssapi/raw/ext_localname_attr.pyx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
GSSAPI="BASE" # This ensures that a full module is generated by Cython
2+
3+
from gssapi.raw.cython_types cimport gss_buffer_t
4+
5+
cdef extern from "python_gssapi_ext.h":
6+
gss_buffer_t GSS_C_ATTR_LOCAL_LOGIN_USER
7+
8+
9+
# Export the attribute name constant as a Python bytes object.
10+
# This can be used with RFC 6680 get_name_attribute/set_name_attribute
11+
# to retrieve or set the local login username for a GSSAPI name.
12+
ATTR_LOCAL_LOGIN_USER = (<char*>GSS_C_ATTR_LOCAL_LOGIN_USER.value)[
13+
:GSS_C_ATTR_LOCAL_LOGIN_USER.length]

gssapi/tests/test_raw.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,6 +1309,83 @@ def test_krb5_set_allowable_enctypes(self):
13091309
self.assertEqual(acceptor_info.cfx_kd.ctx_key_type,
13101310
acceptor_info.cfx_kd.acceptor_subkey_type)
13111311

1312+
@ktu.gssapi_extension_test('localname', 'Local Name')
1313+
def test_localname(self):
1314+
base_name = gb.import_name(self.USER_PRINC,
1315+
gb.NameType.kerberos_principal)
1316+
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)
1317+
1318+
local = gb.localname(canon_name, gb.MechType.kerberos)
1319+
self.assertIsInstance(local, bytes)
1320+
self.assertGreater(len(local), 0)
1321+
1322+
@ktu.gssapi_extension_test('localname', 'Local Name')
1323+
def test_localname_no_mech(self):
1324+
base_name = gb.import_name(self.USER_PRINC,
1325+
gb.NameType.kerberos_principal)
1326+
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)
1327+
1328+
local = gb.localname(canon_name)
1329+
self.assertIsInstance(local, bytes)
1330+
self.assertGreater(len(local), 0)
1331+
1332+
@ktu.gssapi_extension_test('localname', 'Local Name')
1333+
def test_userok(self):
1334+
base_name = gb.import_name(self.USER_PRINC,
1335+
gb.NameType.kerberos_principal)
1336+
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)
1337+
1338+
local = gb.localname(canon_name, gb.MechType.kerberos)
1339+
# The user should be authorized as their own local name
1340+
self.assertTrue(gb.userok(canon_name, local))
1341+
1342+
# A made-up username should not be authorized
1343+
self.assertFalse(gb.userok(canon_name, b'not_a_real_user_name'))
1344+
1345+
@ktu.gssapi_extension_test('localname', 'Local Name')
1346+
def test_userok_str(self):
1347+
base_name = gb.import_name(self.USER_PRINC,
1348+
gb.NameType.kerberos_principal)
1349+
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)
1350+
1351+
local = gb.localname(canon_name, gb.MechType.kerberos)
1352+
# userok should also accept str input
1353+
self.assertTrue(gb.userok(canon_name, local.decode('UTF-8')))
1354+
1355+
# A made-up str username should not be authorized
1356+
self.assertFalse(gb.userok(canon_name, 'not_a_real_user_name'))
1357+
1358+
@ktu.gssapi_extension_test('localname', 'Local Name')
1359+
def test_authorize_localname(self):
1360+
base_name = gb.import_name(self.USER_PRINC,
1361+
gb.NameType.kerberos_principal)
1362+
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)
1363+
1364+
local = gb.localname(canon_name, gb.MechType.kerberos)
1365+
local_name = gb.import_name(local, gb.NameType.user)
1366+
self.assertTrue(gb.authorize_localname(canon_name, local_name))
1367+
1368+
@ktu.gssapi_extension_test('localname', 'Local Name')
1369+
def test_authorize_localname_fails(self):
1370+
base_name = gb.import_name(self.USER_PRINC,
1371+
gb.NameType.kerberos_principal)
1372+
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)
1373+
1374+
fake_local_name = gb.import_name(b'not_a_real_user_name',
1375+
gb.NameType.user)
1376+
self.assertRaises(gb.GSSError, gb.authorize_localname,
1377+
canon_name, fake_local_name)
1378+
1379+
@ktu.gssapi_extension_test('localname', 'Local Name')
1380+
def test_pname_to_uid(self):
1381+
base_name = gb.import_name(self.USER_PRINC,
1382+
gb.NameType.kerberos_principal)
1383+
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)
1384+
1385+
uid = gb.pname_to_uid(canon_name, gb.MechType.kerberos)
1386+
self.assertIsInstance(uid, int)
1387+
self.assertGreaterEqual(uid, 0)
1388+
13121389

13131390
class TestIntEnumFlagSet(unittest.TestCase):
13141391
def test_create_from_int(self):

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,9 @@ def gssapi_modules(lst):
376376
extension_file('password_add', 'gss_add_cred_with_password'),
377377

378378
extension_file('krb5', 'gss_krb5_ccache_name'),
379+
380+
extension_file('localname', 'gss_localname'),
381+
extension_file('localname_attr', 'GSS_C_ATTR_LOCAL_LOGIN_USER'),
379382
]),
380383
options=setup_options,
381384
keywords=['gssapi', 'security'],

0 commit comments

Comments
 (0)