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