1/*
2 * Copyright (c) 1990,1993 Regents of The University of Michigan.
3 * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu)
4 * All Rights Reserved.  See COPYRIGHT.
5 */
6
7#ifdef HAVE_CONFIG_H
8#include "config.h"
9#endif /* HAVE_CONFIG_H */
10
11#if defined (USE_PAM) && defined (UAM_DHX2)
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <atalk/logger.h>
16
17#ifdef HAVE_UNISTD_H
18#include <unistd.h>
19#endif /* HAVE_UNISTD_H */
20#include <errno.h>
21#ifdef HAVE_SECURITY_PAM_APPL_H
22#include <security/pam_appl.h>
23#endif
24#ifdef HAVE_PAM_PAM_APPL_H
25#include <pam/pam_appl.h>
26#endif
27
28
29#ifdef HAVE_LIBGCRYPT
30#include <gcrypt.h>
31#endif /* HAVE_LIBGCRYPT */
32
33#include <atalk/afp.h>
34#include <atalk/uam.h>
35#include <atalk/globals.h>
36
37/* Number of bits for p which we generate. Everybode out there uses 512, so we beet them */
38#define PRIMEBITS 1024
39
40/* hash a number to a 16-bit quantity */
41#define dhxhash(a) ((((unsigned long) (a) >> 8) ^   \
42                     (unsigned long) (a)) & 0xffff)
43
44/* Some parameters need be maintained across calls */
45static gcry_mpi_t p, g, Ra;
46static gcry_mpi_t serverNonce;
47static char *K_MD5hash = NULL;
48static int K_hash_len;
49static u_int16_t ID;
50
51/* The initialization vectors for CAST128 are fixed by Apple. */
52static unsigned char dhx_c2siv[] = { 'L', 'W', 'a', 'l', 'l', 'a', 'c', 'e' };
53static unsigned char dhx_s2civ[] = { 'C', 'J', 'a', 'l', 'b', 'e', 'r', 't' };
54
55/* Static variables used to communicate between the conversation function
56 * and the server_login function */
57static pam_handle_t *pamh = NULL;
58static char *PAM_username;
59static char *PAM_password;
60static struct passwd *dhxpwd;
61
62/*********************************************************
63 * Crypto helper func to generate p and g for use in DH.
64 * libgcrypt doesn't provide one directly.
65 * Algorithm taken from GNUTLS:gnutls_dh_primes.c
66 *********************************************************/
67
68/**
69 * This function will generate a new pair of prime and generator for use in
70 * the Diffie-Hellman key exchange.
71 * The bits value should be one of 768, 1024, 2048, 3072 or 4096.
72 **/
73static int dh_params_generate (unsigned int bits) {
74
75    int result, times = 0, qbits;
76    gcry_mpi_t *factors = NULL;
77    gcry_error_t err;
78
79    /* Version check should be the very first call because it
80       makes sure that important subsystems are intialized. */
81    if (!gcry_check_version (GCRYPT_VERSION)) {
82        LOG(log_error, logtype_uams, "PAM DHX2: libgcrypt versions mismatch. Need: %s", GCRYPT_VERSION);
83        result = AFPERR_MISC;
84        goto error;
85    }
86
87    if (bits < 256)
88        qbits = bits / 2;
89    else
90        qbits = (bits / 40) + 105;
91
92    if (qbits & 1) /* better have an even number */
93        qbits++;
94
95    /* find a prime number of size bits. */
96    do {
97        if (times) {
98            gcry_mpi_release(p);
99            gcry_prime_release_factors (factors);
100        }
101        err = gcry_prime_generate(&p, bits, qbits, &factors, NULL, NULL,
102                                  GCRY_STRONG_RANDOM, GCRY_PRIME_FLAG_SPECIAL_FACTOR);
103        if (err != 0) {
104            result = AFPERR_MISC;
105            goto error;
106        }
107        err = gcry_prime_check(p, 0);
108        times++;
109    } while (err != 0 && times < 10);
110
111    if (err != 0) {
112        result = AFPERR_MISC;
113        goto error;
114    }
115
116    /* generate the group generator. */
117    err = gcry_prime_group_generator(&g, p, factors, NULL);
118    if (err != 0) {
119        result = AFPERR_MISC;
120        goto error;
121    }
122
123    gcry_prime_release_factors(factors);
124
125    return 0;
126
127error:
128    gcry_prime_release_factors(factors);
129
130    return result;
131}
132
133
134/* PAM conversation function
135 * Here we assume (for now, at least) that echo on means login name, and
136 * echo off means password.
137 */
138static int PAM_conv (int num_msg,
139                     const struct pam_message **msg,
140                     struct pam_response **resp,
141                     void *appdata_ptr _U_) {
142    int count = 0;
143    struct pam_response *reply;
144
145#define COPY_STRING(s) (s) ? strdup(s) : NULL
146
147    errno = 0;
148
149    if (num_msg < 1) {
150        /* Log Entry */
151        LOG(log_info, logtype_uams, "PAM DHX2 Conversation Err -- %s",
152            strerror(errno));
153        /* Log Entry */
154        return PAM_CONV_ERR;
155    }
156
157    reply = (struct pam_response *)
158        calloc(num_msg, sizeof(struct pam_response));
159
160    if (!reply) {
161        /* Log Entry */
162        LOG(log_info, logtype_uams, "PAM DHX2: Conversation Err -- %s",
163            strerror(errno));
164        /* Log Entry */
165        return PAM_CONV_ERR;
166    }
167
168    for (count = 0; count < num_msg; count++) {
169        char *string = NULL;
170
171        switch (msg[count]->msg_style) {
172        case PAM_PROMPT_ECHO_ON:
173            if (!(string = COPY_STRING(PAM_username))) {
174                /* Log Entry */
175                LOG(log_info, logtype_uams, "PAM DHX2: username failure -- %s",
176                    strerror(errno));
177                /* Log Entry */
178                goto pam_fail_conv;
179            }
180            break;
181        case PAM_PROMPT_ECHO_OFF:
182            if (!(string = COPY_STRING(PAM_password))) {
183                /* Log Entry */
184                LOG(log_info, logtype_uams, "PAM DHX2: passwd failure: --: %s",
185                    strerror(errno));
186                /* Log Entry */
187                goto pam_fail_conv;
188            }
189            break;
190        case PAM_TEXT_INFO:
191#ifdef PAM_BINARY_PROMPT
192        case PAM_BINARY_PROMPT:
193#endif /* PAM_BINARY_PROMPT */
194            /* ignore it... */
195            break;
196        case PAM_ERROR_MSG:
197        default:
198            LOG(log_info, logtype_uams, "PAM DHX2: Binary_Prompt -- %s", strerror(errno));
199            goto pam_fail_conv;
200        }
201
202        if (string) {
203            reply[count].resp_retcode = 0;
204            reply[count].resp = string;
205            string = NULL;
206        }
207    }
208
209    *resp = reply;
210    LOG(log_info, logtype_uams, "PAM DHX2: PAM Success");
211    return PAM_SUCCESS;
212
213pam_fail_conv:
214    for (count = 0; count < num_msg; count++) {
215        if (!reply[count].resp)
216            continue;
217        switch (msg[count]->msg_style) {
218        case PAM_PROMPT_ECHO_OFF:
219        case PAM_PROMPT_ECHO_ON:
220            free(reply[count].resp);
221            break;
222        }
223    }
224    free(reply);
225    /* Log Entry */
226    LOG(log_info, logtype_uams, "PAM DHX2: Conversation Err -- %s",
227        strerror(errno));
228    /* Log Entry */
229    return PAM_CONV_ERR;
230}
231
232static struct pam_conv PAM_conversation = {
233    &PAM_conv,
234    NULL
235};
236
237
238static int dhx2_setup(void *obj, char *ibuf _U_, size_t ibuflen _U_,
239                      char *rbuf, size_t *rbuflen)
240{
241    int ret;
242    size_t nwritten;
243    gcry_mpi_t Ma;
244    char *Ra_binary = NULL;
245
246    *rbuflen = 0;
247
248    Ra = gcry_mpi_new(0);
249    Ma = gcry_mpi_new(0);
250
251    /* Generate our random number Ra. */
252    Ra_binary = calloc(1, PRIMEBITS/8);
253    if (Ra_binary == NULL) {
254        ret = AFPERR_MISC;
255        goto error;
256    }
257    gcry_randomize(Ra_binary, PRIMEBITS/8, GCRY_STRONG_RANDOM);
258    gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, PRIMEBITS/8, NULL);
259    free(Ra_binary);
260    Ra_binary = NULL;
261
262    /* Ma = g^Ra mod p. This is our "public" key */
263    gcry_mpi_powm(Ma, g, Ra, p);
264
265    /* ------- DH Init done ------ */
266    /* Start building reply packet */
267
268    /* Session ID first */
269    ID = dhxhash(obj);
270    *(u_int16_t *)rbuf = htons(ID);
271    rbuf += 2;
272    *rbuflen += 2;
273
274    /* g is next */
275    gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, 4, &nwritten, g);
276    if (nwritten < 4) {
277        memmove( rbuf+4-nwritten, rbuf, nwritten);
278        memset( rbuf, 0, 4-nwritten);
279    }
280    rbuf += 4;
281    *rbuflen += 4;
282
283    /* len = length of p = PRIMEBITS/8 */
284    *(u_int16_t *)rbuf = htons((u_int16_t) PRIMEBITS/8);
285    rbuf += 2;
286    *rbuflen += 2;
287
288    /* p */
289    gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, NULL, p);
290    rbuf += PRIMEBITS/8;
291    *rbuflen += PRIMEBITS/8;
292
293    /* Ma */
294    gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, &nwritten, Ma);
295    if (nwritten < PRIMEBITS/8) {
296        memmove(rbuf + (PRIMEBITS/8) - nwritten, rbuf, nwritten);
297        memset(rbuf, 0, (PRIMEBITS/8) - nwritten);
298    }
299    rbuf += PRIMEBITS/8;
300    *rbuflen += PRIMEBITS/8;
301
302    ret = AFPERR_AUTHCONT;
303
304error:              /* We exit here anyway */
305    /* We will need Ra later, but mustn't forget to release it ! */
306    gcry_mpi_release(Ma);
307    return ret;
308}
309
310/* -------------------------------- */
311static int login(void *obj, char *username, int ulen,  struct passwd **uam_pwd _U_,
312                 char *ibuf, size_t ibuflen,
313                 char *rbuf, size_t *rbuflen)
314{
315    if (( dhxpwd = uam_getname(obj, username, ulen)) == NULL ) {
316        LOG(log_info, logtype_uams, "DHX2: unknown username");
317        return AFPERR_NOTAUTH;
318    }
319
320    PAM_username = username;
321    LOG(log_info, logtype_uams, "DHX2 login: %s", username);
322    return dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen);
323}
324
325/* -------------------------------- */
326/* dhx login: things are done in a slightly bizarre order to avoid
327 * having to clean things up if there's an error. */
328static int pam_login(void *obj, struct passwd **uam_pwd,
329                     char *ibuf, size_t ibuflen,
330                     char *rbuf, size_t *rbuflen)
331{
332    char *username;
333    size_t len, ulen;
334
335    *rbuflen = 0;
336
337    /* grab some of the options */
338    if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
339        LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username  -- %s",
340            strerror(errno));
341        return AFPERR_PARAM;
342    }
343
344    len = (unsigned char) *ibuf++;
345    if ( len > ulen ) {
346        LOG(log_info, logtype_uams, "DHX2: Signature Retieval Failure -- %s",
347            strerror(errno));
348        return AFPERR_PARAM;
349    }
350
351    memcpy(username, ibuf, len );
352    ibuf += len;
353    username[ len ] = '\0';
354
355    if ((unsigned long) ibuf & 1) /* pad to even boundary */
356        ++ibuf;
357
358    return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
359}
360
361/* ----------------------------- */
362static int pam_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
363                         char *ibuf, size_t ibuflen,
364                         char *rbuf, size_t *rbuflen)
365{
366    char *username;
367    size_t len, ulen;
368    u_int16_t  temp16;
369
370    *rbuflen = 0;
371
372    /* grab some of the options */
373    if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
374        LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username  -- %s",
375            strerror(errno));
376        return AFPERR_PARAM;
377    }
378
379    if (*uname != 3)
380        return AFPERR_PARAM;
381    uname++;
382    memcpy(&temp16, uname, sizeof(temp16));
383    len = ntohs(temp16);
384
385    if ( !len || len > ulen ) {
386        LOG(log_info, logtype_uams, "DHX2: Signature Retrieval Failure -- %s",
387            strerror(errno));
388        return AFPERR_PARAM;
389    }
390    memcpy(username, uname +2, len );
391    username[ len ] = '\0';
392
393    return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
394}
395
396/* -------------------------------- */
397static int logincont1(void *obj _U_, char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen)
398{
399    int ret;
400    size_t nwritten;
401    gcry_mpi_t Mb, K, clientNonce;
402    unsigned char *K_bin = NULL;
403    char serverNonce_bin[16];
404    gcry_cipher_hd_t ctx;
405    gcry_error_t ctxerror;
406
407    *rbuflen = 0;
408
409    Mb = gcry_mpi_new(0);
410    K = gcry_mpi_new(0);
411    clientNonce = gcry_mpi_new(0);
412    serverNonce = gcry_mpi_new(0);
413
414    /* Packet size should be: Session ID + Ma + Encrypted client nonce */
415    if (ibuflen != 2 + PRIMEBITS/8 + 16) {
416        LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
417        ret = AFPERR_PARAM;
418        goto error_noctx;
419    }
420
421    /* Skip session id */
422    ibuf += 2;
423
424    /* Extract Mb, client's "public" key */
425    gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, ibuf, PRIMEBITS/8, NULL);
426    ibuf += PRIMEBITS/8;
427
428    /* Now finally generate the Key: K = Mb^Ra mod p */
429    gcry_mpi_powm(K, Mb, Ra, p);
430
431    /* We need K in binary form in order to ... */
432    K_bin = calloc(1, PRIMEBITS/8);
433    if (K_bin == NULL) {
434        ret = AFPERR_MISC;
435        goto error_noctx;
436    }
437    gcry_mpi_print(GCRYMPI_FMT_USG, K_bin, PRIMEBITS/8, &nwritten, K);
438    if (nwritten < PRIMEBITS/8) {
439        memmove(K_bin + PRIMEBITS/8 - nwritten, K_bin, nwritten);
440        memset(K_bin, 0, PRIMEBITS/8 - nwritten);
441    }
442
443    /* ... generate the MD5 hash of K. K_MD5hash is what we actually use ! */
444    K_MD5hash = calloc(1, K_hash_len = gcry_md_get_algo_dlen(GCRY_MD_MD5));
445    if (K_MD5hash == NULL) {
446        ret = AFPERR_MISC;
447        goto error_noctx;
448    }
449    gcry_md_hash_buffer(GCRY_MD_MD5, K_MD5hash, K_bin, PRIMEBITS/8);
450    free(K_bin);
451    K_bin = NULL;
452
453    /* FIXME: To support the Reconnect UAM, we need to store this key somewhere */
454
455    /* Set up our encryption context. */
456    ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
457    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
458        ret = AFPERR_MISC;
459        goto error_ctx;
460    }
461    /* Set key */
462    ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
463    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
464        ret = AFPERR_MISC;
465        goto error_ctx;
466    }
467    /* Set the initialization vector for client->server transfer. */
468    ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
469    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
470        ret = AFPERR_MISC;
471        goto error_ctx;
472    }
473    /* Finally: decrypt client's md5_K(client nonce, C2SIV) inplace */
474    ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16, NULL, 0);
475    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
476        ret = AFPERR_MISC;
477        goto error_ctx;
478    }
479    /* Pull out clients nonce */
480    gcry_mpi_scan(&clientNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
481    /* Increment nonce */
482    gcry_mpi_add_ui(clientNonce, clientNonce, 1);
483
484    /* Generate our nonce and remember it for Logincont2 */
485    gcry_create_nonce(serverNonce_bin, 16); /* We'll use this here */
486    gcry_mpi_scan(&serverNonce, GCRYMPI_FMT_USG, serverNonce_bin, 16, NULL); /* For use in Logincont2 */
487
488    /* ---- Start building reply packet ---- */
489
490    /* Session ID + 1 first */
491    *(u_int16_t *)rbuf = htons(ID+1);
492    rbuf += 2;
493    *rbuflen += 2;
494
495    /* Client nonce + 1 */
496    gcry_mpi_print(GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, NULL, clientNonce);
497    /* Server nonce */
498    memcpy(rbuf+16, serverNonce_bin, 16);
499
500    /* Set the initialization vector for server->client transfer. */
501    ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ));
502    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
503        ret = AFPERR_MISC;
504        goto error_ctx;
505    }
506    /* Encrypt md5_K(clientNonce+1, serverNonce) inplace */
507    ctxerror = gcry_cipher_encrypt(ctx, rbuf, 32, NULL, 0);
508    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
509        ret = AFPERR_MISC;
510        goto error_ctx;
511    }
512    rbuf += 32;
513    *rbuflen += 32;
514
515    ret = AFPERR_AUTHCONT;
516    goto exit;
517
518error_ctx:
519    gcry_cipher_close(ctx);
520error_noctx:
521    gcry_mpi_release(serverNonce);
522    free(K_MD5hash);
523    K_MD5hash=NULL;
524exit:
525    gcry_mpi_release(K);
526    gcry_mpi_release(Mb);
527    gcry_mpi_release(Ra);
528    gcry_mpi_release(clientNonce);
529    return ret;
530}
531
532/**
533 * Try to authenticate via PAM as "adminauthuser"
534 **/
535static int loginasroot(const char *adminauthuser, const char **hostname, int status)
536{
537    int PAM_error;
538
539    if ((PAM_error = pam_end(pamh, status)) != PAM_SUCCESS)
540        goto exit;
541    pamh = NULL;
542
543    if ((PAM_error = pam_start("netatalk", adminauthuser, &PAM_conversation, &pamh)) != PAM_SUCCESS) {
544        LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s", pam_strerror(pamh,PAM_error));
545        goto exit;
546    }
547
548    /* solaris craps out if PAM_TTY and PAM_RHOST aren't set. */
549    pam_set_item(pamh, PAM_TTY, "afpd");
550    pam_set_item(pamh, PAM_RHOST, *hostname);
551    if ((PAM_error = pam_authenticate(pamh, 0)) != PAM_SUCCESS)
552        goto exit;
553
554    LOG(log_warning, logtype_uams, "DHX2: Authenticated as \"%s\"", adminauthuser);
555
556exit:
557    return PAM_error;
558}
559
560static int logincont2(void *obj_in, struct passwd **uam_pwd,
561                      char *ibuf, size_t ibuflen,
562                      char *rbuf _U_, size_t *rbuflen)
563{
564    AFPObj *obj = obj_in;
565    int ret = AFPERR_MISC;
566    int PAM_error;
567    const char *hostname = NULL;
568    gcry_mpi_t retServerNonce;
569    gcry_cipher_hd_t ctx;
570    gcry_error_t ctxerror;
571    char *utfpass = NULL;
572
573    *rbuflen = 0;
574
575    /* Packet size should be: Session ID + ServerNonce + Passwd buffer (evantually +10 extra bytes, see Apples Docs) */
576    if ((ibuflen != 2 + 16 + 256) && (ibuflen != 2 + 16 + 256 + 10)) {
577        LOG(log_error, logtype_uams, "DHX2: Paket length not correct: %u. Should be 274 or 284.", ibuflen);
578        ret = AFPERR_PARAM;
579        goto error_noctx;
580    }
581
582    retServerNonce = gcry_mpi_new(0);
583
584    /* For PAM */
585    uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL);
586
587    /* Set up our encryption context. */
588    ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
589    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
590        ret = AFPERR_MISC;
591        goto error_ctx;
592    }
593    /* Set key */
594    ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
595    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
596        ret = AFPERR_MISC;
597        goto error_ctx;
598    }
599    /* Set the initialization vector for client->server transfer. */
600    ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
601    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
602        ret = AFPERR_MISC;
603        goto error_ctx;
604    }
605
606    /* Skip Session ID */
607    ibuf += 2;
608
609    /* Finally: decrypt client's md5_K(serverNonce+1, passwor, C2SIV) inplace */
610    ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+256, NULL, 0);
611    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
612        ret = AFPERR_MISC;
613        goto error_ctx;
614    }
615    /* Pull out nonce. Should be serverNonce+1 */
616    gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
617    gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1);
618    if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) {
619        /* We're hacked!  */
620        ret = AFPERR_NOTAUTH;
621        goto error_ctx;
622    }
623    ibuf += 16;
624
625    /* ---- Start authentication with PAM --- */
626
627    /* The password is in legacy Mac encoding, convert it to host encoding */
628    if (convert_string_allocate(CH_MAC, CH_UNIX, ibuf, -1, &utfpass) == (size_t)-1) {
629        LOG(log_error, logtype_uams, "DHX2: conversion error");
630        goto error_ctx;
631    }
632    PAM_password = utfpass;
633
634#ifdef DEBUG
635    LOG(log_maxdebug, logtype_default, "DHX2: password: %s", PAM_password);
636#endif
637
638    /* Set these things up for the conv function */
639
640    ret = AFPERR_NOTAUTH;
641    PAM_error = pam_start("netatalk", PAM_username, &PAM_conversation, &pamh);
642    if (PAM_error != PAM_SUCCESS) {
643        LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s", pam_strerror(pamh,PAM_error));
644        goto error_ctx;
645    }
646
647    /* solaris craps out if PAM_TTY and PAM_RHOST aren't set. */
648    pam_set_item(pamh, PAM_TTY, "afpd");
649    pam_set_item(pamh, PAM_RHOST, hostname);
650    pam_set_item(pamh, PAM_RUSER, PAM_username);
651
652    PAM_error = pam_authenticate(pamh, 0);
653    if (PAM_error != PAM_SUCCESS) {
654        if (PAM_error == PAM_MAXTRIES)
655            ret = AFPERR_PWDEXPR;
656        LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s", pam_strerror(pamh, PAM_error));
657
658        if (!obj->options.adminauthuser)
659            goto error_ctx;
660        if (loginasroot(obj->options.adminauthuser, &hostname, PAM_error) != PAM_SUCCESS) {
661            goto error_ctx;
662        }
663    }
664
665    PAM_error = pam_acct_mgmt(pamh, 0);
666    if (PAM_error != PAM_SUCCESS ) {
667        LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
668            pam_strerror(pamh, PAM_error));
669        if (PAM_error == PAM_NEW_AUTHTOK_REQD)    /* password expired */
670            ret = AFPERR_PWDEXPR;
671#ifdef PAM_AUTHTOKEN_REQD
672        else if (PAM_error == PAM_AUTHTOKEN_REQD)
673            ret = AFPERR_PWDCHNG;
674#endif
675        goto error_ctx;
676    }
677
678#ifndef PAM_CRED_ESTABLISH
679#define PAM_CRED_ESTABLISH PAM_ESTABLISH_CRED
680#endif
681    PAM_error = pam_setcred(pamh, PAM_CRED_ESTABLISH);
682    if (PAM_error != PAM_SUCCESS) {
683        LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
684            pam_strerror(pamh, PAM_error));
685        goto error_ctx;
686    }
687
688    PAM_error = pam_open_session(pamh, 0);
689    if (PAM_error != PAM_SUCCESS) {
690        LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
691            pam_strerror(pamh, PAM_error));
692        goto error_ctx;
693    }
694
695    memset(ibuf, 0, 256); /* zero out the password */
696    if (utfpass)
697        memset(utfpass, 0, strlen(utfpass));
698    *uam_pwd = dhxpwd;
699    LOG(log_info, logtype_uams, "DHX2: PAM Auth OK!");
700
701    ret = AFP_OK;
702
703error_ctx:
704    gcry_cipher_close(ctx);
705error_noctx:
706    if (utfpass) free(utfpass);
707    free(K_MD5hash);
708    K_MD5hash=NULL;
709    gcry_mpi_release(serverNonce);
710    gcry_mpi_release(retServerNonce);
711    return ret;
712}
713
714static int pam_logincont(void *obj, struct passwd **uam_pwd,
715                         char *ibuf, size_t ibuflen,
716                         char *rbuf, size_t *rbuflen)
717{
718    u_int16_t retID;
719    int ret;
720
721    /* check for session id */
722    retID = ntohs(*(u_int16_t *)ibuf);
723    if (retID == ID)
724        ret = logincont1(obj, ibuf, ibuflen, rbuf, rbuflen);
725    else if (retID == ID+1)
726        ret = logincont2(obj, uam_pwd, ibuf,ibuflen, rbuf, rbuflen);
727    else {
728        LOG(log_info, logtype_uams, "DHX2: Session ID Mismatch");
729        ret = AFPERR_PARAM;
730    }
731    return ret;
732}
733
734
735/* logout */
736static void pam_logout(void) {
737    pam_close_session(pamh, 0);
738    pam_end(pamh, 0);
739    pamh = NULL;
740}
741
742/****************************
743 * --- Change pwd stuff --- */
744
745static int changepw_1(void *obj, char *uname,
746                      char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen)
747{
748    *rbuflen = 0;
749
750    /* Remember it now, use it in changepw_3 */
751    PAM_username = uname;
752    return( dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen) );
753}
754
755static int changepw_2(void *obj,
756                      char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen)
757{
758    return( logincont1(obj, ibuf, ibuflen, rbuf, rbuflen) );
759}
760
761static int changepw_3(void *obj _U_,
762                      char *ibuf, size_t ibuflen _U_,
763                      char *rbuf _U_, size_t *rbuflen _U_)
764{
765    int ret;
766    int PAM_error;
767    uid_t uid;
768    pam_handle_t *lpamh;
769    const char *hostname = NULL;
770    gcry_mpi_t retServerNonce;
771    gcry_cipher_hd_t ctx;
772    gcry_error_t ctxerror;
773
774    *rbuflen = 0;
775
776    LOG(log_error, logtype_uams, "DHX2 ChangePW: packet 3 processing");
777
778    /* Packet size should be: Session ID + ServerNonce + 2*Passwd buffer */
779    if (ibuflen != 2 + 16 + 2*256) {
780        LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
781        ret = AFPERR_PARAM;
782        goto error_noctx;
783    }
784
785    retServerNonce = gcry_mpi_new(0);
786
787    /* For PAM */
788    uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL);
789
790    /* Set up our encryption context. */
791    ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
792    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
793        ret = AFPERR_MISC;
794        goto error_ctx;
795    }
796    /* Set key */
797    ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
798    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
799        ret = AFPERR_MISC;
800        goto error_ctx;
801    }
802
803    /* Set the initialization vector for client->server transfer. */
804    ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
805    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
806        ret = AFPERR_MISC;
807        goto error_ctx;
808    }
809
810    /* Skip Session ID */
811    ibuf += 2;
812
813    /* Finally: decrypt client's md5_K(serverNonce+1, 2*password, C2SIV) inplace */
814    ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+2*256, NULL, 0);
815    if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
816        ret = AFPERR_MISC;
817        goto error_ctx;
818    }
819    /* Pull out nonce. Should be serverNonce+1 */
820    gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
821    gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1);
822    if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) {
823        /* We're hacked!  */
824        ret = AFPERR_NOTAUTH;
825        goto error_ctx;
826    }
827    ibuf += 16;
828
829    /* ---- Start pwd changing with PAM --- */
830    ibuf[255] = '\0';       /* For safety */
831    ibuf[511] = '\0';
832
833    /* check if new and old password are equal */
834    if (memcmp(ibuf, ibuf + 256, 255) == 0) {
835        LOG(log_info, logtype_uams, "DHX2 Chgpwd: new and old password are equal");
836        ret = AFPERR_PWDSAME;
837        goto error_ctx;
838    }
839
840    /* Set these things up for the conv function. PAM_username was set in changepw_1 */
841    PAM_password = ibuf + 256;
842    PAM_error = pam_start("netatalk", PAM_username, &PAM_conversation, &lpamh);
843    if (PAM_error != PAM_SUCCESS) {
844        LOG(log_info, logtype_uams, "DHX2 Chgpwd: PAM error in pam_start");
845        ret = AFPERR_PARAM;
846        goto error_ctx;
847    }
848    pam_set_item(lpamh, PAM_TTY, "afpd");
849    uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL);
850    pam_set_item(lpamh, PAM_RHOST, hostname);
851    uid = geteuid();
852    seteuid(0);
853    PAM_error = pam_authenticate(lpamh,0);
854    if (PAM_error != PAM_SUCCESS) {
855        LOG(log_info, logtype_uams, "DHX2 Chgpwd: error authenticating with PAM");
856        seteuid(uid);
857        pam_end(lpamh, PAM_error);
858        ret = AFPERR_NOTAUTH;
859        goto error_ctx;
860    }
861    PAM_password = ibuf;
862    PAM_error = pam_chauthtok(lpamh, 0);
863    seteuid(uid); /* un-root ourselves. */
864    memset(ibuf, 0, 512);
865    if (PAM_error != PAM_SUCCESS) {
866        LOG(log_info, logtype_uams, "DHX2 Chgpwd: error changing pw with PAM");
867        pam_end(lpamh, PAM_error);
868        ret = AFPERR_ACCESS;
869        goto error_ctx;
870    }
871    pam_end(lpamh, 0);
872    ret = AFP_OK;
873
874error_ctx:
875    gcry_cipher_close(ctx);
876error_noctx:
877    free(K_MD5hash);
878    K_MD5hash=NULL;
879    gcry_mpi_release(serverNonce);
880    gcry_mpi_release(retServerNonce);
881    return ret;
882}
883
884static int dhx2_changepw(void *obj _U_, char *uname,
885                         struct passwd *pwd _U_, char *ibuf, size_t ibuflen _U_,
886                         char *rbuf _U_, size_t *rbuflen _U_)
887{
888    /* We use this to serialize the three incoming FPChangePassword calls */
889    static int dhx2_changepw_status = 1;
890
891    int ret = AFPERR_NOTAUTH;  /* gcc can't figure out it's always initialized */
892
893    switch (dhx2_changepw_status) {
894    case 1:
895        ret = changepw_1( obj, uname, ibuf, ibuflen, rbuf, rbuflen);
896        if ( ret == AFPERR_AUTHCONT)
897            dhx2_changepw_status = 2;
898        break;
899    case 2:
900        ret = changepw_2( obj, ibuf, ibuflen, rbuf, rbuflen);
901        if ( ret == AFPERR_AUTHCONT)
902            dhx2_changepw_status = 3;
903        else
904            dhx2_changepw_status = 1;
905        break;
906    case 3:
907        ret = changepw_3( obj, ibuf, ibuflen, rbuf, rbuflen);
908        dhx2_changepw_status = 1; /* Whether is was succesfull or not: we
909                                     restart anyway !*/
910        break;
911    }
912    return ret;
913}
914
915static int uam_setup(const char *path)
916{
917    if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHX2", pam_login,
918                     pam_logincont, pam_logout, pam_login_ext) < 0)
919        return -1;
920    if (uam_register(UAM_SERVER_CHANGEPW, path, "DHX2", dhx2_changepw) < 0)
921        return -1;
922
923    p = gcry_mpi_new(0);
924    g = gcry_mpi_new(0);
925
926    LOG(log_debug, logtype_uams, "DHX2: generating mersenne primes");
927    /* Generate p and g for DH */
928    if (dh_params_generate(PRIMEBITS) != 0) {
929        LOG(log_error, logtype_uams, "DHX2: Couldn't generate p and g");
930        return -1;
931    }
932
933    return 0;
934}
935
936static void uam_cleanup(void)
937{
938    uam_unregister(UAM_SERVER_LOGIN, "DHX2");
939    uam_unregister(UAM_SERVER_CHANGEPW, "DHX2");
940
941    gcry_mpi_release(p);
942    gcry_mpi_release(g);
943}
944
945
946UAM_MODULE_EXPORT struct uam_export uams_dhx2 = {
947    UAM_MODULE_SERVER,
948    UAM_MODULE_VERSION,
949    uam_setup, uam_cleanup
950};
951
952
953UAM_MODULE_EXPORT struct uam_export uams_dhx2_pam = {
954    UAM_MODULE_SERVER,
955    UAM_MODULE_VERSION,
956    uam_setup, uam_cleanup
957};
958
959#endif /* USE_PAM && UAM_DHX2 */
960