]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_dhx2_pam.c
Merge remote-tracking branch 'origin/branch-netatalk-3-0' into develop
[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 <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 */
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 uint16_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 *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
127 error:
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  */
138 static int PAM_conv (int num_msg,
139 #ifdef LINUX
140                      const struct pam_message **msg,
141 #else
142                      struct pam_message **msg,
143 #endif
144                      struct pam_response **resp,
145                      void *appdata_ptr _U_) {
146     int count = 0;
147     struct pam_response *reply;
148
149 #define COPY_STRING(s) (s) ? strdup(s) : NULL
150
151     errno = 0;
152
153     if (num_msg < 1) {
154         /* Log Entry */
155         LOG(log_info, logtype_uams, "PAM DHX2 Conversation Err -- %s",
156             strerror(errno));
157         /* Log Entry */
158         return PAM_CONV_ERR;
159     }
160
161     reply = (struct pam_response *)
162         calloc(num_msg, sizeof(struct pam_response));
163
164     if (!reply) {
165         /* Log Entry */
166         LOG(log_info, logtype_uams, "PAM DHX2: Conversation Err -- %s",
167             strerror(errno));
168         /* Log Entry */
169         return PAM_CONV_ERR;
170     }
171
172     for (count = 0; count < num_msg; count++) {
173         char *string = NULL;
174
175         switch (msg[count]->msg_style) {
176         case PAM_PROMPT_ECHO_ON:
177             if (!(string = COPY_STRING(PAM_username))) {
178                 /* Log Entry */
179                 LOG(log_info, logtype_uams, "PAM DHX2: username failure -- %s",
180                     strerror(errno));
181                 /* Log Entry */
182                 goto pam_fail_conv;
183             }
184             break;
185         case PAM_PROMPT_ECHO_OFF:
186             if (!(string = COPY_STRING(PAM_password))) {
187                 /* Log Entry */
188                 LOG(log_info, logtype_uams, "PAM DHX2: passwd failure: --: %s",
189                     strerror(errno));
190                 /* Log Entry */
191                 goto pam_fail_conv;
192             }
193             break;
194         case PAM_TEXT_INFO:
195 #ifdef PAM_BINARY_PROMPT
196         case PAM_BINARY_PROMPT:
197 #endif /* PAM_BINARY_PROMPT */
198             /* ignore it... */
199             break;
200         case PAM_ERROR_MSG:
201         default:
202             LOG(log_info, logtype_uams, "PAM DHX2: Binary_Prompt -- %s", strerror(errno));
203             goto pam_fail_conv;
204         }
205
206         if (string) {
207             reply[count].resp_retcode = 0;
208             reply[count].resp = string;
209             string = NULL;
210         }
211     }
212
213     *resp = reply;
214     LOG(log_info, logtype_uams, "PAM DHX2: PAM Success");
215     return PAM_SUCCESS;
216
217 pam_fail_conv:
218     for (count = 0; count < num_msg; count++) {
219         if (!reply[count].resp)
220             continue;
221         switch (msg[count]->msg_style) {
222         case PAM_PROMPT_ECHO_OFF:
223         case PAM_PROMPT_ECHO_ON:
224             free(reply[count].resp);
225             break;
226         }
227     }
228     free(reply);
229     /* Log Entry */
230     LOG(log_info, logtype_uams, "PAM DHX2: Conversation Err -- %s",
231         strerror(errno));
232     /* Log Entry */
233     return PAM_CONV_ERR;
234 }
235
236 static struct pam_conv PAM_conversation = {
237     &PAM_conv,
238     NULL
239 };
240
241
242 static int dhx2_setup(void *obj, char *ibuf _U_, size_t ibuflen _U_,
243                       char *rbuf, size_t *rbuflen)
244 {
245     int ret;
246     size_t nwritten;
247     gcry_mpi_t Ma;
248     char *Ra_binary = NULL;
249     uint16_t uint16;
250
251     *rbuflen = 0;
252
253     Ra = gcry_mpi_new(0);
254     Ma = gcry_mpi_new(0);
255
256     /* Generate our random number Ra. */
257     Ra_binary = calloc(1, PRIMEBITS/8);
258     if (Ra_binary == NULL) {
259         ret = AFPERR_MISC;
260         goto error;
261     }
262     gcry_randomize(Ra_binary, PRIMEBITS/8, GCRY_STRONG_RANDOM);
263     gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, PRIMEBITS/8, NULL);
264     free(Ra_binary);
265     Ra_binary = NULL;
266
267     /* Ma = g^Ra mod p. This is our "public" key */
268     gcry_mpi_powm(Ma, g, Ra, p);
269
270     /* ------- DH Init done ------ */
271     /* Start building reply packet */
272
273     /* Session ID first */
274     ID = dhxhash(obj);
275     uint16 = htons(ID);
276     memcpy(rbuf, &uint16, sizeof(uint16_t));
277     rbuf += 2;
278     *rbuflen += 2;
279
280     /* g is next */
281     gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, 4, &nwritten, g);
282     if (nwritten < 4) {
283         memmove( rbuf+4-nwritten, rbuf, nwritten);
284         memset( rbuf, 0, 4-nwritten);
285     }
286     rbuf += 4;
287     *rbuflen += 4;
288
289     /* len = length of p = PRIMEBITS/8 */
290
291     uint16 = htons((uint16_t) PRIMEBITS/8);
292     memcpy(rbuf, &uint16, sizeof(uint16_t));
293     rbuf += 2;
294     *rbuflen += 2;
295
296     /* p */
297     gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, NULL, p);
298     rbuf += PRIMEBITS/8;
299     *rbuflen += PRIMEBITS/8;
300
301     /* Ma */
302     gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, &nwritten, Ma);
303     if (nwritten < PRIMEBITS/8) {
304         memmove(rbuf + (PRIMEBITS/8) - nwritten, rbuf, nwritten);
305         memset(rbuf, 0, (PRIMEBITS/8) - nwritten);
306     }
307     rbuf += PRIMEBITS/8;
308     *rbuflen += PRIMEBITS/8;
309
310     ret = AFPERR_AUTHCONT;
311
312 error:              /* We exit here anyway */
313     /* We will need Ra later, but mustn't forget to release it ! */
314     gcry_mpi_release(Ma);
315     return ret;
316 }
317
318 /* -------------------------------- */
319 static int login(void *obj, char *username, int ulen,  struct passwd **uam_pwd _U_,
320                  char *ibuf, size_t ibuflen,
321                  char *rbuf, size_t *rbuflen)
322 {
323     if (( dhxpwd = uam_getname(obj, username, ulen)) == NULL ) {
324         LOG(log_info, logtype_uams, "DHX2: unknown username");
325         return AFPERR_NOTAUTH;
326     }
327
328     PAM_username = username;
329     LOG(log_info, logtype_uams, "DHX2 login: %s", username);
330     return dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen);
331 }
332
333 /* -------------------------------- */
334 /* dhx login: things are done in a slightly bizarre order to avoid
335  * having to clean things up if there's an error. */
336 static int pam_login(void *obj, struct passwd **uam_pwd,
337                      char *ibuf, size_t ibuflen,
338                      char *rbuf, size_t *rbuflen)
339 {
340     char *username;
341     size_t len, ulen;
342
343     *rbuflen = 0;
344
345     /* grab some of the options */
346     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
347         LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username  -- %s",
348             strerror(errno));
349         return AFPERR_PARAM;
350     }
351
352     len = (unsigned char) *ibuf++;
353     if ( len > ulen ) {
354         LOG(log_info, logtype_uams, "DHX2: Signature Retieval Failure -- %s",
355             strerror(errno));
356         return AFPERR_PARAM;
357     }
358
359     memcpy(username, ibuf, len );
360     ibuf += len;
361     username[ len ] = '\0';
362
363     if ((unsigned long) ibuf & 1) /* pad to even boundary */
364         ++ibuf;
365
366     return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
367 }
368
369 /* ----------------------------- */
370 static int pam_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
371                          char *ibuf, size_t ibuflen,
372                          char *rbuf, size_t *rbuflen)
373 {
374     char *username;
375     size_t len, ulen;
376     uint16_t  temp16;
377
378     *rbuflen = 0;
379
380     /* grab some of the options */
381     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
382         LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username  -- %s",
383             strerror(errno));
384         return AFPERR_PARAM;
385     }
386
387     if (*uname != 3)
388         return AFPERR_PARAM;
389     uname++;
390     memcpy(&temp16, uname, sizeof(temp16));
391     len = ntohs(temp16);
392
393     if ( !len || len > ulen ) {
394         LOG(log_info, logtype_uams, "DHX2: Signature Retrieval Failure -- %s",
395             strerror(errno));
396         return AFPERR_PARAM;
397     }
398     memcpy(username, uname +2, len );
399     username[ len ] = '\0';
400
401     return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
402 }
403
404 /* -------------------------------- */
405 static int logincont1(void *obj _U_, char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen)
406 {
407     int ret;
408     size_t nwritten;
409     gcry_mpi_t Mb, K, clientNonce;
410     unsigned char *K_bin = NULL;
411     char serverNonce_bin[16];
412     gcry_cipher_hd_t ctx;
413     gcry_error_t ctxerror;
414     uint16_t uint16;
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     uint16 = htons(ID+1);
501     memcpy(rbuf, &uint16, sizeof(uint16_t));
502     rbuf += 2;
503     *rbuflen += 2;
504
505     /* Client nonce + 1 */
506     gcry_mpi_print(GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, NULL, clientNonce);
507     /* Server nonce */
508     memcpy(rbuf+16, serverNonce_bin, 16);
509
510     /* Set the initialization vector for server->client transfer. */
511     ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ));
512     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
513         ret = AFPERR_MISC;
514         goto error_ctx;
515     }
516     /* Encrypt md5_K(clientNonce+1, serverNonce) inplace */
517     ctxerror = gcry_cipher_encrypt(ctx, rbuf, 32, NULL, 0);
518     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
519         ret = AFPERR_MISC;
520         goto error_ctx;
521     }
522     rbuf += 32;
523     *rbuflen += 32;
524
525     ret = AFPERR_AUTHCONT;
526     goto exit;
527
528 error_ctx:
529     gcry_cipher_close(ctx);
530 error_noctx:
531     gcry_mpi_release(serverNonce);
532     free(K_MD5hash);
533     K_MD5hash=NULL;
534 exit:
535     gcry_mpi_release(K);
536     gcry_mpi_release(Mb);
537     gcry_mpi_release(Ra);
538     gcry_mpi_release(clientNonce);
539     return ret;
540 }
541
542 /**
543  * Try to authenticate via PAM as "adminauthuser"
544  **/
545 static int loginasroot(const char *adminauthuser, const char **hostname, int status)
546 {
547     int PAM_error;
548
549     if ((PAM_error = pam_end(pamh, status)) != PAM_SUCCESS)
550         goto exit;
551     pamh = NULL;
552
553     if ((PAM_error = pam_start("netatalk", adminauthuser, &PAM_conversation, &pamh)) != PAM_SUCCESS) {
554         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s", pam_strerror(pamh,PAM_error));
555         goto exit;
556     }
557
558     /* solaris craps out if PAM_TTY and PAM_RHOST aren't set. */
559     pam_set_item(pamh, PAM_TTY, "afpd");
560     pam_set_item(pamh, PAM_RHOST, *hostname);
561     if ((PAM_error = pam_authenticate(pamh, 0)) != PAM_SUCCESS)
562         goto exit;
563
564     LOG(log_warning, logtype_uams, "DHX2: Authenticated as \"%s\"", adminauthuser);
565
566 exit:
567     return PAM_error;
568 }
569
570 static int logincont2(void *obj_in, struct passwd **uam_pwd,
571                       char *ibuf, size_t ibuflen,
572                       char *rbuf _U_, size_t *rbuflen)
573 {
574     AFPObj *obj = obj_in;
575     int ret = AFPERR_MISC;
576     int PAM_error;
577     const char *hostname = NULL;
578     gcry_mpi_t retServerNonce;
579     gcry_cipher_hd_t ctx;
580     gcry_error_t ctxerror;
581     char *utfpass = NULL;
582
583     *rbuflen = 0;
584
585     /* Packet size should be: Session ID + ServerNonce + Passwd buffer (evantually +10 extra bytes, see Apples Docs) */
586     if ((ibuflen != 2 + 16 + 256) && (ibuflen != 2 + 16 + 256 + 10)) {
587         LOG(log_error, logtype_uams, "DHX2: Paket length not correct: %u. Should be 274 or 284.", ibuflen);
588         ret = AFPERR_PARAM;
589         goto error_noctx;
590     }
591
592     retServerNonce = gcry_mpi_new(0);
593
594     /* For PAM */
595     uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL);
596
597     /* Set up our encryption context. */
598     ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
599     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
600         ret = AFPERR_MISC;
601         goto error_ctx;
602     }
603     /* Set key */
604     ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
605     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
606         ret = AFPERR_MISC;
607         goto error_ctx;
608     }
609     /* Set the initialization vector for client->server transfer. */
610     ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
611     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
612         ret = AFPERR_MISC;
613         goto error_ctx;
614     }
615
616     /* Skip Session ID */
617     ibuf += 2;
618
619     /* Finally: decrypt client's md5_K(serverNonce+1, passwor, C2SIV) inplace */
620     ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+256, NULL, 0);
621     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
622         ret = AFPERR_MISC;
623         goto error_ctx;
624     }
625     /* Pull out nonce. Should be serverNonce+1 */
626     gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
627     gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1);
628     if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) {
629         /* We're hacked!  */
630         ret = AFPERR_NOTAUTH;
631         goto error_ctx;
632     }
633     ibuf += 16;
634
635     /* ---- Start authentication with PAM --- */
636
637     /* The password is in legacy Mac encoding, convert it to host encoding */
638     if (convert_string_allocate(CH_MAC, CH_UNIX, ibuf, -1, &utfpass) == (size_t)-1) {
639         LOG(log_error, logtype_uams, "DHX2: conversion error");
640         goto error_ctx;
641     }
642     PAM_password = utfpass;
643
644 #ifdef DEBUG
645     LOG(log_maxdebug, logtype_default, "DHX2: password: %s", PAM_password);
646 #endif
647
648     /* Set these things up for the conv function */
649
650     ret = AFPERR_NOTAUTH;
651     PAM_error = pam_start("netatalk", PAM_username, &PAM_conversation, &pamh);
652     if (PAM_error != PAM_SUCCESS) {
653         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s", pam_strerror(pamh,PAM_error));
654         goto error_ctx;
655     }
656
657     /* solaris craps out if PAM_TTY and PAM_RHOST aren't set. */
658     pam_set_item(pamh, PAM_TTY, "afpd");
659     pam_set_item(pamh, PAM_RHOST, hostname);
660     pam_set_item(pamh, PAM_RUSER, PAM_username);
661
662     PAM_error = pam_authenticate(pamh, 0);
663     if (PAM_error != PAM_SUCCESS) {
664         if (PAM_error == PAM_MAXTRIES)
665             ret = AFPERR_PWDEXPR;
666         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s", pam_strerror(pamh, PAM_error));
667
668         if (!obj->options.adminauthuser)
669             goto error_ctx;
670         if (loginasroot(obj->options.adminauthuser, &hostname, PAM_error) != PAM_SUCCESS) {
671             goto error_ctx;
672         }
673     }
674
675     PAM_error = pam_acct_mgmt(pamh, 0);
676     if (PAM_error != PAM_SUCCESS ) {
677         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
678             pam_strerror(pamh, PAM_error));
679         if (PAM_error == PAM_NEW_AUTHTOK_REQD)    /* password expired */
680             ret = AFPERR_PWDEXPR;
681 #ifdef PAM_AUTHTOKEN_REQD
682         else if (PAM_error == PAM_AUTHTOKEN_REQD)
683             ret = AFPERR_PWDCHNG;
684 #endif
685         goto error_ctx;
686     }
687
688 #ifndef PAM_CRED_ESTABLISH
689 #define PAM_CRED_ESTABLISH PAM_ESTABLISH_CRED
690 #endif
691     PAM_error = pam_setcred(pamh, PAM_CRED_ESTABLISH);
692     if (PAM_error != PAM_SUCCESS) {
693         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
694             pam_strerror(pamh, PAM_error));
695         goto error_ctx;
696     }
697
698     PAM_error = pam_open_session(pamh, 0);
699     if (PAM_error != PAM_SUCCESS) {
700         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
701             pam_strerror(pamh, PAM_error));
702         goto error_ctx;
703     }
704
705     memset(ibuf, 0, 256); /* zero out the password */
706     if (utfpass)
707         memset(utfpass, 0, strlen(utfpass));
708     *uam_pwd = dhxpwd;
709     LOG(log_info, logtype_uams, "DHX2: PAM Auth OK!");
710
711     ret = AFP_OK;
712
713 error_ctx:
714     gcry_cipher_close(ctx);
715 error_noctx:
716     if (utfpass) free(utfpass);
717     free(K_MD5hash);
718     K_MD5hash=NULL;
719     gcry_mpi_release(serverNonce);
720     gcry_mpi_release(retServerNonce);
721     return ret;
722 }
723
724 static int pam_logincont(void *obj, struct passwd **uam_pwd,
725                          char *ibuf, size_t ibuflen,
726                          char *rbuf, size_t *rbuflen)
727 {
728     uint16_t retID;
729     int ret;
730
731     /* check for session id */
732     memcpy(&retID, ibuf, sizeof(uint16_t));
733     retID = ntohs(retID);
734     if (retID == ID)
735         ret = logincont1(obj, ibuf, ibuflen, rbuf, rbuflen);
736     else if (retID == ID+1)
737         ret = logincont2(obj, uam_pwd, ibuf,ibuflen, rbuf, rbuflen);
738     else {
739         LOG(log_info, logtype_uams, "DHX2: Session ID Mismatch");
740         ret = AFPERR_PARAM;
741     }
742     return ret;
743 }
744
745
746 /* logout */
747 static void pam_logout(void) {
748     pam_close_session(pamh, 0);
749     pam_end(pamh, 0);
750     pamh = NULL;
751 }
752
753 /****************************
754  * --- Change pwd stuff --- */
755
756 static int changepw_1(void *obj, char *uname,
757                       char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen)
758 {
759     *rbuflen = 0;
760
761     /* Remember it now, use it in changepw_3 */
762     PAM_username = uname;
763     return( dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen) );
764 }
765
766 static int changepw_2(void *obj,
767                       char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen)
768 {
769     return( logincont1(obj, ibuf, ibuflen, rbuf, rbuflen) );
770 }
771
772 static int changepw_3(void *obj _U_,
773                       char *ibuf, size_t ibuflen _U_,
774                       char *rbuf _U_, size_t *rbuflen _U_)
775 {
776     int ret;
777     int PAM_error;
778     uid_t uid;
779     pam_handle_t *lpamh;
780     const char *hostname = NULL;
781     gcry_mpi_t retServerNonce;
782     gcry_cipher_hd_t ctx;
783     gcry_error_t ctxerror;
784
785     *rbuflen = 0;
786
787     LOG(log_error, logtype_uams, "DHX2 ChangePW: packet 3 processing");
788
789     /* Packet size should be: Session ID + ServerNonce + 2*Passwd buffer */
790     if (ibuflen != 2 + 16 + 2*256) {
791         LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
792         ret = AFPERR_PARAM;
793         goto error_noctx;
794     }
795
796     retServerNonce = gcry_mpi_new(0);
797
798     /* For PAM */
799     uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL);
800
801     /* Set up our encryption context. */
802     ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
803     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
804         ret = AFPERR_MISC;
805         goto error_ctx;
806     }
807     /* Set key */
808     ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
809     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
810         ret = AFPERR_MISC;
811         goto error_ctx;
812     }
813
814     /* Set the initialization vector for client->server transfer. */
815     ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
816     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
817         ret = AFPERR_MISC;
818         goto error_ctx;
819     }
820
821     /* Skip Session ID */
822     ibuf += 2;
823
824     /* Finally: decrypt client's md5_K(serverNonce+1, 2*password, C2SIV) inplace */
825     ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+2*256, NULL, 0);
826     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
827         ret = AFPERR_MISC;
828         goto error_ctx;
829     }
830     /* Pull out nonce. Should be serverNonce+1 */
831     gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
832     gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1);
833     if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) {
834         /* We're hacked!  */
835         ret = AFPERR_NOTAUTH;
836         goto error_ctx;
837     }
838     ibuf += 16;
839
840     /* ---- Start pwd changing with PAM --- */
841     ibuf[255] = '\0';       /* For safety */
842     ibuf[511] = '\0';
843
844     /* check if new and old password are equal */
845     if (memcmp(ibuf, ibuf + 256, 255) == 0) {
846         LOG(log_info, logtype_uams, "DHX2 Chgpwd: new and old password are equal");
847         ret = AFPERR_PWDSAME;
848         goto error_ctx;
849     }
850
851     /* Set these things up for the conv function. PAM_username was set in changepw_1 */
852     PAM_password = ibuf + 256;
853     PAM_error = pam_start("netatalk", PAM_username, &PAM_conversation, &lpamh);
854     if (PAM_error != PAM_SUCCESS) {
855         LOG(log_info, logtype_uams, "DHX2 Chgpwd: PAM error in pam_start");
856         ret = AFPERR_PARAM;
857         goto error_ctx;
858     }
859     pam_set_item(lpamh, PAM_TTY, "afpd");
860     uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL);
861     pam_set_item(lpamh, PAM_RHOST, hostname);
862     uid = geteuid();
863     seteuid(0);
864     PAM_error = pam_authenticate(lpamh,0);
865     if (PAM_error != PAM_SUCCESS) {
866         LOG(log_info, logtype_uams, "DHX2 Chgpwd: error authenticating with PAM");
867         seteuid(uid);
868         pam_end(lpamh, PAM_error);
869         ret = AFPERR_NOTAUTH;
870         goto error_ctx;
871     }
872     PAM_password = ibuf;
873     PAM_error = pam_chauthtok(lpamh, 0);
874     seteuid(uid); /* un-root ourselves. */
875     memset(ibuf, 0, 512);
876     if (PAM_error != PAM_SUCCESS) {
877         LOG(log_info, logtype_uams, "DHX2 Chgpwd: error changing pw with PAM");
878         pam_end(lpamh, PAM_error);
879         ret = AFPERR_ACCESS;
880         goto error_ctx;
881     }
882     pam_end(lpamh, 0);
883     ret = AFP_OK;
884
885 error_ctx:
886     gcry_cipher_close(ctx);
887 error_noctx:
888     free(K_MD5hash);
889     K_MD5hash=NULL;
890     gcry_mpi_release(serverNonce);
891     gcry_mpi_release(retServerNonce);
892     return ret;
893 }
894
895 static int dhx2_changepw(void *obj _U_, char *uname,
896                          struct passwd *pwd _U_, char *ibuf, size_t ibuflen _U_,
897                          char *rbuf _U_, size_t *rbuflen _U_)
898 {
899     /* We use this to serialize the three incoming FPChangePassword calls */
900     static int dhx2_changepw_status = 1;
901
902     int ret = AFPERR_NOTAUTH;  /* gcc can't figure out it's always initialized */
903
904     switch (dhx2_changepw_status) {
905     case 1:
906         ret = changepw_1( obj, uname, ibuf, ibuflen, rbuf, rbuflen);
907         if ( ret == AFPERR_AUTHCONT)
908             dhx2_changepw_status = 2;
909         break;
910     case 2:
911         ret = changepw_2( obj, ibuf, ibuflen, rbuf, rbuflen);
912         if ( ret == AFPERR_AUTHCONT)
913             dhx2_changepw_status = 3;
914         else
915             dhx2_changepw_status = 1;
916         break;
917     case 3:
918         ret = changepw_3( obj, ibuf, ibuflen, rbuf, rbuflen);
919         dhx2_changepw_status = 1; /* Whether is was succesfull or not: we
920                                      restart anyway !*/
921         break;
922     }
923     return ret;
924 }
925
926 static int uam_setup(void *obj, const char *path)
927 {
928     if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHX2", pam_login,
929                      pam_logincont, pam_logout, pam_login_ext) < 0)
930         return -1;
931     if (uam_register(UAM_SERVER_CHANGEPW, path, "DHX2", dhx2_changepw) < 0)
932         return -1;
933
934     LOG(log_debug, logtype_uams, "DHX2: generating mersenne primes");
935     /* Generate p and g for DH */
936     if (dh_params_generate(PRIMEBITS) != 0) {
937         LOG(log_error, logtype_uams, "DHX2: Couldn't generate p and g");
938         return -1;
939     }
940
941     return 0;
942 }
943
944 static void uam_cleanup(void)
945 {
946     uam_unregister(UAM_SERVER_LOGIN, "DHX2");
947     uam_unregister(UAM_SERVER_CHANGEPW, "DHX2");
948
949     LOG(log_debug, logtype_uams, "DHX2: uam_cleanup");
950
951     gcry_mpi_release(p);
952     gcry_mpi_release(g);
953 }
954
955 UAM_MODULE_EXPORT struct uam_export uams_dhx2 = {
956     UAM_MODULE_SERVER,
957     UAM_MODULE_VERSION,
958     uam_setup, uam_cleanup
959 };
960
961
962 UAM_MODULE_EXPORT struct uam_export uams_dhx2_pam = {
963     UAM_MODULE_SERVER,
964     UAM_MODULE_VERSION,
965     uam_setup, uam_cleanup
966 };
967
968 #endif /* USE_PAM && UAM_DHX2 */