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