1
1
/*
2
2
* migauthhandler.c - C <-> Python wrappers for MiG user authentication
3
- * Copyright (C) 2003-2023 The MiG Project lead by Brian Vinter
3
+ * Copyright (C) 2003-2025 The MiG Project lead by the Science HPC Center at UCPH
4
4
*
5
5
* This file is part of MiG
6
6
*
84
84
#define RATE_LIMIT_EXPIRE_DELAY 120
85
85
#endif
86
86
87
+ #ifndef MAX_AUTH_TRIES
88
+ #define MAX_AUTH_TRIES 3
89
+ #endif
90
+
87
91
void * libpython_handle = NULL ;
92
+ unsigned int migauth_tries = 0 ;
93
+ bool migauth_exit = false;
94
+
88
95
PyObject * py_main = NULL ;
89
96
90
97
static void pyrun (const char * cmd , ...)
@@ -110,16 +117,19 @@ static bool mig_pyinit()
110
117
{
111
118
// https://stackoverflow.com/questions/11842920/undefined-symbol-pyexc-importerror-when-embedding-python-in-c/50489814#50489814
112
119
if (libpython_handle != NULL ) {
113
- WRITELOGMESSAGE (LOG_DEBUG , "Python already initialized\n" );
120
+ migauth_tries += 1 ;
121
+ WRITELOGMESSAGE (LOG_DEBUG ,
122
+ "Python already initialized with migauth_tries: %d/%d\n" ,
123
+ migauth_tries , MAX_AUTH_TRIES );
114
124
} else {
125
+ migauth_tries = 1 ;
115
126
// NOTE: use make-detected LIBPYTHON shared library and RTLD_NOW
116
127
// NOTE: The issue with RTLD_LAZY is that C-extensions do not have dependency on the libpython
117
128
// (as can be seen with help of ldd), so once they are loaded and a symbol (e.g. PyFloat_Type)
118
129
// from libpython which is not yet resolved must be looked up,
119
130
// the dynamic linker doesn't know that it has to look into the libpython.
120
131
// https://stackoverflow.com/questions/67891197/ctypes-cpython-39-x86-64-linux-gnu-so-undefined-symbol-pyfloat-type-in-embedd
121
132
libpython_handle = dlopen (LIBPYTHON , RTLD_NOW | RTLD_GLOBAL );
122
-
123
133
#if PY_VERSION_HEX < 0x03000000
124
134
Py_SetProgramName ("pam-mig" );
125
135
#else
@@ -136,6 +146,9 @@ static bool mig_pyinit()
136
146
WRITELOGMESSAGE (LOG_ERR , "Failed to find Python __main__\n" );
137
147
return false;
138
148
}
149
+ WRITELOGMESSAGE (LOG_DEBUG ,
150
+ "Python initialized with migauth_tries: %d/%d\n" ,
151
+ migauth_tries , MAX_AUTH_TRIES );
139
152
pyrun ("from __future__ import absolute_import" );
140
153
141
154
pyrun ("import os" );
@@ -164,14 +177,24 @@ static bool mig_pyinit()
164
177
return true;
165
178
}
166
179
167
- static bool mig_pyexit ()
180
+ static bool mig_pyexit (int exit_value )
168
181
{
169
182
if (libpython_handle == NULL ) {
170
183
WRITELOGMESSAGE (LOG_DEBUG , "Python already finalized\n" );
171
- } else {
184
+ } else if (exit_value == PAM_SUCCESS \
185
+ || migauth_exit == true \
186
+ || migauth_tries >= MAX_AUTH_TRIES ) {
187
+ WRITELOGMESSAGE (LOG_DEBUG ,
188
+ "Python finalize with exit value: %d, migauth_exit: %d, migauth_tries: %d/%d\n" ,
189
+ exit_value , migauth_exit , migauth_tries , MAX_AUTH_TRIES );
172
190
Py_Finalize ();
173
191
dlclose (libpython_handle );
174
192
libpython_handle = NULL ;
193
+ migauth_exit = true;
194
+ } else {
195
+ WRITELOGMESSAGE (LOG_DEBUG ,
196
+ "mig_pyexit called with exit_value: %d migauth_tries: %d/%d\n" ,
197
+ exit_value , migauth_tries , MAX_AUTH_TRIES );
175
198
}
176
199
return true;
177
200
}
@@ -331,20 +354,26 @@ static bool mig_reg_auth_attempt(const unsigned int mode,
331
354
const char * address , const char * secret )
332
355
{
333
356
bool result = false;
357
+ bool disconnect = true;
334
358
WRITELOGMESSAGE (LOG_DEBUG ,
335
359
"mode: 0x%X, username: %s, address: %s, secret: %s\n" ,
336
360
mode , username , address , secret );
337
- char pycmd [MAX_PYCMD_LENGTH ] =
338
- "(authorized, disconnect) = validate_auth_attempt(configuration, 'sftp-subsys', " ;
339
- char pytmp [MAX_PYCMD_LENGTH ];
361
+ /* Filter valid auth types first - we currently only allow password auth */
340
362
if (mode & MIG_AUTHTYPE_PASSWORD ) {
341
- strncat ( & pycmd [ 0 ] , "'password', " , MAX_PYCMD_LENGTH - strlen ( pycmd ) );
363
+ WRITELOGMESSAGE ( LOG_DEBUG , "proceed with password authentication\n" );
342
364
} else {
343
365
WRITELOGMESSAGE (LOG_ERR ,
344
366
"mig_reg_auth_attempt: No valid auth-type in mode: 0x%X\n" ,
345
367
mode );
368
+ /* We don't exit hard here to make sure other auth types may follow */
346
369
return false;
347
370
}
371
+ char pycmd [MAX_PYCMD_LENGTH ] =
372
+ "(authorized, disconnect) = validate_auth_attempt(configuration, 'sftp-subsys', " ;
373
+ char pytmp [MAX_PYCMD_LENGTH ];
374
+ /* Always password auth here as mentioned in the above comment */
375
+ strncat (& pycmd [0 ], "'password', " , MAX_PYCMD_LENGTH - strlen (pycmd ));
376
+
348
377
strncat (& pycmd [0 ], "'" , MAX_PYCMD_LENGTH - strlen (pycmd ));
349
378
strncat (& pycmd [0 ], username , MAX_PYCMD_LENGTH - strlen (pycmd ));
350
379
strncat (& pycmd [0 ], "', '" , MAX_PYCMD_LENGTH - strlen (pycmd ));
@@ -415,17 +444,36 @@ static bool mig_reg_auth_attempt(const unsigned int mode,
415
444
MAX_PYCMD_LENGTH - strlen (pycmd ));
416
445
}
417
446
strncat (& pycmd [0 ], ")" , MAX_PYCMD_LENGTH - strlen (pycmd ));
418
- if (MAX_PYCMD_LENGTH == strlen (pycmd )) {
419
- WRITELOGMESSAGE (LOG_ERR , "mig_reg_auth_attempt: pycmd overflow\n" );
420
- return false;
421
- }
422
- pyrun (& pycmd [0 ]);
423
- PyObject * py_authorized = PyObject_GetAttrString (py_main , "authorized" );
424
- if (py_authorized == NULL ) {
425
- WRITELOGMESSAGE (LOG_ERR , "Missing python variable: py_authorized\n" );
447
+ /* Execute python command if and only if it didn't overflow */
448
+ if (MAX_PYCMD_LENGTH > strlen (pycmd )) {
449
+ pyrun (& pycmd [0 ]);
450
+ PyObject * py_authorized = PyObject_GetAttrString (py_main , "authorized" );
451
+ if (py_authorized == NULL ) {
452
+ WRITELOGMESSAGE (LOG_ERR , "Missing python variable: py_authorized\n" );
453
+ } else {
454
+ result = PyObject_IsTrue (py_authorized );
455
+ Py_DECREF (py_authorized );
456
+ }
457
+ PyObject * py_disconnect = PyObject_GetAttrString (py_main , "disconnect" );
458
+ if (py_disconnect == NULL ) {
459
+ WRITELOGMESSAGE (LOG_ERR , "Missing python variable: py_disconnect\n" );
460
+ } else {
461
+ disconnect = PyObject_IsTrue (py_disconnect );
462
+ Py_DECREF (py_disconnect );
463
+ }
426
464
} else {
427
- result = PyObject_IsTrue (py_authorized );
428
- Py_DECREF (py_authorized );
465
+ WRITELOGMESSAGE (LOG_ERR , "mig_reg_auth_attempt: pycmd overflow!\n" );
466
+ }
467
+
468
+ /* NOTE: '(mode & MIG_VALID_AUTH)'
469
+ If caller (libpam_mig) validated credentials
470
+ then there are no more passwords (re-)tries.
471
+ We honor 'disconnect' here to follow central password policy.
472
+ */
473
+ if (disconnect == true || (mode & MIG_VALID_AUTH )) {
474
+ WRITELOGMESSAGE (LOG_DEBUG , "disconnect: %d, mode & MIG_VALID_AUTH: %d\n" ,
475
+ disconnect , (mode & MIG_VALID_AUTH ));
476
+ migauth_exit = true;
429
477
}
430
478
431
479
return result ;
0 commit comments