]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_dhx2_passwd.c
Merge sf.net
[netatalk.git] / etc / uams / uams_dhx2_passwd.c
1 /*
2  * $Id: uams_dhx2_passwd.c,v 1.8 2010-03-30 10:25:49 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 #ifdef UAM_DHX2
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <errno.h>
19 #include <pwd.h>
20
21 #ifdef NETBSD
22 #define _XOPEN_SOURCE 500 /* for crypt() */
23 #endif
24 #ifdef FREEBSD
25 #define _XOPEN_SOURCE /* for crypt() */
26 #endif
27
28 #ifdef HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31
32 #ifdef HAVE_CRYPT_H
33 #include <crypt.h>
34 #endif
35
36 #ifdef HAVE_SYS_TIME_H
37 #include <sys/time.h>
38 #endif
39
40 #ifdef HAVE_TIME_H
41 #include <time.h>
42 #endif
43
44 #ifdef SHADOWPW
45 #include <shadow.h>
46 #endif
47
48 #ifdef HAVE_LIBGCRYPT
49 #include <gcrypt.h>
50 #endif
51
52 #include <atalk/afp.h>
53 #include <atalk/uam.h>
54 #include <atalk/logger.h>
55
56 /* Number of bits for p which we generate. Everybode out there uses 512, so we beet them */
57 #define PRIMEBITS 1024
58
59 /* hash a number to a 16-bit quantity */
60 #define dhxhash(a) ((((unsigned long) (a) >> 8) ^   \
61                      (unsigned long) (a)) & 0xffff)
62
63 /* Some parameters need be maintained across calls */
64 static gcry_mpi_t p, Ra;
65 static gcry_mpi_t serverNonce;
66 static char *K_MD5hash = NULL;
67 static int K_hash_len;
68 static u_int16_t ID;
69
70 /* The initialization vectors for CAST128 are fixed by Apple. */
71 static unsigned char dhx_c2siv[] = { 'L', 'W', 'a', 'l', 'l', 'a', 'c', 'e' };
72 static unsigned char dhx_s2civ[] = { 'C', 'J', 'a', 'l', 'b', 'e', 'r', 't' };
73
74 /* Static variables used to communicate between the conversation function
75  * and the server_login function */
76 static struct passwd *dhxpwd;
77
78 /*********************************************************
79  * Crypto helper func to generate p and g for use in DH.
80  * libgcrypt doesn't provide one directly.
81  * Algorithm taken from GNUTLS:gnutls_dh_primes.c
82  *********************************************************/
83
84 /**
85  * This function will generate a new pair of prime and generator for use in
86  * the Diffie-Hellman key exchange.
87  * The bits value should be one of 768, 1024, 2048, 3072 or 4096.
88  **/
89
90 static int
91 dh_params_generate (gcry_mpi_t *ret_p, gcry_mpi_t *ret_g, unsigned int bits) {
92
93     int result, times = 0, qbits;
94
95     gcry_mpi_t g = NULL, prime = NULL;
96     gcry_mpi_t *factors = NULL;
97     gcry_error_t err;
98
99     /* Version check should be the very first call because it
100        makes sure that important subsystems are intialized. */
101     if (!gcry_check_version (GCRYPT_VERSION)) {
102         LOG(log_info, logtype_uams, "PAM DHX2: libgcrypt versions mismatch. Need: %s", GCRYPT_VERSION);
103         result = AFPERR_MISC;
104         goto error;
105     }
106
107     if (bits < 256)
108         qbits = bits / 2;
109     else
110         qbits = (bits / 40) + 105;
111
112     if (qbits & 1) /* better have an even number */
113         qbits++;
114
115     /* find a prime number of size bits. */
116     do {
117         if (times) {
118             gcry_mpi_release (prime);
119             gcry_prime_release_factors (factors);
120         }
121         err = gcry_prime_generate (&prime, bits, qbits, &factors, NULL, NULL,
122                                    GCRY_STRONG_RANDOM, GCRY_PRIME_FLAG_SPECIAL_FACTOR);
123         if (err != 0) {
124             result = AFPERR_MISC;
125             goto error;
126         }
127         err = gcry_prime_check (prime, 0);
128         times++;
129     } while (err != 0 && times < 10);
130
131     if (err != 0) {
132         result = AFPERR_MISC;
133         goto error;
134     }
135
136     /* generate the group generator. */
137     err = gcry_prime_group_generator (&g, prime, factors, NULL);
138     if (err != 0) {
139         result = AFPERR_MISC;
140         goto error;
141     }
142
143     gcry_prime_release_factors (factors);
144     factors = NULL;
145
146     if (ret_g)
147         *ret_g = g;
148     else
149         gcry_mpi_release (g);
150     if (ret_p)
151         *ret_p = prime;
152     else
153         gcry_mpi_release (prime);
154
155     return 0;
156
157 error:
158     gcry_prime_release_factors (factors);
159     gcry_mpi_release (g);
160     gcry_mpi_release (prime);
161
162     return result;
163 }
164
165 static int dhx2_setup(void *obj, char *ibuf _U_, size_t ibuflen _U_,
166                       char *rbuf, size_t *rbuflen)
167 {
168     int ret;
169     size_t nwritten;
170     gcry_mpi_t g, Ma;
171     char *Ra_binary = NULL;
172 #ifdef SHADOWPW
173     struct spwd *sp;
174 #endif /* SHADOWPW */
175
176     *rbuflen = 0;
177
178     /* Initialize passwd/shadow */
179 #ifdef SHADOWPW
180     if (( sp = getspnam( dhxpwd->pw_name )) == NULL ) {
181         LOG(log_info, logtype_uams, "DHX2: no shadow passwd entry for this user");
182         return AFPERR_NOTAUTH;
183     }
184     dhxpwd->pw_passwd = sp->sp_pwdp;
185 #endif /* SHADOWPW */
186
187     if (!dhxpwd->pw_passwd)
188         return AFPERR_NOTAUTH;
189
190     /* Initialize DH params */
191
192     p = gcry_mpi_new(0);
193     g = gcry_mpi_new(0);
194     Ra = gcry_mpi_new(0);
195     Ma = gcry_mpi_new(0);
196
197     /* Generate p and g for DH */
198     ret = dh_params_generate( &p, &g, PRIMEBITS);
199     if (ret != 0) {
200         LOG(log_info, logtype_uams, "DHX2: Couldn't generate p and g");
201         ret = AFPERR_MISC;
202         goto error;
203     }
204
205     /* Generate our random number Ra. */
206     Ra_binary = calloc(1, PRIMEBITS/8);
207     if (Ra_binary == NULL) {
208         ret = AFPERR_MISC;
209         goto error;
210     }
211     gcry_randomize(Ra_binary, PRIMEBITS/8, GCRY_STRONG_RANDOM);
212     gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, PRIMEBITS/8, NULL);
213     free(Ra_binary);
214     Ra_binary = NULL;
215
216     /* Ma = g^Ra mod p. This is our "public" key */
217     gcry_mpi_powm(Ma, g, Ra, p);
218
219     /* ------- DH Init done ------ */
220     /* Start building reply packet */
221
222     /* Session ID first */
223     ID = dhxhash(obj);
224     *(u_int16_t *)rbuf = htons(ID);
225     rbuf += 2;
226     *rbuflen += 2;
227
228     /* g is next */
229     gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, 4, &nwritten, g);
230     if (nwritten < 4) {
231         memmove( rbuf+4-nwritten, rbuf, nwritten);
232         memset( rbuf, 0, 4-nwritten);
233     }
234     rbuf += 4;
235     *rbuflen += 4;
236
237     /* len = length of p = PRIMEBITS/8 */
238     *(u_int16_t *)rbuf = htons((u_int16_t) PRIMEBITS/8);
239     rbuf += 2;
240     *rbuflen += 2;
241
242     /* p */
243     gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, NULL, p);
244     rbuf += PRIMEBITS/8;
245     *rbuflen += PRIMEBITS/8;
246
247     /* Ma */
248     gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, &nwritten, Ma);
249     if (nwritten < PRIMEBITS/8) {
250         memmove(rbuf + (PRIMEBITS/8) - nwritten, rbuf, nwritten);
251         memset(rbuf, 0, (PRIMEBITS/8) - nwritten);
252     }
253     rbuf += PRIMEBITS/8;
254     *rbuflen += PRIMEBITS/8;
255
256     ret = AFPERR_AUTHCONT;
257
258 error:              /* We exit here anyway */
259     /* We will only need p and Ra later, but mustn't forget to release it ! */
260     gcry_mpi_release(g);
261     gcry_mpi_release(Ma);
262     return ret;
263 }
264
265 /* -------------------------------- */
266 static int login(void *obj, char *username, int ulen,  struct passwd **uam_pwd _U_,
267                  char *ibuf, size_t ibuflen,
268                  char *rbuf, size_t *rbuflen)
269 {
270     if (( dhxpwd = uam_getname(obj, username, ulen)) == NULL ) {
271         LOG(log_info, logtype_uams, "DHX2: unknown username");
272         return AFPERR_NOTAUTH;
273     }
274
275     LOG(log_info, logtype_uams, "DHX2 login: %s", username);
276     return dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen);
277 }
278
279 /* -------------------------------- */
280 /* dhx login: things are done in a slightly bizarre order to avoid
281  * having to clean things up if there's an error. */
282 static int passwd_login(void *obj, struct passwd **uam_pwd,
283                         char *ibuf, size_t ibuflen,
284                         char *rbuf, size_t *rbuflen)
285 {
286     char *username;
287     size_t len, ulen;
288
289     *rbuflen = 0;
290
291     /* grab some of the options */
292     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
293         LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username  -- %s",
294             strerror(errno));
295         return AFPERR_PARAM;
296     }
297
298     len = (unsigned char) *ibuf++;
299     if ( len > ulen ) {
300         LOG(log_info, logtype_uams, "DHX2: Signature Retieval Failure -- %s",
301             strerror(errno));
302         return AFPERR_PARAM;
303     }
304
305     memcpy(username, ibuf, len );
306     ibuf += len;
307     username[ len ] = '\0';
308
309     if ((unsigned long) ibuf & 1) /* pad to even boundary */
310         ++ibuf;
311
312     return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
313 }
314
315 /* ----------------------------- */
316 static int passwd_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
317                             char *ibuf, size_t ibuflen,
318                             char *rbuf, size_t *rbuflen)
319 {
320     char *username;
321     size_t len, ulen;
322     u_int16_t  temp16;
323
324     *rbuflen = 0;
325
326     /* grab some of the options */
327     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
328         LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username  -- %s",
329             strerror(errno));
330         return AFPERR_PARAM;
331     }
332
333     if (*uname != 3)
334         return AFPERR_PARAM;
335     uname++;
336     memcpy(&temp16, uname, sizeof(temp16));
337     len = ntohs(temp16);
338
339     if ( !len || len > ulen ) {
340         LOG(log_info, logtype_uams, "DHX2: Signature Retrieval Failure -- %s",
341             strerror(errno));
342         return AFPERR_PARAM;
343     }
344     memcpy(username, uname +2, len );
345     username[ len ] = '\0';
346
347     return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
348 }
349
350 /* -------------------------------- */
351
352 static int logincont1(void *obj _U_, struct passwd **uam_pwd _U_,
353                       char *ibuf, size_t ibuflen,
354                       char *rbuf, size_t *rbuflen)
355 {
356     size_t nwritten;
357     int ret;
358     gcry_mpi_t Mb, K, clientNonce;
359     unsigned char *K_bin = NULL;
360     char serverNonce_bin[16];
361     gcry_cipher_hd_t ctx;
362     gcry_error_t ctxerror;
363
364     *rbuflen = 0;
365
366     Mb = gcry_mpi_new(0);
367     K = gcry_mpi_new(0);
368     clientNonce = gcry_mpi_new(0);
369     serverNonce = gcry_mpi_new(0);
370
371     /* Packet size should be: Session ID + Ma + Encrypted client nonce */
372     if (ibuflen != 2 + PRIMEBITS/8 + 16) {
373         LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
374         ret = AFPERR_PARAM;
375         goto error_noctx;
376     }
377
378     /* Skip session id */
379     ibuf += 2;
380
381     /* Extract Mb, client's "public" key */
382     gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, ibuf, PRIMEBITS/8, NULL);
383     ibuf += PRIMEBITS/8;
384
385     /* Now finally generate the Key: K = Mb^Ra mod p */
386     gcry_mpi_powm(K, Mb, Ra, p);
387
388     /* We need K in binary form in order to ... */
389     K_bin = calloc(1, PRIMEBITS/8);
390     if (K_bin == NULL) {
391         ret = AFPERR_MISC;
392         goto error_noctx;
393     }
394     gcry_mpi_print(GCRYMPI_FMT_USG, K_bin, PRIMEBITS/8, &nwritten, K);
395     if (nwritten < PRIMEBITS/8) {
396         memmove(K_bin + PRIMEBITS/8 - nwritten, K_bin, nwritten);
397         memset(K_bin, 0, PRIMEBITS/8 - nwritten);
398     }
399
400     /* ... generate the MD5 hash of K. K_MD5hash is what we actually use ! */
401     K_MD5hash = calloc(1, K_hash_len = gcry_md_get_algo_dlen(GCRY_MD_MD5));
402     if (K_MD5hash == NULL) {
403         ret = AFPERR_MISC;
404         free(K_bin);
405         K_bin = NULL;
406         goto error_noctx;
407     }
408     gcry_md_hash_buffer(GCRY_MD_MD5, K_MD5hash, K_bin, PRIMEBITS/8);
409     free(K_bin);
410     K_bin = NULL;
411
412     /* FIXME: To support the Reconnect UAM, we need to store this key somewhere */
413
414     /* Set up our encryption context. */
415     ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
416     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
417         ret = AFPERR_MISC;
418         goto error_ctx;
419     }
420     /* Set key */
421     ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
422     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
423         ret = AFPERR_MISC;
424         goto error_ctx;
425     }
426     /* Set the initialization vector for client->server transfer. */
427     ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
428     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
429         ret = AFPERR_MISC;
430         goto error_ctx;
431     }
432     /* Finally: decrypt client's md5_K(client nonce, C2SIV) inplace */
433     ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16, NULL, 0);
434     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
435         ret = AFPERR_MISC;
436         goto error_ctx;
437     }
438     /* Pull out clients nonce */
439     gcry_mpi_scan(&clientNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
440     /* Increment nonce */
441     gcry_mpi_add_ui(clientNonce, clientNonce, 1);
442
443     /* Generate our nonce and remember it for Logincont2 */
444     gcry_create_nonce(serverNonce_bin, 16); /* We'll use this here */
445     gcry_mpi_scan(&serverNonce, GCRYMPI_FMT_USG, serverNonce_bin, 16, NULL); /* For use in Logincont2 */
446
447     /* ---- Start building reply packet ---- */
448
449     /* Session ID + 1 first */
450     *(u_int16_t *)rbuf = htons(ID+1);
451     rbuf += 2;
452     *rbuflen += 2;
453
454     /* Client nonce + 1 */
455     gcry_mpi_print(GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, NULL, clientNonce);
456     /* Server nonce */
457     memcpy(rbuf+16, serverNonce_bin, 16);
458
459     /* Set the initialization vector for server->client transfer. */
460     ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ));
461     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
462         ret = AFPERR_MISC;
463         goto error_ctx;
464     }
465     /* Encrypt md5_K(clientNonce+1, serverNonce) inplace */
466     ctxerror = gcry_cipher_encrypt(ctx, rbuf, 32, NULL, 0);
467     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
468         ret = AFPERR_MISC;
469         goto error_ctx;
470     }
471     rbuf += 32;
472     *rbuflen += 32;
473
474     ret = AFPERR_AUTHCONT;
475     goto exit;
476
477 error_ctx:
478     gcry_cipher_close(ctx);
479 error_noctx:
480     gcry_mpi_release(serverNonce);
481     free(K_MD5hash);
482     K_MD5hash=NULL;
483 exit:
484     gcry_mpi_release(K);
485     gcry_mpi_release(Mb);
486     gcry_mpi_release(Ra);
487     gcry_mpi_release(p);
488     gcry_mpi_release(clientNonce);
489     return ret;
490 }
491
492 static int logincont2(void *obj _U_, struct passwd **uam_pwd,
493                       char *ibuf, size_t ibuflen,
494                       char *rbuf _U_, size_t *rbuflen)
495 {
496 #ifdef SHADOWPW
497     struct spwd *sp;
498 #endif /* SHADOWPW */
499     int ret;
500     char *p;
501     gcry_mpi_t retServerNonce;
502     gcry_cipher_hd_t ctx;
503     gcry_error_t ctxerror;
504
505     *rbuflen = 0;
506     retServerNonce = gcry_mpi_new(0);
507
508     /* Packet size should be: Session ID + ServerNonce + Passwd buffer (evantually +10 extra bytes, see Apples Docs)*/
509     if ((ibuflen != 2 + 16 + 256) && (ibuflen != 2 + 16 + 256 + 10)) {
510         LOG(log_error, logtype_uams, "DHX2: Paket length not correct: %d. Should be 274 or 284.", ibuflen);
511         ret = AFPERR_PARAM;
512         goto error_noctx;
513     }
514
515     /* Set up our encryption context. */
516     ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
517     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
518         ret = AFPERR_MISC;
519         goto error_ctx;
520     }
521     /* Set key */
522     ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
523     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
524         ret = AFPERR_MISC;
525         goto error_ctx;
526     }
527     /* Set the initialization vector for client->server transfer. */
528     ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
529     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
530         ret = AFPERR_MISC;
531         goto error_ctx;
532     }
533
534     /* Skip Session ID */
535     ibuf += 2;
536
537     /* Finally: decrypt client's md5_K(serverNonce+1, passwor, C2SIV) inplace */
538     ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+256, NULL, 0);
539     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
540         ret = AFPERR_MISC;
541         goto error_ctx;
542     }
543     /* Pull out nonce. Should be serverNonce+1 */
544     gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
545     gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1);
546     if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) {
547         /* We're hacked!  */
548         ret = AFPERR_NOTAUTH;
549         goto error_ctx;
550     }
551     ibuf += 16;         /* ibuf now point to passwd in cleartext */
552
553     /* ---- Start authentication --- */
554     ret = AFPERR_NOTAUTH;
555
556     p = crypt( ibuf, dhxpwd->pw_passwd );
557     memset(ibuf, 0, 255);
558     if ( strcmp( p, dhxpwd->pw_passwd ) == 0 ) {
559         *uam_pwd = dhxpwd;
560         ret = AFP_OK;
561     }
562
563 #ifdef SHADOWPW
564     if (( sp = getspnam( dhxpwd->pw_name )) == NULL ) {
565         LOG(log_info, logtype_uams, "no shadow passwd entry for %s", dhxpwd->pw_name);
566         ret = AFPERR_NOTAUTH;
567         goto exit;
568     }
569
570     /* check for expired password */
571     if (sp && sp->sp_max != -1 && sp->sp_lstchg) {
572         time_t now = time(NULL) / (60*60*24);
573         int32_t expire_days = sp->sp_lstchg - now + sp->sp_max;
574         if ( expire_days < 0 ) {
575             LOG(log_info, logtype_uams, "password for user %s expired", dhxpwd->pw_name);
576             ret = AFPERR_PWDEXPR;
577             goto exit;
578         }
579     }
580 #endif /* SHADOWPW */
581
582 error_ctx:
583     gcry_cipher_close(ctx);
584 error_noctx:
585 exit:
586     free(K_MD5hash);
587     K_MD5hash=NULL;
588     gcry_mpi_release(serverNonce);
589     gcry_mpi_release(retServerNonce);
590     return ret;
591 }
592
593 static int passwd_logincont(void *obj, struct passwd **uam_pwd,
594                             char *ibuf, size_t ibuflen,
595                             char *rbuf, size_t *rbuflen)
596 {
597     u_int16_t retID;
598     int ret;
599
600     /* check for session id */
601     retID = ntohs(*(u_int16_t *)ibuf);
602     if (retID == ID)
603         ret = logincont1(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
604     else if (retID == ID+1)
605         ret = logincont2(obj, uam_pwd, ibuf,ibuflen, rbuf, rbuflen);
606     else {
607         LOG(log_info, logtype_uams, "DHX2: Session ID Mismatch");
608         ret = AFPERR_PARAM;
609     }
610     return ret;
611 }
612
613 static int uam_setup(const char *path)
614 {
615     if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHX2", passwd_login,
616                      passwd_logincont, NULL, passwd_login_ext) < 0)
617         return -1;
618     return 0;
619 }
620
621 static void uam_cleanup(void)
622 {
623     uam_unregister(UAM_SERVER_LOGIN, "DHX2");
624 }
625
626
627 UAM_MODULE_EXPORT struct uam_export uams_dhx2 = {
628     UAM_MODULE_SERVER,
629     UAM_MODULE_VERSION,
630     uam_setup, uam_cleanup
631 };
632
633
634 UAM_MODULE_EXPORT struct uam_export uams_dhx2_passwd = {
635     UAM_MODULE_SERVER,
636     UAM_MODULE_VERSION,
637     uam_setup, uam_cleanup
638 };
639
640 #endif /* UAM_DHX2 */
641