static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *xp) { const char *myname = "xsasl_cyrus_server_get_mechanism_list"; XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; MECHANISM_TYPE mechanism_list; MECHANISM_COUNT_TYPE mechanism_count; int sasl_status; /* * Get the list of authentication mechanisms. */ #define UNSUPPORTED_USER ((char *) 0) #define IGNORE_MECHANISM_LEN ((unsigned *) 0) if ((sasl_status = sasl_listmech(server->sasl_conn, UNSUPPORTED_USER, "", " ", "", &mechanism_list, IGNORE_MECHANISM_LEN, &mechanism_count)) != SASL_OK) { msg_warn("%s: %s", myname, xsasl_cyrus_strerror(sasl_status)); return (0); } if (mechanism_count <= 0) { msg_warn("%s: no applicable SASL mechanisms", myname); return (0); } server->mechanism_list = mystrdup(mechanism_list); #if SASL_VERSION_MAJOR < 2 /* SASL version 1 doesn't free memory that it allocates. */ free(mechanism_list); #endif return (server->mechanism_list); }
int xsasl_cyrus_server_first(XSASL_SERVER *xp, const char *sasl_method, const char *init_response, VSTRING *reply) { const char *myname = "xsasl_cyrus_server_first"; XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; char *dec_buffer; unsigned dec_length; unsigned reply_len; unsigned serveroutlen; int sasl_status; SERVEROUT_TYPE serverout = 0; int xsasl_status; #if SASL_VERSION_MAJOR < 2 const char *errstr = 0; #endif #define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3)) if (msg_verbose) msg_info("%s: sasl_method %s%s%s", myname, sasl_method, IFELSE(init_response, ", init_response ", ""), IFELSE(init_response, init_response, "")); /* * SASL authentication protocol start-up. Process any initial client * response that was sent along in the AUTH command. */ if (init_response) { reply_len = strlen(init_response); VSTRING_RESET(server->decoded); /* Fix 200512 */ VSTRING_SPACE(server->decoded, reply_len); if ((sasl_status = SASL_DECODE64(init_response, reply_len, dec_buffer = STR(server->decoded), vstring_avail(server->decoded), &dec_length)) != SASL_OK) { vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_FORM); } if (msg_verbose) msg_info("%s: decoded initial response %s", myname, dec_buffer); } else { dec_buffer = 0; dec_length = 0; } sasl_status = SASL_SERVER_START(server->sasl_conn, sasl_method, dec_buffer, dec_length, &serverout, &serveroutlen, &errstr); xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, serveroutlen, reply); #if SASL_VERSION_MAJOR < 2 /* SASL version 1 doesn't free memory that it allocates. */ free(serverout); #endif return (xsasl_status); }
static int xsasl_cyrus_server_auth_response(int sasl_status, SERVEROUT_TYPE serverout, unsigned serveroutlen, VSTRING *reply) { const char *myname = "xsasl_cyrus_server_auth_response"; unsigned enc_length; unsigned enc_length_out; /* * Encode the server first/next non-error response; otherwise return the * unencoded error text that corresponds to the SASL error status. * * Regarding the hairy expression below: output from sasl_encode64() comes * in multiples of four bytes for each triple of input bytes, plus four * bytes for any incomplete last triple, plus one byte for the null * terminator. */ if (sasl_status == SASL_OK) { vstring_strcpy(reply, ""); return (XSASL_AUTH_DONE); } else if (sasl_status == SASL_CONTINUE) { if (msg_verbose) msg_info("%s: uncoded server challenge: %.*s", myname, (int) serveroutlen, serverout); enc_length = ((serveroutlen + 2) / 3) * 4 + 1; VSTRING_RESET(reply); /* Fix 200512 */ VSTRING_SPACE(reply, enc_length); if ((sasl_status = sasl_encode64(serverout, serveroutlen, STR(reply), vstring_avail(reply), &enc_length_out)) != SASL_OK) msg_panic("%s: sasl_encode64 botch: %s", myname, xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_MORE); } else { if (sasl_status == SASL_NOUSER) /* privacy */ sasl_status = SASL_BADAUTH; vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_FAIL); } }
static int xsasl_cyrus_server_next(XSASL_SERVER *xp, const char *request, VSTRING *reply) { const char *myname = "xsasl_cyrus_server_next"; XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; unsigned dec_length; unsigned request_len; unsigned serveroutlen; int sasl_status; SERVEROUT_TYPE serverout = 0; int xsasl_status; #if SASL_VERSION_MAJOR < 2 const char *errstr = 0; #endif request_len = strlen(request); VSTRING_RESET(server->decoded); /* Fix 200512 */ VSTRING_SPACE(server->decoded, request_len); if ((sasl_status = SASL_DECODE64(request, request_len, STR(server->decoded), vstring_avail(server->decoded), &dec_length)) != SASL_OK) { vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_FORM); } if (msg_verbose) msg_info("%s: decoded response: %.*s", myname, (int) dec_length, STR(server->decoded)); sasl_status = SASL_SERVER_STEP(server->sasl_conn, STR(server->decoded), dec_length, &serverout, &serveroutlen, &errstr); xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, serveroutlen, reply); #if SASL_VERSION_MAJOR < 2 /* SASL version 1 doesn't free memory that it allocates. */ free(serverout); #endif return (xsasl_status); }
static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *xp) { const char *myname = "xsasl_cyrus_server_get_username"; XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; VOID_SERVEROUT_TYPE serverout = 0; int sasl_status; /* * XXX Do not free(serverout). */ sasl_status = sasl_getprop(server->sasl_conn, SASL_USERNAME, &serverout); if (sasl_status != SASL_OK || serverout == 0) { msg_warn("%s: sasl_getprop SASL_USERNAME botch: %s", myname, xsasl_cyrus_strerror(sasl_status)); return (0); } if (server->username) myfree(server->username); server->username = mystrdup(serverout); printable(server->username, '?'); return (server->username); }
static int xsasl_cyrus_server_set_security(XSASL_SERVER *xp, const char *sasl_opts_val) { XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; sasl_security_properties_t sec_props; int sasl_status; /* * Security options. Some information can be found in the sasl.h include * file. */ memset(&sec_props, 0, sizeof(sec_props)); sec_props.min_ssf = 0; sec_props.max_ssf = 0; /* don't allow real SASL * security layer */ if (*sasl_opts_val == 0) { sec_props.security_flags = 0; } else { sec_props.security_flags = xsasl_cyrus_security_parse_opts(sasl_opts_val); if (sec_props.security_flags == 0) { msg_warn("bad per-session SASL security properties"); return (XSASL_AUTH_FAIL); } } sec_props.maxbufsize = 0; sec_props.property_names = 0; sec_props.property_values = 0; if ((sasl_status = sasl_setprop(server->sasl_conn, SASL_SEC_PROPS, &sec_props)) != SASL_OK) { msg_warn("SASL per-connection security setup; %s", xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_FAIL); } return (XSASL_AUTH_OK); }
static int xsasl_cyrus_client_set_security(XSASL_CLIENT *xp, const char *sasl_opts_val) { XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; sasl_security_properties_t sec_props; int sasl_status; /* * Per-session security properties. XXX This routine is not sufficiently * documented. What is the purpose of all this? */ memset(&sec_props, 0, sizeof(sec_props)); sec_props.min_ssf = 0; sec_props.max_ssf = 0; /* don't allow real SASL * security layer */ if (*sasl_opts_val == 0) { sec_props.security_flags = 0; } else { sec_props.security_flags = xsasl_cyrus_security_parse_opts(sasl_opts_val); if (sec_props.security_flags == 0) { msg_warn("bad per-session SASL security properties"); return (XSASL_AUTH_FAIL); } } sec_props.maxbufsize = 0; sec_props.property_names = 0; sec_props.property_values = 0; if ((sasl_status = sasl_setprop(client->sasl_conn, SASL_SEC_PROPS, &sec_props)) != SASL_OK) { msg_warn("set per-session SASL security properties: %s", xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_FAIL); } return (XSASL_AUTH_OK); }
static int xsasl_cyrus_client_next(XSASL_CLIENT *xp, const char *server_reply, VSTRING *client_reply) { const char *myname = "xsasl_cyrus_client_next"; XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; unsigned enc_length; unsigned enc_length_out; CLIENTOUT_TYPE clientout; unsigned clientoutlen; unsigned serverinlen; int sasl_status; /* * Process a server challenge. */ serverinlen = strlen(server_reply); VSTRING_RESET(client->decoded); /* Fix 200512 */ VSTRING_SPACE(client->decoded, serverinlen); if ((sasl_status = SASL_DECODE64(server_reply, serverinlen, STR(client->decoded), vstring_avail(client->decoded), &enc_length)) != SASL_OK) { vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_FORM); } if (msg_verbose) msg_info("%s: decoded challenge: %.*s", myname, (int) enc_length, STR(client->decoded)); sasl_status = sasl_client_step(client->sasl_conn, STR(client->decoded), enc_length, NO_SASL_INTERACTION, &clientout, &clientoutlen); if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) { vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_FAIL); } /* * Send a client response. */ if (clientoutlen > 0) { if (msg_verbose) msg_info("%s: uncoded client response %.*s", myname, (int) clientoutlen, clientout); enc_length = ENCODE64_LENGTH(clientoutlen) + 1; VSTRING_RESET(client_reply); /* Fix 200512 */ VSTRING_SPACE(client_reply, enc_length); if ((sasl_status = sasl_encode64(clientout, clientoutlen, STR(client_reply), vstring_avail(client_reply), &enc_length_out)) != SASL_OK) msg_panic("%s: sasl_encode64 botch: %s", myname, xsasl_cyrus_strerror(sasl_status)); #if SASL_VERSION_MAJOR < 2 /* SASL version 1 doesn't free memory that it allocates. */ free(clientout); #endif } else { /* XXX Can't happen. */ vstring_strcpy(client_reply, ""); } return (XSASL_AUTH_OK); }
static int xsasl_cyrus_client_first(XSASL_CLIENT *xp, const char *mechanism_list, const char *username, const char *password, const char **mechanism, VSTRING *init_resp) { const char *myname = "xsasl_cyrus_client_first"; XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; unsigned enc_length; unsigned enc_length_out; CLIENTOUT_TYPE clientout; unsigned clientoutlen; int sasl_status; #define NO_SASL_SECRET 0 #define NO_SASL_INTERACTION 0 /* * Save the username and password for the call-backs. */ if (client->username) myfree(client->username); client->username = mystrdup(username); if (client->password) myfree(client->password); client->password = mystrdup(password); /* * Start the client side authentication protocol. */ sasl_status = SASL_CLIENT_START((sasl_conn_t *) client->sasl_conn, mechanism_list, NO_SASL_SECRET, NO_SASL_INTERACTION, &clientout, &clientoutlen, mechanism); if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) { vstring_strcpy(init_resp, xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_FAIL); } /* * Generate the AUTH command and the optional initial client response. * sasl_encode64() produces four bytes for each complete or incomplete * triple of input bytes. Allocate an extra byte for string termination. */ #define ENCODE64_LENGTH(n) ((((n) + 2) / 3) * 4) if (clientoutlen > 0) { if (msg_verbose) { escape(client->decoded, clientout, clientoutlen); msg_info("%s: uncoded initial reply: %s", myname, STR(client->decoded)); } enc_length = ENCODE64_LENGTH(clientoutlen) + 1; VSTRING_RESET(init_resp); /* Fix 200512 */ VSTRING_SPACE(init_resp, enc_length); if ((sasl_status = sasl_encode64(clientout, clientoutlen, STR(init_resp), vstring_avail(init_resp), &enc_length_out)) != SASL_OK) msg_panic("%s: sasl_encode64 botch: %s", myname, xsasl_cyrus_strerror(sasl_status)); VSTRING_AT_OFFSET(init_resp, enc_length_out); /* XXX */ #if SASL_VERSION_MAJOR < 2 /* SASL version 1 doesn't free memory that it allocates. */ free(clientout); #endif } else { vstring_strcpy(init_resp, ""); } return (XSASL_AUTH_OK); }
XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *unused_impl, XSASL_CLIENT_CREATE_ARGS *args) { XSASL_CYRUS_CLIENT *client = 0; static sasl_callback_t callbacks[] = { {SASL_CB_USER, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0}, {SASL_CB_AUTHNAME, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0}, {SASL_CB_PASS, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_passwd, 0}, {SASL_CB_LIST_END, 0, 0} }; sasl_conn_t *sasl_conn = 0; sasl_callback_t *custom_callbacks = 0; sasl_callback_t *cp; int sasl_status; /* * The optimizer will eliminate code duplication and/or dead code. */ #define XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(x) \ do { \ if (client) { \ xsasl_cyrus_client_free(&client->xsasl); \ } else { \ if (custom_callbacks) \ myfree((void *) custom_callbacks); \ if (sasl_conn) \ sasl_dispose(&sasl_conn); \ } \ return (x); \ } while (0) /* * Per-session initialization. Provide each session with its own callback * context. */ #define NULL_SECFLAGS 0 custom_callbacks = (sasl_callback_t *) mymalloc(sizeof(callbacks)); memcpy((void *) custom_callbacks, callbacks, sizeof(callbacks)); #define NULL_SERVER_ADDR ((char *) 0) #define NULL_CLIENT_ADDR ((char *) 0) if ((sasl_status = SASL_CLIENT_NEW(args->service, args->server_name, NULL_CLIENT_ADDR, NULL_SERVER_ADDR, var_cyrus_sasl_authzid ? custom_callbacks : custom_callbacks + 1, NULL_SECFLAGS, &sasl_conn)) != SASL_OK) { msg_warn("per-session SASL client initialization: %s", xsasl_cyrus_strerror(sasl_status)); XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0); } /* * Extend the XSASL_CLIENT object with our own state. We use long-lived * conversion buffers rather than local variables to avoid memory leaks * in case of read/write timeout or I/O error. * * XXX If we enable SASL encryption, there needs to be a way to inform the * application, so that they can turn off connection caching, refuse * STARTTLS, etc. */ client = (XSASL_CYRUS_CLIENT *) mymalloc(sizeof(*client)); client->xsasl.free = xsasl_cyrus_client_free; client->xsasl.first = xsasl_cyrus_client_first; client->xsasl.next = xsasl_cyrus_client_next; client->stream = args->stream; client->sasl_conn = sasl_conn; client->callbacks = custom_callbacks; client->decoded = vstring_alloc(20); client->username = 0; client->password = 0; for (cp = custom_callbacks; cp->id != SASL_CB_LIST_END; cp++) cp->context = (void *) client; if (xsasl_cyrus_client_set_security(&client->xsasl, args->security_options) != XSASL_AUTH_OK) XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0); return (&client->xsasl); }
XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(const char *unused_client_type, const char *unused_path_info) { XSASL_CLIENT_IMPL *xp; int sasl_status; /* * Global callbacks. These have no per-session context. */ static sasl_callback_t callbacks[] = { {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, 0}, {SASL_CB_LIST_END, 0, 0} }; #if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \ || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19)) int sasl_major; int sasl_minor; int sasl_step; /* * DLL hell guard. */ sasl_version_info((const char **) 0, (const char **) 0, &sasl_major, &sasl_minor, &sasl_step, (int *) 0); if (sasl_major != SASL_VERSION_MAJOR #if 0 || sasl_minor != SASL_VERSION_MINOR || sasl_step != SASL_VERSION_STEP #endif ) { msg_warn("incorrect SASL library version. " "Postfix was built with include files from version %d.%d.%d, " "but the run-time library version is %d.%d.%d", SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, sasl_major, sasl_minor, sasl_step); return (0); } #endif if (*var_cyrus_conf_path) { #ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */ if (sasl_set_path(SASL_PATH_TYPE_CONFIG, var_cyrus_conf_path) != SASL_OK) msg_warn("failed to set Cyrus SASL configuration path: \"%s\"", var_cyrus_conf_path); #else msg_warn("%s is not empty, but setting the Cyrus SASL configuration " "path is not supported with SASL library version %d.%d.%d", VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP); #endif } /* * Initialize the SASL library. */ if ((sasl_status = sasl_client_init(callbacks)) != SASL_OK) { msg_warn("SASL library initialization error: %s", xsasl_cyrus_strerror(sasl_status)); return (0); } /* * Return a generic XSASL_CLIENT_IMPL object. We don't need to extend it * with our own methods or data. */ xp = (XSASL_CLIENT_IMPL *) mymalloc(sizeof(*xp)); xp->create = xsasl_cyrus_client_create; xp->done = xsasl_cyrus_client_done; return (xp); }
static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *unused_impl, XSASL_SERVER_CREATE_ARGS *args) { const char *myname = "xsasl_cyrus_server_create"; char *server_address; char *client_address; sasl_conn_t *sasl_conn = 0; XSASL_CYRUS_SERVER *server = 0; int sasl_status; if (msg_verbose) msg_info("%s: SASL service=%s, realm=%s", myname, args->service, args->user_realm ? args->user_realm : "(null)"); /* * The optimizer will eliminate code duplication and/or dead code. */ #define XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(x) \ do { \ if (server) { \ xsasl_cyrus_server_free(&server->xsasl); \ } else { \ if (sasl_conn) \ sasl_dispose(&sasl_conn); \ } \ return (x); \ } while (0) /* * Set up a new server context. */ #define NO_SECURITY_LAYERS (0) #define NO_SESSION_CALLBACKS ((sasl_callback_t *) 0) #define NO_AUTH_REALM ((char *) 0) #if SASL_VERSION_MAJOR >= 2 && defined(USE_SASL_IP_AUTH) /* * Get IP addresses of local and remote endpoints for SASL. */ #error "USE_SASL_IP_AUTH is not implemented" #else /* * Don't give any IP address information to SASL. SASLv1 doesn't use it, * and in SASLv2 this will disable any mechanisms that do. */ server_address = 0; client_address = 0; #endif if ((sasl_status = SASL_SERVER_NEW(args->service, var_myhostname, args->user_realm ? args->user_realm : NO_AUTH_REALM, server_address, client_address, NO_SESSION_CALLBACKS, NO_SECURITY_LAYERS, &sasl_conn)) != SASL_OK) { msg_warn("SASL per-connection server initialization: %s", xsasl_cyrus_strerror(sasl_status)); XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); } /* * Extend the XSASL_SERVER object with our own data. We use long-lived * conversion buffers rather than local variables to avoid memory leaks * in case of read/write timeout or I/O error. */ server = (XSASL_CYRUS_SERVER *) mymalloc(sizeof(*server)); server->xsasl.free = xsasl_cyrus_server_free; server->xsasl.first = xsasl_cyrus_server_first; server->xsasl.next = xsasl_cyrus_server_next; server->xsasl.get_mechanism_list = xsasl_cyrus_server_get_mechanism_list; server->xsasl.get_username = xsasl_cyrus_server_get_username; server->stream = args->stream; server->sasl_conn = sasl_conn; server->decoded = vstring_alloc(20); server->username = 0; server->mechanism_list = 0; if (xsasl_cyrus_server_set_security(&server->xsasl, args->security_options) != XSASL_AUTH_OK) XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); return (&server->xsasl); }
XSASL_SERVER_IMPL *xsasl_cyrus_server_init(const char *unused_server_type, const char *path_info) { const char *myname = "xsasl_cyrus_server_init"; XSASL_SERVER_IMPL *xp; int sasl_status; #if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \ || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19)) int sasl_major; int sasl_minor; int sasl_step; /* * DLL hell guard. */ sasl_version_info((const char **) 0, (const char **) 0, &sasl_major, &sasl_minor, &sasl_step, (int *) 0); if (sasl_major != SASL_VERSION_MAJOR #if 0 || sasl_minor != SASL_VERSION_MINOR || sasl_step != SASL_VERSION_STEP #endif ) { msg_warn("incorrect SASL library version. " "Postfix was built with include files from version %d.%d.%d, " "but the run-time library version is %d.%d.%d", SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, sasl_major, sasl_minor, sasl_step); return (0); } #endif if (*var_cyrus_conf_path) { #ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */ if (sasl_set_path(SASL_PATH_TYPE_CONFIG, var_cyrus_conf_path) != SASL_OK) msg_warn("failed to set Cyrus SASL configuration path: \"%s\"", var_cyrus_conf_path); #else msg_warn("%s is not empty, but setting the Cyrus SASL configuration " "path is not supported with SASL library version %d.%d.%d", VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP); #endif } /* * Initialize the library: load SASL plug-in routines, etc. */ if (msg_verbose) msg_info("%s: SASL config file is %s.conf", myname, path_info); if ((sasl_status = sasl_server_init(callbacks, path_info)) != SASL_OK) { msg_warn("SASL per-process initialization failed: %s", xsasl_cyrus_strerror(sasl_status)); return (0); } /* * Return a generic XSASL_SERVER_IMPL object. We don't need to extend it * with our own methods or data. */ xp = (XSASL_SERVER_IMPL *) mymalloc(sizeof(*xp)); xp->create = xsasl_cyrus_server_create; xp->done = xsasl_cyrus_server_done; return (xp); }