File: pam_krb5_migrate.c

package info (click to toggle)
pam-krb5-migrate 0.0.11-5
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, buster, sid
  • size: 192 kB
  • ctags: 20
  • sloc: ansic: 379; makefile: 61; sh: 10
file content (505 lines) | stat: -rw-r--r-- 14,814 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
/*
   Kerberos 5 migration module
   PAM authentication module to transparently add passwords to a Kerberos 5
   database.

   Copyright (C) Steve Langasek 2000-2001
   Copyright (C) Jelmer Vernooij 2006

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * TODO:
 * nullok, nonull options.  One may not want to add a principal
 * to a kerberos db with a null password.
 */

/* indicate the following groups are defined */
#define PAM_SM_AUTH

#include "pam_krb5_migrate.h"

#define DEFAULT_KEYTAB	"/etc/security/pam_krb5.keytab"
#define MIN_UID 100

/* Stub symbol, never used, that's needed when dlopen()ing libkdb5.so. */
void kdb2_dbopen()
{

}

/* Cleanup function for pam data. */
static void _cleanup(pam_handle_t * pamh, void *x, int error_status)
{
    if(x)
        free(x);
    x = NULL;
}


/* syslogging function for errors and other information */
static void _log_err(int err, pam_handle_t *pamh, const char *format, ...)
{
    char *service = NULL;
    char logname[1024];
    va_list args;

    pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
    if (service) {
        snprintf(logname, sizeof(logname) - 1, "%s(pam_krb5_migrate)",
service);
    } else {
        snprintf(logname, sizeof(logname) - 1, "pam_krb5_migrate");
    }

    va_start(args, format);
    openlog(logname, LOG_CONS | LOG_PID, LOG_AUTH);
    vsyslog(err, format, args);
    va_end(args);
    closelog();
}


/*
 * Safe duplication of character strings, leaving
 * no evidence for later stack analysis.
 */
static char * _xstrdup(pam_handle_t *pamh, const char *x)
{
    register char *new = NULL;

    if (x != NULL) {
        register int i;

        for (i = 0; x[i]; ++i); /* length of string */
        if ((new = malloc(++i)) == NULL) {
            i = 0;
            _log_err(LOG_CRIT, pamh, "out of memory in _xstrdup");
        } else {
            while (i-- > 0) {
                new[i] = x[i];
            }
        }
        x = NULL;
    }
    return new;                 /* return the duplicate or NULL on error */
}


/* this is a front-end for module-application conversations */

static int converse(pam_handle_t * pamh, int debug, int nargs,
                     struct pam_message **message,
                     struct pam_response **response)
{
    int retval;
    struct pam_conv *conv;
    retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
    if (retval == PAM_SUCCESS) {
        retval = conv->conv(nargs, (const struct pam_message **) message
                            , response, conv->appdata_ptr);
        if (retval != PAM_SUCCESS && debug) {
            _log_err(LOG_DEBUG, pamh, "conversation failure [%s]",
                     pam_strerror(pamh, retval));
        }
    } else {
        _log_err(LOG_ERR, pamh,
                 "couldn't obtain coversation function [%s]",
                 pam_strerror(pamh, retval));
    }
    return retval;				/* propagate error status */
}


static int make_remark(pam_handle_t * pamh, int debug,
                        int type, const char *text)
{
    struct pam_message *pmsg[1], msg[1];
    struct pam_response *resp;
    pmsg[0] = &msg[0];
    msg[0].msg = text;
    msg[0].msg_style = type;
    resp = NULL;
    return converse(pamh, debug, 1, pmsg, &resp);
    return PAM_SUCCESS;
}


/*
 * pam_sm_authenticate() takes an authentication token and stores
 * it to a Kerberos database using the kadmin API.
 *
 */
int pam_sm_authenticate(pam_handle_t *pamh, int flags,
                        int argc, const char **argv)
{
    int retval, *ret_data = NULL;

    int debug = 0, quiet = flags & PAM_SILENT;
#ifndef KADMIN_LOCAL
    int local = 1, remote = 1;
#endif
    char *def_realm = NULL;
    char *cp;
    char *name = NULL, *pass = NULL;
    const char *lname = NULL;
    char *princstr = NULL, *keytab_name = NULL;
    kadm5_ret_t kret;
    krb5_context context;
    krb5_principal princ;
    kadm5_principal_ent_rec newprinc;
    kadm5_config_params params;
    kadm5_policy_ent_rec defpol;
    long mask = 0;
    void *handle = NULL;
    uid_t min_uid = MIN_UID;
    struct passwd *pwent = NULL;


    /* Get a few bytes so we can pass our return value to pam_sm_setcred(). */
    ret_data = malloc(sizeof(int));

    /* Initialize the params struct for kadmin. */
    memset((char *) &params, 0, sizeof(params));

    if ((kret = krb5_init_context(&context))) {
        _log_err(LOG_ERR, pamh, "%s while initializing krb5 library",
                 error_message(kret));
	retval = PAM_SYSTEM_ERR;
        goto cleanup;
    }

    while (argc--) {
        if (!strncmp(*argv, "debug", 5)) {
            debug = 1;
#ifndef KADMIN_LOCAL
        } else if (!strncmp(*argv, "keytab=", 7)) {
            keytab_name = _xstrdup(pamh, *argv+7);
            if (keytab_name == NULL) {
                retval = PAM_BUF_ERR;
                goto cleanup;
            }
#else
        } else if (!strncmp(*argv, "keytab=", 7)) {
            _log_err(LOG_NOTICE, pamh,
                     "module compiled with local database support,"
                     "ignoring option %s",
                     *argv);
#endif
        } else if (!strncmp(*argv, "principal=", 10)) {
            princstr = _xstrdup(pamh, *argv+10);
            if (princstr == NULL) {
                retval = PAM_BUF_ERR;
                goto cleanup;
            }
        } else if (!strncmp(*argv, "realm=", 6)) {
            def_realm = _xstrdup(pamh, *argv+6);
            if (def_realm == NULL) {
                retval = PAM_BUF_ERR;
                goto cleanup;
            }
        } else if (!strncmp(*argv, "min_uid=", 8)) {
            min_uid = atoi(*argv+8);
        } else {
            _log_err(LOG_ERR, pamh, "unrecognized option [%s]", *argv);
            retval = PAM_SYSTEM_ERR;
            goto cleanup;
        }
        ++argv;
    }

    /* Even if connected locally, we need a realm so we can properly build
       principal names. */
    if (def_realm == NULL && krb5_get_default_realm(context, &def_realm))
    {
        _log_err(LOG_ERR, pamh, "unable to get default realm");
        if(!quiet) {
            make_remark(pamh, debug, PAM_ERROR_MSG,
                        "unable to get default Kerberos realm");
        }
        retval = PAM_SYSTEM_ERR;
        goto cleanup;
    }

    params.mask |= KADM5_CONFIG_REALM;
    params.realm = def_realm;


    /*
     * If no principal name is specified, the principal name is
     * pam_migrate/hostname.
     */

    if (princstr == NULL) {
        /* We want a principal using the service name (pam_migrate) and
           the hostname. */
        if ((kret = krb5_sname_to_principal(context, NULL,
                                           "pam_migrate", KRB5_NT_SRV_HST,
                                           &princ)))
        {
            _log_err(LOG_ERR, pamh, "%s creating host service principal",
                     error_message(kret));
             retval = PAM_SYSTEM_ERR;
             goto cleanup;
        }

        /* Can we extract a string from the result? */
        if ((kret = krb5_unparse_name(context, princ, &princstr))) {
            _log_err(LOG_ERR, pamh, "%s while canonicalizing principal name",
                     error_message(kret));
             krb5_free_principal(context, princ);
             retval = PAM_SYSTEM_ERR;
             goto cleanup;
        }

        /* Done with it either way */
        krb5_free_principal(context, princ);
    }

    /*
     * Initialize the kadm5 connection.  Either we're running in local
     * mode, in which case anything goes; or we need a keytab.
     */
#ifndef KADMIN_LOCAL
    /* Get default keytab if none was provided. */
    if (!keytab_name) {
        keytab_name = _xstrdup(pamh, DEFAULT_KEYTAB);
        if (keytab_name == NULL) {
            retval = PAM_BUF_ERR;
            goto cleanup;
        }
    }

    if (debug) {
        _log_err(LOG_DEBUG, pamh,
                 "Authenticating as principal %s with keytab %s.\n",
                 princstr, keytab_name);
    }
#endif

#ifdef KADM5_INIT_WITH_SKEY_7_ARGS
    kret = kadm5_init_with_skey(princstr, keytab_name,
                                    KADM5_ADMIN_SERVICE,
                                    &params,
                                    KADM5_STRUCT_VERSION,
                                    KADM5_API_VERSION_2,
                                    &handle);
#else
    kret = kadm5_init_with_skey(context,
                                princstr, keytab_name,
                                KADM5_ADMIN_SERVICE,
                                &params,
                                KADM5_STRUCT_VERSION,
                                KADM5_API_VERSION_2,
                                NULL, &handle);
#endif

    free(princstr);
    princstr = NULL;

    if (kret) {
        _log_err(LOG_ERR, pamh, 
                 "%s while initializing kadmin interface",
                 error_message(kret));
        retval = PAM_SYSTEM_ERR;
        goto cleanup;
    }


    /* Everything is in order.  Get our username and our realm,
       and add the principal. */

    /* get the username */
    retval = pam_get_user(pamh, &lname, "Username: ");
    if (retval != PAM_SUCCESS) {
        if (debug) {
            _log_err(LOG_DEBUG, pamh, "could not identify user");
        }
        goto cleanup;
    }
    if (debug) {
        _log_err(LOG_DEBUG, pamh, "username [%s] obtained", lname);
    }

    pwent = getpwnam(lname);
    if (pwent != NULL && pwent->pw_uid < min_uid) {
       if (debug) {
           _log_err(LOG_DEBUG, pamh, "username [%s] has uid less than %d, not creating a principal", lname, min_uid);
       }
       retval = PAM_IGNORE;
       goto cleanup;
    }

    name = malloc(strlen(lname) + strlen(def_realm) + 2);
    if (name == NULL) {
        _log_err(LOG_CRIT, pamh, "no memory for principal name");
        retval = PAM_BUF_ERR;
        goto cleanup;
    }

    strncpy(name, lname, strlen(lname) + 1);

    /* Make sure we're dealing with a valid username. */
    if ((cp = strchr(name, '@'))) {
        *cp = '\0';
    }
    if ((cp = strchr(name, '/'))) {
        *cp = '\0';
    }

    /* Tack on the @REALM portion of the principal name. */
    strncat(name, "@", 2);
    strncat(name, def_realm, strlen(def_realm) + 1);

    /* Get the authtok; if we don't have one, silently fail. */
    retval = pam_get_item(pamh, PAM_AUTHTOK,
                          (const void **)&pass);

    if (retval != PAM_SUCCESS)
    {
	_log_err(LOG_ALERT, pamh,
	         "pam_get_item returned error to pam_sm_authenticate");
	retval = PAM_AUTHTOK_RECOVER_ERR;
        goto cleanup;
    } else if (pass == NULL) {
	retval = PAM_AUTHTOK_RECOVER_ERR;
        goto cleanup;
    }

    /* Zero all fields in request structure */
    memset(&newprinc, 0, sizeof(newprinc));
    newprinc.attributes = 0;

    kret = krb5_parse_name(context, name, &newprinc.principal);
    if (kret) {
        _log_err(LOG_ERR, pamh, "%s while setting up principal \"%s\"",
                 error_message(kret), name);
        krb5_free_principal(context, newprinc.principal);
        retval = PAM_SYSTEM_ERR;
        goto cleanup;
    }

#ifdef HAVE_KADM5_GET_POLICY
    if (!kadm5_get_policy(handle, "default", &defpol)) {
        if (debug) {
            _log_err(LOG_DEBUG, pamh,
                     "no policy specified for %s; assigning \"default\"",
                     name);
        }
        newprinc.policy = "default";
        mask |= KADM5_POLICY;
#if KADM5_FREE_POLICY_ENT_1_ARG
        (void) kadm5_free_policy_ent(&defpol);
#else
        (void) kadm5_free_policy_ent(handle, &defpol);
#endif
    } else {
        if (debug) {
            _log_err(LOG_DEBUG, pamh,
                     "no policy specified for %s; defaulting to no policy",
                     name);
        }
    }
#endif
    mask &= ~KADM5_POLICY_CLR;

    mask |= KADM5_PRINCIPAL;
    kret = kadm5_create_principal(handle, &newprinc, mask, pass);

    /* TODO: some errors are more noteworthy than others. */
    if (kret && kret != KADM5_DUP) { // No need to log that the
                                     // principal is already there.
        if (!quiet)
	    make_remark(pamh, debug, PAM_ERROR_MSG, error_message(kret));
        _log_err(LOG_NOTICE, pamh, "%s creating principal \"%s\"",
                 error_message(kret), name);
        krb5_free_principal(context, newprinc.principal);
        retval = PAM_IGNORE;
        goto cleanup;
    } else if (kret && debug) {
        _log_err(LOG_DEBUG, pamh, "principal %s already exists, continuing",
                 name);
    }

    krb5_free_principal(context, newprinc.principal);
    if (debug && !kret) {
        _log_err(LOG_NOTICE, pamh, "Principal \"%s\" created", name);
    }

    /* return PAM_IGNORE, so that we don't
       affect the authentication stack. */
    retval = PAM_IGNORE;

cleanup:

    kadm5_flush(handle);
    kadm5_destroy(handle);
    krb5_free_context(context);
    if (princstr)
        free(princstr);
    if (def_realm)
        free(def_realm);
    if (keytab_name)
        free(keytab_name);
    if (name)
        free(name);
    if (ret_data) {
        *ret_data = retval;
        pam_set_data(pamh, "krb5_migrate_return",
                     (void *) ret_data, _cleanup);
    }
    return retval;
}

/*
 * pam_sm_setcred: stub function.  We have no credentials to set,
 * so we just return a value to match pam_sm_authenticate.
 */

int pam_sm_setcred(pam_handle_t *pamh, int flags,
                   int argc, const char **argv)
{
    int retval, *pretval = NULL;

    retval = PAM_SUCCESS;

    /* Retrieve the previous return value. */
    pam_get_data(pamh, "krb5_migrate_return", (const void **) &pretval);

    /* Copy the return value to local memory. */
    if(pretval) {
        retval = *pretval;
    }

    /* Trigger the cleanup function. */
    pam_set_data(pamh, "krb5_migrate_return", NULL, NULL);

    return retval;
}


/* static module data */
#ifdef PAM_STATIC
struct pam_module _pam_krb5_migrate_auth_modstruct = {
     "pam_krb5_migrate",
     pam_sm_authenticate,
     pam_sm_setcred,
     NULL,
     NULL,
     NULL,
     NULL
};
#endif