]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_gss.c
Update NEWS
[netatalk.git] / etc / uams / uams_gss.c
1 /*
2  * $Id: uams_gss.c,v 1.12 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  * Copyright (c) 2003 The Reed Institute
7  * Copyright (c) 2004 Bjoern Fernhomberg
8  * All Rights Reserved.  See COPYRIGHT.
9  */
10
11 #ifdef HAVE_CONFIG_H
12 #include "config.h"
13 #endif /* HAVE_CONFIG_H */
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif /* HAVE_UNISTD_H */
20
21 /* STDC check */
22 #if STDC_HEADERS
23 #include <string.h>
24 #else /* STDC_HEADERS */
25 #ifndef HAVE_STRCHR
26 #define strchr index
27 #define strrchr index
28 #endif /* HAVE_STRCHR */
29 char *strchr (), *strrchr ();
30 #ifndef HAVE_MEMCPY
31 #define memcpy(d,s,n) bcopy ((s), (d), (n))
32 #define memmove(d,s,n) bcopy ((s), (d), (n))
33 #endif /* ! HAVE_MEMCPY */
34 #endif /* STDC_HEADERS */
35
36 #include <errno.h>
37 #include <atalk/logger.h>
38 #include <atalk/afp.h>
39 #include <atalk/uam.h>
40 #include <atalk/util.h>
41
42 /* Kerberos includes */
43
44 #if HAVE_GSSAPI_H
45 #include <gssapi.h>
46 #endif
47
48 #if HAVE_GSSAPI_GSSAPI_H
49 #include <gssapi/gssapi.h>
50 #endif
51
52 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
53 #include <gssapi/gssapi_generic.h>
54 #endif
55
56 #if HAVE_GSSAPI_GSSAPI_KRB5_H
57 #include <gssapi/gssapi_krb5.h>
58 #endif
59
60 #if HAVE_COM_ERR_H
61 #include <com_err.h>
62 #endif
63
64 /* We work around something I don't entirely understand... */
65 /* BF: This is a Heimdal/MIT compatibility fix */
66 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
67 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
68 #endif
69
70 #ifdef MIN
71 #undef MIN
72 #endif
73
74 #define MIN(a, b) ((a > b) ? b : a)
75
76 static void log_status( char *s, OM_uint32 major_status,
77                         OM_uint32 minor_status )
78 {
79     gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
80     OM_uint32 min_status, maj_status;
81     OM_uint32 maj_ctx = 0, min_ctx = 0;
82
83     while (1) {
84         maj_status = gss_display_status( &min_status, major_status,
85                                          GSS_C_GSS_CODE, GSS_C_NULL_OID,
86                                          &maj_ctx, &msg );
87         LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
88             (int)msg.length, msg.value, strerror(errno));
89         gss_release_buffer(&min_status, &msg);
90
91         if (!maj_ctx)
92             break;
93     }
94     while (1) {
95         maj_status = gss_display_status( &min_status, minor_status,
96                                          GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
97                                          &min_ctx, &msg );
98         LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
99             (int)msg.length, msg.value, strerror(errno));
100         gss_release_buffer(&min_status, &msg);
101
102         if (!min_ctx)
103             break;
104     }
105 }
106
107
108 static void log_ctx_flags( OM_uint32 flags )
109 {
110 #ifdef DEBUG1
111     if (flags & GSS_C_DELEG_FLAG)
112         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
113     if (flags & GSS_C_MUTUAL_FLAG)
114         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
115     if (flags & GSS_C_REPLAY_FLAG)
116         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
117     if (flags & GSS_C_SEQUENCE_FLAG)
118         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
119     if (flags & GSS_C_CONF_FLAG)
120         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
121     if (flags & GSS_C_INTEG_FLAG)
122         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
123 #endif
124 }
125
126 static void log_principal(gss_name_t server_name)
127 {
128 #if 0
129     if (server_name == GSS_C_NO_NAME)
130         return;
131     /* FIXME: must call gss_canonicalize_name before gss_export_name */
132     OM_uint32 major_status = 0, minor_status = 0;
133     gss_buffer_desc exported_name;
134
135     /* Only for debugging purposes, check the gssapi internal representation */
136     major_status = gss_export_name(&minor_status, server_name, &exported_name);
137     LOG(log_debug, logtype_uams, "log_principal: exported server name is %s", (char*) exported_name.value);
138     gss_release_buffer( &minor_status, &exported_name );
139 #endif
140 }
141
142 /* get the principal from afpd and import it into server_name */
143 static int get_afpd_principal(void *obj, gss_name_t *server_name)
144 {
145     OM_uint32 major_status = 0, minor_status = 0;
146     char *fqdn, *service, *principal, *p;
147     size_t fqdnlen=0, servicelen=0;
148     size_t principal_length;
149     gss_buffer_desc s_princ_buffer;
150
151     /* get information from afpd */
152     if (uam_afpserver_option(obj, UAM_OPTION_FQDN, (void*) &fqdn, &fqdnlen) < 0)
153         return 1;
154     LOG(log_debug, logtype_uams, "get_afpd_principal: fqdn: %s", fqdn);
155
156     if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
157         return 1;
158     LOG(log_debug, logtype_uams, "get_afpd_principal: service: %s", service);
159
160     /* if we don't have all the info, log that and return GSS_C_NO_NAME */
161     if (!service || !servicelen || !fqdn || !fqdnlen) {
162         LOG(log_note, logtype_uams,
163             "get_afpd_principal: could not retrieve information from afpd, using default service principal(s)");
164
165                *server_name = GSS_C_NO_NAME;
166         return 0;
167     }
168
169     /* allocate memory to hold the temporary principal string */
170     principal_length = servicelen + 1 + fqdnlen + 1;
171     if ( NULL == (principal = (char*) malloc( principal_length)) ) {
172         LOG(log_error, logtype_uams,
173             "get_afpd_principal: out of memory allocating %u bytes",
174             principal_length);
175         return 1;
176     }
177
178     /*
179      * Build the principal string.
180      * Format: 'service@fqdn'
181      */
182     strlcpy( principal, service, principal_length);
183     strlcat( principal, "@", principal_length);
184
185     /*
186      * The fqdn we get from afpd may contain a port.
187      * We need to strip the port from fqdn for principal.
188      */
189     if ((p = strchr(fqdn, ':')))
190         *p = '\0';
191
192     strlcat( principal, fqdn, principal_length);
193     if (p)
194         *p = ':';
195     /*
196      * Import our principal into the gssapi internal representation
197      * stored in server_name.
198      */
199     s_princ_buffer.value = principal;
200     s_princ_buffer.length = strlen( principal ) + 1;
201
202     LOG(log_debug, logtype_uams, "get_afpd_principal: importing principal `%s'", principal);
203     major_status = gss_import_name( &minor_status,
204                                     &s_princ_buffer,
205                                     GSS_C_NT_HOSTBASED_SERVICE,
206                                     server_name );
207
208     /*
209      * Get rid of malloc'ed memmory.
210      * Don't release the s_princ_buffer, we free principal instead.
211      */
212     free(principal);
213
214     if (major_status != GSS_S_COMPLETE) {
215         /* Importing our service principal failed, bail out. */
216         log_status( "import_principal", major_status, minor_status );
217         return 1;
218     }
219     return 0;
220 }
221
222
223 /* get the username */
224 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
225 {
226     OM_uint32 major_status = 0, minor_status = 0;
227     gss_buffer_desc client_name_buffer;
228     char *p;
229     int namelen, ret=0;
230
231     /*
232      * To extract the unix username, use gss_display_name on client_name.
233      * We do rely on gss_display_name returning a zero terminated string.
234      * The username returned contains the realm and possibly an instance.
235      * We only want the username for afpd, so we have to strip those from
236      * the username before copying it to afpd's buffer.
237      */
238
239     major_status = gss_display_name( &minor_status, *client_name,
240                                      &client_name_buffer, (gss_OID *)NULL );
241     if (major_status != GSS_S_COMPLETE) {
242         log_status( "display_name", major_status, minor_status );
243         return 1;
244     }
245
246     LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
247
248     /* chop off realm */
249     p = strchr( client_name_buffer.value, '@' );
250     if (p)
251         *p = 0;
252     /* FIXME: chop off instance? */
253     p = strchr( client_name_buffer.value, '/' );
254     if (p)
255         *p = 0;
256
257     /* check if this username fits into afpd's username buffer */
258     namelen = strlen(client_name_buffer.value);
259     if ( namelen >= ulen ) {
260         /* The username is too long for afpd's buffer, bail out */
261         LOG(log_error, logtype_uams,
262             "get_client_username: username `%s' too long", client_name_buffer.value);
263         ret = 1;
264     }
265     else {
266         /* copy stripped username to afpd's buffer */
267         strlcpy(username, client_name_buffer.value, ulen);
268     }
269
270     /* we're done with client_name_buffer, release it */
271     gss_release_buffer(&minor_status, &client_name_buffer );
272
273     return ret;
274 }
275
276 /* wrap afpd's sessionkey */
277 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
278 {
279     OM_uint32 status = 0;
280     int ret=0;
281     gss_buffer_desc sesskey_buff, wrap_buff;
282
283     /*
284      * gss_wrap afpd's session_key.
285      * This is needed fo OS X 10.3 clients. They request this information
286      * with type 8 (kGetKerberosSessionKey) on FPGetSession.
287      * See AFP 3.1 specs, page 77.
288      */
289
290     sesskey_buff.value = sinfo->sessionkey;
291     sesskey_buff.length = sinfo->sessionkey_len;
292
293     /* gss_wrap the session key with the default machanism.
294        Require both confidentiality and integrity services */
295     gss_wrap (&status, context, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff);
296
297     if ( status != GSS_S_COMPLETE) {
298         LOG(log_error, logtype_uams, "wrap_sessionkey: failed to gss_wrap sessionkey");
299         log_status( "GSS wrap", 0, status );
300         return 1;
301     }
302
303     /* store the wrapped session key in afpd's session_info struct */
304     if ( NULL == (sinfo->cryptedkey = malloc ( wrap_buff.length )) ) {
305         LOG(log_error, logtype_uams,
306             "wrap_sessionkey: out of memory tyring to allocate %u bytes",
307             wrap_buff.length);
308         ret = 1;
309     } else {
310         /* cryptedkey is binary data */
311         memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
312         sinfo->cryptedkey_len = wrap_buff.length;
313     }
314
315     /* we're done with buffer, release */
316     gss_release_buffer( &status, &wrap_buff );
317
318     return ret;
319 }
320
321 /*-------------*/
322 static int acquire_credentials (gss_name_t *server_name, gss_cred_id_t *server_creds)
323 {
324     OM_uint32 major_status = 0, minor_status = 0;
325     char *envp;
326
327     if ((envp = getenv("KRB5_KTNAME")))
328         LOG(log_debug, logtype_uams,
329             "acquire credentials: acquiring credentials (uid = %d, keytab = %s)",
330             (int)geteuid(), envp);
331     else
332         LOG(log_debug, logtype_uams,
333             "acquire credentials: acquiring credentials (uid = %d) - $KRB5_KTNAME not found in env",
334             (int)geteuid());
335         
336     /*
337      * Acquire credentials usable for accepting context negotiations.
338      * Credentials are for server_name, have an indefinite lifetime,
339      * have no specific mechanisms, are to be used for accepting context
340      * negotiations and are to be placed in server_creds.
341      * We don't care about the mechanisms or about the time for which they are valid.
342      */
343     major_status = gss_acquire_cred( &minor_status, *server_name,
344                                      GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
345                                      server_creds, NULL, NULL );
346
347     if (major_status != GSS_S_COMPLETE) {
348         log_status( "acquire_cred", major_status, minor_status );
349         return 1;
350     }
351
352     return 0;
353 }
354
355 /*-------------*/
356 static int accept_sec_context (gss_ctx_id_t *context, gss_cred_id_t server_creds,
357                                gss_buffer_desc *ticket_buffer, gss_name_t *client_name,
358                                gss_buffer_desc *authenticator_buff)
359 {
360     OM_uint32 major_status = 0, minor_status = 0, ret_flags;
361
362     /* Initialize autheticator buffer. */
363     authenticator_buff->length = 0;
364     authenticator_buff->value = NULL;
365
366     LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
367         ticket_buffer->length);
368
369     /*
370      * Try to accept the secondary context using the tocken in ticket_buffer.
371      * We don't care about the mechanisms used, nor for the time.
372      * We don't act as a proxy either.
373      */
374     major_status = gss_accept_sec_context( &minor_status, context,
375                                            server_creds, ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
376                                            client_name, NULL, authenticator_buff,
377                                            &ret_flags, NULL, NULL );
378
379     if (major_status != GSS_S_COMPLETE) {
380         log_status( "accept_sec_context", major_status, minor_status );
381         return 1;
382     }
383     log_ctx_flags( ret_flags );
384     return 0;
385 }
386
387
388 /* return 0 on success */
389 static int do_gss_auth(void *obj, char *ibuf, int ticket_len,
390                        char *rbuf, int *rbuflen, char *username, int ulen,
391                        struct session_info *sinfo )
392 {
393     OM_uint32 status = 0;
394     gss_name_t server_name, client_name;
395     gss_cred_id_t server_creds;
396     gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
397     gss_buffer_desc ticket_buffer, authenticator_buff;
398     int ret = 0;
399
400     /* import our principal name from afpd */
401     if (get_afpd_principal(obj, &server_name) != 0) {
402         return 1;
403     }
404     log_principal(server_name);
405
406     /* Now we have to acquire our credentials */
407     if ((ret = acquire_credentials (&server_name, &server_creds)))
408         goto cleanup_vars;
409
410     /*
411      * Try to accept the secondary context, using the ticket/token the
412      * client sent us. Ticket is stored at current ibuf position.
413      * Don't try to release ticket_buffer later, it points into ibuf!
414      */
415     ticket_buffer.length = ticket_len;
416     ticket_buffer.value = ibuf;
417
418     ret = accept_sec_context (&context_handle, server_creds, &ticket_buffer,
419                               &client_name, &authenticator_buff);
420
421     if (!ret) {
422         /* We succesfully acquired the secondary context, now get the
423            username for afpd and gss_wrap the sessionkey */
424         if ( 0 == (ret = get_client_username(username, ulen, &client_name)) ) {
425             ret = wrap_sessionkey(context_handle, sinfo);
426         }
427
428         if (!ret) {
429             /* FIXME: Is copying the authenticator really necessary?
430                Where is this documented? */
431             u_int16_t auth_len = htons( authenticator_buff.length );
432
433             /* copy the authenticator length into the reply buffer */
434             memcpy( rbuf, &auth_len, sizeof(auth_len) );
435             *rbuflen += sizeof(auth_len);
436             rbuf += sizeof(auth_len);
437
438             /* copy the authenticator value into the reply buffer */
439             memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
440             *rbuflen += authenticator_buff.length;
441         }
442
443         /* Clean up after ourselves */
444         gss_release_name( &status, &client_name );
445         if ( authenticator_buff.value)
446             gss_release_buffer( &status, &authenticator_buff );
447
448         gss_delete_sec_context( &status, &context_handle, NULL );
449     }
450     gss_release_cred( &status, &server_creds );
451
452 cleanup_vars:
453     if (server_name != GSS_C_NO_NAME)
454         gss_release_name( &status, &server_name );
455
456     return ret;
457 }
458
459 /* -------------------------- */
460 static int gss_login(void *obj, struct passwd **uam_pwd,
461                      char *ibuf, size_t ibuflen,
462                      char *rbuf, size_t *rbuflen)
463 {
464
465     u_int16_t  temp16;
466
467     *rbuflen = 0;
468
469     /* The reply contains a two-byte ID value - note
470      * that Apple's implementation seems to always return 1 as well
471      */
472     temp16 = htons( 1 );
473     memcpy(rbuf, &temp16, sizeof(temp16));
474     *rbuflen += sizeof(temp16);
475     return AFPERR_AUTHCONT;
476 }
477
478 static int gss_logincont(void *obj, struct passwd **uam_pwd,
479                          char *ibuf, size_t ibuflen,
480                          char *rbuf, size_t *rbuflen)
481 {
482     struct passwd *pwd = NULL;
483     u_int16_t login_id;
484     char *username;
485     u_int16_t ticket_len;
486     char *p;
487     int rblen;
488     size_t userlen;
489     struct session_info *sinfo;
490
491     /* Apple's AFP 3.1 documentation specifies that this command
492      * takes the following format:
493      * pad (byte)
494      * id returned in LoginExt response (u_int16_t)
495      * username (format unspecified) padded, when necessary, to end on an even boundary
496      * ticket length (u_int16_t)
497      * ticket
498      */
499
500     /* Observation of AFP clients in the wild indicate that the actual
501      * format of this request is as follows:
502      * pad (byte) [consumed before login_ext is called]
503      * ?? (byte) - always observed to be 0
504      * id returned in LoginExt response (u_int16_t)
505      * username, encoding unspecified, null terminated C string,
506      *   padded when the terminating null is an even numbered byte.
507      *   The packet is formated such that the username begins on an
508      *   odd numbered byte. Eg if the username is 3 characters and the
509      *   terminating null makes 4, expect to pad the the result.
510      *   The encoding of this string is unknown.
511      * ticket length (u_int16_t)
512      * ticket
513      */
514
515     rblen = *rbuflen = 0;
516
517     if (ibuflen < 1 +sizeof(login_id)) {
518         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
519         return AFPERR_PARAM;
520     }
521     ibuf++, ibuflen--; /* ?? */
522
523     /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
524     memcpy( &login_id, ibuf, sizeof(login_id) );
525     ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
526     login_id = ntohs( login_id );
527
528     /* get the username buffer from apfd */
529     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
530         return AFPERR_MISC;
531
532     /* get the session_info structure from afpd. We need the session key */
533     if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
534         return AFPERR_MISC;
535
536     if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
537         /* Should never happen. Most likely way too old afpd version */
538         LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set");
539         return AFPERR_MISC;
540     }
541
542     /* We skip past the 'username' parameter because all that matters is the ticket */
543     p = ibuf;
544     while( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
545     if (ibuflen < 4) {
546         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
547         return AFPERR_PARAM;
548     }
549
550     ibuf++, ibuflen--; /* null termination */
551
552     if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
553
554     LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
555
556     /* get the length of the ticket the client sends us */
557     memcpy(&ticket_len, ibuf, sizeof(ticket_len));
558     ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
559     ticket_len = ntohs( ticket_len );
560
561     /* a little bounds checking */
562     if (ticket_len > ibuflen) {
563         LOG(log_info, logtype_uams,
564             "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen);
565         return AFPERR_PARAM;
566     }
567
568     /* now try to authenticate */
569     if (!do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
570         /* We use the username we got back from the gssapi client_name.
571            Should we compare this to the username the client sent in the clear?
572            We know the character encoding of the cleartext username (UTF8), what
573            encoding is the gssapi name in? */
574         if((pwd = uam_getname( obj, username, userlen )) == NULL) {
575             LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
576             return AFPERR_NOTAUTH;
577         }
578         if (uam_checkuser(pwd) < 0) {
579             LOG(log_info, logtype_uams, "%s not a valid user", username);
580             return AFPERR_NOTAUTH;
581         }
582         *rbuflen = rblen;
583         *uam_pwd = pwd;
584         return AFP_OK;
585     } else {
586         LOG(log_info, logtype_uams, "do_gss_auth failed" );
587         *rbuflen = 0;
588         return AFPERR_MISC;
589     }
590 }
591
592 /*
593  * For the krb5 uam, this function only needs to return a two-byte
594  * login-session id. None of the data provided by the client up to this
595  * point is trustworthy as we'll have a signed ticket to parse in logincont.
596  */
597 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
598                          char *ibuf, size_t ibuflen,
599                          char *rbuf, size_t *rbuflen)
600 {
601     u_int16_t  temp16;
602
603     *rbuflen = 0;
604
605     /* The reply contains a two-byte ID value - note
606      * that Apple's implementation seems to always return 1 as well
607      */
608     temp16 = htons( 1 );
609     memcpy(rbuf, &temp16, sizeof(temp16));
610     *rbuflen += sizeof(temp16);
611     return AFPERR_AUTHCONT;
612 }
613
614 /* logout */
615 static void gss_logout() {
616 }
617
618 int uam_setup(const char *path)
619 {
620     if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
621                      gss_login, gss_logincont, gss_logout, gss_login_ext) < 0)
622         if (uam_register(UAM_SERVER_LOGIN, path, "Client Krb v2",
623                          gss_login, gss_logincont, gss_logout) < 0)
624             return -1;
625
626     return 0;
627 }
628
629 static void uam_cleanup(void)
630 {
631     uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
632 }
633
634 UAM_MODULE_EXPORT struct uam_export uams_gss = {
635     UAM_MODULE_SERVER,
636     UAM_MODULE_VERSION,
637     uam_setup, uam_cleanup
638 };