1/*++ 2/* NAME 3/* xsasl_cyrus_server 3 4/* SUMMARY 5/* Cyrus SASL server-side plug-in 6/* SYNOPSIS 7/* #include <xsasl_cyrus_server.h> 8/* 9/* XSASL_SERVER_IMPL *xsasl_cyrus_server_init(server_type, path_info) 10/* const char *server_type; 11/* const char *path_info; 12/* DESCRIPTION 13/* This module implements the Cyrus SASL server-side authentication 14/* plug-in. 15/* 16/* xsasl_cyrus_server_init() initializes the Cyrus SASL library and 17/* returns an implementation handle that can be used to generate 18/* SASL server instances. 19/* 20/* Arguments: 21/* .IP server_type 22/* The server type (cyrus). This argument is ignored, but it 23/* could be used when one implementation provides multiple 24/* variants. 25/* .IP path_info 26/* The base name of the SASL server configuration file (example: 27/* smtpd becomes /usr/lib/sasl2/smtpd.conf). 28/* DIAGNOSTICS 29/* Fatal: out of memory. 30/* 31/* Panic: interface violation. 32/* 33/* Other: the routines log a warning and return an error result 34/* as specified in xsasl_server(3). 35/* LICENSE 36/* .ad 37/* .fi 38/* The Secure Mailer license must be distributed with this software. 39/* AUTHOR(S) 40/* Initial implementation by: 41/* Till Franke 42/* SuSE Rhein/Main AG 43/* 65760 Eschborn, Germany 44/* 45/* Adopted by: 46/* Wietse Venema 47/* IBM T.J. Watson Research 48/* P.O. Box 704 49/* Yorktown Heights, NY 10598, USA 50/*--*/ 51 52/* System library. */ 53 54#include <sys_defs.h> 55#include <stdlib.h> 56#include <string.h> 57 58/* Utility library. */ 59 60#include <msg.h> 61#include <mymalloc.h> 62#include <name_mask.h> 63#include <stringops.h> 64 65/* Global library. */ 66 67#include <mail_params.h> 68 69/* Application-specific. */ 70 71#include <xsasl.h> 72#include <xsasl_cyrus.h> 73#include <xsasl_cyrus_common.h> 74 75#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) 76 77#include <sasl.h> 78#include <saslutil.h> 79 80#ifdef __APPLE_OS_X_SERVER__ 81/* temporary work around to <rdar://problem/8196059> */ 82#include <ldap.h> 83#endif 84 85/* 86 * Silly little macros. 87 */ 88#define STR(s) vstring_str(s) 89 90 /* 91 * Macros to handle API differences between SASLv1 and SASLv2. Specifics: 92 * 93 * The SASL_LOG_* constants were renamed in SASLv2. 94 * 95 * SASLv2's sasl_server_new takes two new parameters to specify local and 96 * remote IP addresses for auth mechs that use them. 97 * 98 * SASLv2's sasl_server_start and sasl_server_step no longer have the errstr 99 * parameter. 100 * 101 * SASLv2's sasl_decode64 function takes an extra parameter for the length of 102 * the output buffer. 103 * 104 * The other major change is that SASLv2 now takes more responsibility for 105 * deallocating memory that it allocates internally. Thus, some of the 106 * function parameters are now 'const', to make sure we don't try to free 107 * them too. This is dealt with in the code later on. 108 */ 109 110#if SASL_VERSION_MAJOR < 2 111/* SASL version 1.x */ 112#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \ 113 sasl_server_new(srv, fqdn, rlm, cb, secflags, pconn) 114#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \ 115 sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen, err) 116#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \ 117 sasl_server_step(conn, clin, clinlen, srvout, srvoutlen, err) 118#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ 119 sasl_decode64(in, inlen, out, outlen) 120typedef char *MECHANISM_TYPE; 121typedef unsigned MECHANISM_COUNT_TYPE; 122typedef char *SERVEROUT_TYPE; 123typedef void *VOID_SERVEROUT_TYPE; 124 125#endif 126 127#if SASL_VERSION_MAJOR >= 2 128/* SASL version > 2.x */ 129#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \ 130 sasl_server_new(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) 131#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \ 132 sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen) 133#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \ 134 sasl_server_step(conn, clin, clinlen, srvout, srvoutlen) 135#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ 136 sasl_decode64(in, inlen, out, outmaxlen, outlen) 137typedef const char *MECHANISM_TYPE; 138typedef int MECHANISM_COUNT_TYPE; 139typedef const char *SERVEROUT_TYPE; 140typedef const void *VOID_SERVEROUT_TYPE; 141 142#endif 143 144 /* 145 * The XSASL_CYRUS_SERVER object is derived from the generic XSASL_SERVER 146 * object. 147 */ 148typedef struct { 149 XSASL_SERVER xsasl; /* generic members, must be first */ 150 VSTREAM *stream; /* client-server connection */ 151 sasl_conn_t *sasl_conn; /* SASL context */ 152 VSTRING *decoded; /* decoded challenge or response */ 153 char *username; /* authenticated user */ 154 char *mechanism_list; /* applicable mechanisms */ 155} XSASL_CYRUS_SERVER; 156 157 /* 158 * Forward declarations. 159 */ 160static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *); 161static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *, 162 XSASL_SERVER_CREATE_ARGS *); 163static void xsasl_cyrus_server_free(XSASL_SERVER *); 164static int xsasl_cyrus_server_first(XSASL_SERVER *, const char *, 165 const char *, VSTRING *); 166static int xsasl_cyrus_server_next(XSASL_SERVER *, const char *, VSTRING *); 167static int xsasl_cyrus_server_set_security(XSASL_SERVER *, const char *); 168static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *); 169static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *); 170 171 /* 172 * SASL callback interface structure. These call-backs have no per-session 173 * context. 174 */ 175#define NO_CALLBACK_CONTEXT 0 176 177static sasl_callback_t callbacks[] = { 178 {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, NO_CALLBACK_CONTEXT}, 179 {SASL_CB_LIST_END, 0, 0} 180}; 181 182/* xsasl_cyrus_server_init - create implementation handle */ 183 184XSASL_SERVER_IMPL *xsasl_cyrus_server_init(const char *unused_server_type, 185 const char *path_info) 186{ 187 const char *myname = "xsasl_cyrus_server_init"; 188 XSASL_SERVER_IMPL *xp; 189 int sasl_status; 190 191#if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \ 192 || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19)) 193 int sasl_major; 194 int sasl_minor; 195 int sasl_step; 196 197 /* 198 * DLL hell guard. 199 */ 200 sasl_version_info((const char **) 0, (const char **) 0, 201 &sasl_major, &sasl_minor, 202 &sasl_step, (int *) 0); 203 if (sasl_major != SASL_VERSION_MAJOR 204#if 0 205 || sasl_minor != SASL_VERSION_MINOR 206 || sasl_step != SASL_VERSION_STEP 207#endif 208 ) { 209 msg_warn("incorrect SASL library version. " 210 "Postfix was built with include files from version %d.%d.%d, " 211 "but the run-time library version is %d.%d.%d", 212 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, 213 sasl_major, sasl_minor, sasl_step); 214 return (0); 215 } 216#endif 217 218 if (*var_cyrus_conf_path) { 219#ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */ 220 if (sasl_set_path(SASL_PATH_TYPE_CONFIG, 221 var_cyrus_conf_path) != SASL_OK) 222 msg_warn("failed to set Cyrus SASL configuration path: \"%s\"", 223 var_cyrus_conf_path); 224#else 225 msg_warn("%s is not empty, but setting the Cyrus SASL configuration " 226 "path is not supported with SASL library version %d.%d.%d", 227 VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR, 228 SASL_VERSION_MINOR, SASL_VERSION_STEP); 229#endif 230 } 231 232 /* 233 * Initialize the library: load SASL plug-in routines, etc. 234 */ 235 if (msg_verbose) 236 msg_info("%s: SASL config file is %s.conf", myname, path_info); 237 238#ifdef __APPLE_OS_X_SERVER__ 239/* temporary work around to <rdar://problem/8196059> */ 240LDAP *ldap_con = NULL; 241ldap_initialize(&ldap_con, "ldap://127.0.0.1"); 242 243 if ((sasl_status = sasl_server_init_alt(callbacks, path_info)) != SASL_OK) { 244#else 245 if ((sasl_status = sasl_server_init(callbacks, path_info)) != SASL_OK) { 246#endif /* __APPLE_OS_X_SERVER__ */ 247 msg_warn("SASL per-process initialization failed: %s", 248 xsasl_cyrus_strerror(sasl_status)); 249 return (0); 250 } 251 252 /* 253 * Return a generic XSASL_SERVER_IMPL object. We don't need to extend it 254 * with our own methods or data. 255 */ 256 xp = (XSASL_SERVER_IMPL *) mymalloc(sizeof(*xp)); 257 xp->create = xsasl_cyrus_server_create; 258 xp->done = xsasl_cyrus_server_done; 259 return (xp); 260} 261 262/* xsasl_cyrus_server_done - dispose of implementation */ 263 264static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *impl) 265{ 266 myfree((char *) impl); 267 sasl_done(); 268} 269 270/* xsasl_cyrus_server_create - create server instance */ 271 272static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *unused_impl, 273 XSASL_SERVER_CREATE_ARGS *args) 274{ 275 const char *myname = "xsasl_cyrus_server_create"; 276 char *server_address; 277 char *client_address; 278 sasl_conn_t *sasl_conn = 0; 279 XSASL_CYRUS_SERVER *server = 0; 280 int sasl_status; 281 282 if (msg_verbose) 283 msg_info("%s: SASL service=%s, realm=%s", 284 myname, args->service, args->user_realm ? 285 args->user_realm : "(null)"); 286 287 /* 288 * The optimizer will eliminate code duplication and/or dead code. 289 */ 290#define XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(x) \ 291 do { \ 292 if (server) { \ 293 xsasl_cyrus_server_free(&server->xsasl); \ 294 } else { \ 295 if (sasl_conn) \ 296 sasl_dispose(&sasl_conn); \ 297 } \ 298 return (x); \ 299 } while (0) 300 301 /* 302 * Set up a new server context. 303 */ 304#define NO_SECURITY_LAYERS (0) 305#define NO_SESSION_CALLBACKS ((sasl_callback_t *) 0) 306#define NO_AUTH_REALM ((char *) 0) 307 308#if SASL_VERSION_MAJOR >= 2 && defined(USE_SASL_IP_AUTH) 309 310 /* 311 * Get IP addresses of local and remote endpoints for SASL. 312 */ 313#error "USE_SASL_IP_AUTH is not implemented" 314 315#else 316 317 /* 318 * Don't give any IP address information to SASL. SASLv1 doesn't use it, 319 * and in SASLv2 this will disable any mechanisms that do. 320 */ 321 server_address = 0; 322 client_address = 0; 323#endif 324 325 if ((sasl_status = 326 SASL_SERVER_NEW(args->service, var_myhostname, 327 args->user_realm ? args->user_realm : NO_AUTH_REALM, 328 server_address, client_address, 329 NO_SESSION_CALLBACKS, NO_SECURITY_LAYERS, 330 &sasl_conn)) != SASL_OK) { 331 msg_warn("SASL per-connection server initialization: %s", 332 xsasl_cyrus_strerror(sasl_status)); 333 XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); 334 } 335 336 /* 337 * Extend the XSASL_SERVER object with our own data. We use long-lived 338 * conversion buffers rather than local variables to avoid memory leaks 339 * in case of read/write timeout or I/O error. 340 */ 341 server = (XSASL_CYRUS_SERVER *) mymalloc(sizeof(*server)); 342 server->xsasl.free = xsasl_cyrus_server_free; 343 server->xsasl.first = xsasl_cyrus_server_first; 344 server->xsasl.next = xsasl_cyrus_server_next; 345 server->xsasl.get_mechanism_list = xsasl_cyrus_server_get_mechanism_list; 346 server->xsasl.get_username = xsasl_cyrus_server_get_username; 347 server->stream = args->stream; 348 server->sasl_conn = sasl_conn; 349 server->decoded = vstring_alloc(20); 350 server->username = 0; 351 server->mechanism_list = 0; 352 353 if (xsasl_cyrus_server_set_security(&server->xsasl, args->security_options) 354 != XSASL_AUTH_OK) 355 XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); 356 357 return (&server->xsasl); 358} 359 360/* xsasl_cyrus_server_set_security - set security properties */ 361 362static int xsasl_cyrus_server_set_security(XSASL_SERVER *xp, 363 const char *sasl_opts_val) 364{ 365 XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; 366 sasl_security_properties_t sec_props; 367 int sasl_status; 368 369 /* 370 * Security options. Some information can be found in the sasl.h include 371 * file. 372 */ 373 memset(&sec_props, 0, sizeof(sec_props)); 374 sec_props.min_ssf = 0; 375 sec_props.max_ssf = 0; /* don't allow real SASL 376 * security layer */ 377 if (*sasl_opts_val == 0) { 378 sec_props.security_flags = 0; 379 } else { 380 sec_props.security_flags = 381 xsasl_cyrus_security_parse_opts(sasl_opts_val); 382 if (sec_props.security_flags == 0) { 383 msg_warn("bad per-session SASL security properties"); 384 return (XSASL_AUTH_FAIL); 385 } 386 } 387 sec_props.maxbufsize = 0; 388 sec_props.property_names = 0; 389 sec_props.property_values = 0; 390 391 if ((sasl_status = sasl_setprop(server->sasl_conn, SASL_SEC_PROPS, 392 &sec_props)) != SASL_OK) { 393 msg_warn("SASL per-connection security setup; %s", 394 xsasl_cyrus_strerror(sasl_status)); 395 return (XSASL_AUTH_FAIL); 396 } 397 return (XSASL_AUTH_OK); 398} 399 400/* xsasl_cyrus_server_get_mechanism_list - get available mechanisms */ 401 402static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *xp) 403{ 404 const char *myname = "xsasl_cyrus_server_get_mechanism_list"; 405 XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; 406 MECHANISM_TYPE mechanism_list; 407 MECHANISM_COUNT_TYPE mechanism_count; 408 int sasl_status; 409 410 /* 411 * Get the list of authentication mechanisms. 412 */ 413#define UNSUPPORTED_USER ((char *) 0) 414#define IGNORE_MECHANISM_LEN ((unsigned *) 0) 415 416 if ((sasl_status = sasl_listmech(server->sasl_conn, UNSUPPORTED_USER, 417 "", " ", "", 418 &mechanism_list, 419 IGNORE_MECHANISM_LEN, 420 &mechanism_count)) != SASL_OK) { 421 msg_warn("%s: %s", myname, xsasl_cyrus_strerror(sasl_status)); 422 return (0); 423 } 424 if (mechanism_count <= 0) { 425 msg_warn("%s: no applicable SASL mechanisms", myname); 426 return (0); 427 } 428 server->mechanism_list = mystrdup(mechanism_list); 429#if SASL_VERSION_MAJOR < 2 430 /* SASL version 1 doesn't free memory that it allocates. */ 431 free(mechanism_list); 432#endif 433 return (server->mechanism_list); 434} 435 436/* xsasl_cyrus_server_free - destroy server instance */ 437 438static void xsasl_cyrus_server_free(XSASL_SERVER *xp) 439{ 440 XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; 441 442 /* temporary work around to <rdar://problem/8196059> */ 443// sasl_dispose(&server->sasl_conn); 444 vstring_free(server->decoded); 445 if (server->username) 446 myfree(server->username); 447 if (server->mechanism_list) 448 myfree(server->mechanism_list); 449 myfree((char *) server); 450} 451 452/* xsasl_cyrus_server_auth_response - encode server first/next response */ 453 454static int xsasl_cyrus_server_auth_response(int sasl_status, 455 SERVEROUT_TYPE serverout, 456 unsigned serveroutlen, 457 VSTRING *reply) 458{ 459 const char *myname = "xsasl_cyrus_server_auth_response"; 460 unsigned enc_length; 461 unsigned enc_length_out; 462 463 /* 464 * Encode the server first/next non-error response; otherwise return the 465 * unencoded error text that corresponds to the SASL error status. 466 * 467 * Regarding the hairy expression below: output from sasl_encode64() comes 468 * in multiples of four bytes for each triple of input bytes, plus four 469 * bytes for any incomplete last triple, plus one byte for the null 470 * terminator. 471 */ 472 if (sasl_status == SASL_OK) { 473 vstring_strcpy(reply, ""); 474 return (XSASL_AUTH_DONE); 475 } else if (sasl_status == SASL_CONTINUE) { 476 if (msg_verbose) 477 msg_info("%s: uncoded server challenge: %.*s", 478 myname, (int) serveroutlen, serverout); 479 enc_length = ((serveroutlen + 2) / 3) * 4 + 1; 480 VSTRING_RESET(reply); /* Fix 200512 */ 481 VSTRING_SPACE(reply, enc_length); 482 if ((sasl_status = sasl_encode64(serverout, serveroutlen, 483 STR(reply), vstring_avail(reply), 484 &enc_length_out)) != SASL_OK) 485 msg_panic("%s: sasl_encode64 botch: %s", 486 myname, xsasl_cyrus_strerror(sasl_status)); 487 return (XSASL_AUTH_MORE); 488 } else { 489 if (sasl_status == SASL_NOUSER) /* privacy */ 490 sasl_status = SASL_BADAUTH; 491 vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); 492 return (XSASL_AUTH_FAIL); 493 } 494} 495 496/* xsasl_cyrus_server_first - per-session authentication */ 497 498int xsasl_cyrus_server_first(XSASL_SERVER *xp, const char *sasl_method, 499 const char *init_response, VSTRING *reply) 500{ 501 const char *myname = "xsasl_cyrus_server_first"; 502 XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; 503 char *dec_buffer; 504 unsigned dec_length; 505 unsigned reply_len; 506 unsigned serveroutlen; 507 int sasl_status; 508 SERVEROUT_TYPE serverout = 0; 509 int xsasl_status; 510 511#if SASL_VERSION_MAJOR < 2 512 const char *errstr = 0; 513 514#endif 515 516#define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3)) 517 518 if (msg_verbose) 519 msg_info("%s: sasl_method %s%s%s", myname, sasl_method, 520 IFELSE(init_response, ", init_response ", ""), 521 IFELSE(init_response, init_response, "")); 522 523 /* 524 * SASL authentication protocol start-up. Process any initial client 525 * response that was sent along in the AUTH command. 526 */ 527 if (init_response) { 528 reply_len = strlen(init_response); 529 VSTRING_RESET(server->decoded); /* Fix 200512 */ 530 VSTRING_SPACE(server->decoded, reply_len); 531 if ((sasl_status = SASL_DECODE64(init_response, reply_len, 532 dec_buffer = STR(server->decoded), 533 vstring_avail(server->decoded), 534 &dec_length)) != SASL_OK) { 535 vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); 536 return (XSASL_AUTH_FORM); 537 } 538 if (msg_verbose) 539 msg_info("%s: decoded initial response %s", myname, dec_buffer); 540 } else { 541 dec_buffer = 0; 542 dec_length = 0; 543 } 544 sasl_status = SASL_SERVER_START(server->sasl_conn, sasl_method, dec_buffer, 545 dec_length, &serverout, 546 &serveroutlen, &errstr); 547 xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, 548 serveroutlen, reply); 549#if SASL_VERSION_MAJOR < 2 550 /* SASL version 1 doesn't free memory that it allocates. */ 551 free(serverout); 552#endif 553 return (xsasl_status); 554} 555 556/* xsasl_cyrus_server_next - continue authentication */ 557 558static int xsasl_cyrus_server_next(XSASL_SERVER *xp, const char *request, 559 VSTRING *reply) 560{ 561 const char *myname = "xsasl_cyrus_server_next"; 562 XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; 563 unsigned dec_length; 564 unsigned request_len; 565 unsigned serveroutlen; 566 int sasl_status; 567 SERVEROUT_TYPE serverout = 0; 568 int xsasl_status; 569 570#if SASL_VERSION_MAJOR < 2 571 const char *errstr = 0; 572 573#endif 574 575 request_len = strlen(request); 576 VSTRING_RESET(server->decoded); /* Fix 200512 */ 577 VSTRING_SPACE(server->decoded, request_len); 578 if ((sasl_status = SASL_DECODE64(request, request_len, 579 STR(server->decoded), 580 vstring_avail(server->decoded), 581 &dec_length)) != SASL_OK) { 582 vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); 583 return (XSASL_AUTH_FORM); 584 } 585 if (msg_verbose) 586 msg_info("%s: decoded response: %.*s", 587 myname, (int) dec_length, STR(server->decoded)); 588 sasl_status = SASL_SERVER_STEP(server->sasl_conn, STR(server->decoded), 589 dec_length, &serverout, 590 &serveroutlen, &errstr); 591 xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, 592 serveroutlen, reply); 593#if SASL_VERSION_MAJOR < 2 594 /* SASL version 1 doesn't free memory that it allocates. */ 595 free(serverout); 596#endif 597 return (xsasl_status); 598} 599 600/* xsasl_cyrus_server_get_username - get authenticated username */ 601 602static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *xp) 603{ 604 const char *myname = "xsasl_cyrus_server_get_username"; 605 XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; 606 VOID_SERVEROUT_TYPE serverout = 0; 607 int sasl_status; 608 609 /* 610 * XXX Do not free(serverout). 611 */ 612 sasl_status = sasl_getprop(server->sasl_conn, SASL_USERNAME, &serverout); 613 if (sasl_status != SASL_OK || serverout == 0) { 614 msg_warn("%s: sasl_getprop SASL_USERNAME botch: %s", 615 myname, xsasl_cyrus_strerror(sasl_status)); 616 return (0); 617 } 618 if (server->username) 619 myfree(server->username); 620 server->username = mystrdup(serverout); 621 printable(server->username, '?'); 622 return (server->username); 623} 624 625#endif 626