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