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