static void replica_connect(const char *channel) { int wait; struct protoent *proto; sasl_callback_t *cb; int timeout; const char *port, *auth_status = NULL; cb = mysasl_callbacks(NULL, get_config(channel, "sync_authname"), get_config(channel, "sync_realm"), get_config(channel, "sync_password")); /* get the right port */ port = get_config(channel, "sync_port"); if (port) { imap_csync_protocol.service = port; csync_protocol.service = port; } for (wait = 15;; wait *= 2) { sync_backend = backend_connect(sync_backend, servername, &imap_csync_protocol, "", cb, &auth_status, (verbose > 1 ? fileno(stderr) : -1)); if (sync_backend) { if (sync_backend->capability & CAPA_REPLICATION) { /* attach our IMAP tag buffer to our protstreams as userdata */ sync_backend->in->userdata = sync_backend->out->userdata = &tagbuf; break; } else { backend_disconnect(sync_backend); sync_backend = NULL; } } sync_backend = backend_connect(sync_backend, servername, &csync_protocol, "", cb, NULL, (verbose > 1 ? fileno(stderr) : -1)); if (sync_backend || auth_status || connect_once || wait > 1000) break; fprintf(stderr, "Can not connect to server '%s', retrying in %d seconds\n", servername, wait); sleep(wait); } free_callbacks(cb); cb = NULL; if (!sync_backend) { fprintf(stderr, "Can not connect to server '%s'\n", servername); syslog(LOG_ERR, "Can not connect to server '%s'", servername); _exit(1); } /* Disable Nagle's Algorithm => increase throughput * * http://en.wikipedia.org/wiki/Nagle's_algorithm */ if (servername[0] != '/') { if (sync_backend->sock >= 0 && (proto = getprotobyname("tcp")) != NULL) { int on = 1; if (setsockopt(sync_backend->sock, proto->p_proto, TCP_NODELAY, (void *) &on, sizeof(on)) != 0) { syslog(LOG_ERR, "unable to setsocketopt(TCP_NODELAY): %m"); } /* turn on TCP keepalive if set */ if (config_getswitch(IMAPOPT_TCP_KEEPALIVE)) { int r; int optval = 1; socklen_t optlen = sizeof(optval); struct protoent *proto = getprotobyname("TCP"); r = setsockopt(sync_backend->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen); if (r < 0) { syslog(LOG_ERR, "unable to setsocketopt(SO_KEEPALIVE): %m"); } #ifdef TCP_KEEPCNT optval = config_getint(IMAPOPT_TCP_KEEPALIVE_CNT); if (optval) { r = setsockopt(sync_backend->sock, proto->p_proto, TCP_KEEPCNT, &optval, optlen); if (r < 0) { syslog(LOG_ERR, "unable to setsocketopt(TCP_KEEPCNT): %m"); } } #endif #ifdef TCP_KEEPIDLE optval = config_getint(IMAPOPT_TCP_KEEPALIVE_IDLE); if (optval) { r = setsockopt(sync_backend->sock, proto->p_proto, TCP_KEEPIDLE, &optval, optlen); if (r < 0) { syslog(LOG_ERR, "unable to setsocketopt(TCP_KEEPIDLE): %m"); } } #endif #ifdef TCP_KEEPINTVL optval = config_getint(IMAPOPT_TCP_KEEPALIVE_INTVL); if (optval) { r = setsockopt(sync_backend->sock, proto->p_proto, TCP_KEEPINTVL, &optval, optlen); if (r < 0) { syslog(LOG_ERR, "unable to setsocketopt(TCP_KEEPINTVL): %m"); } } #endif } } else { syslog(LOG_ERR, "unable to getprotobyname(\"tcp\"): %m"); } } #ifdef HAVE_ZLIB /* Does the backend support compression? */ if (CAPA(sync_backend, CAPA_COMPRESS)) { prot_printf(sync_backend->out, "%s\r\n", sync_backend->prot->u.std.compress_cmd.cmd); prot_flush(sync_backend->out); if (sync_parse_response("COMPRESS", sync_backend->in, NULL)) { if (do_compress) fatal("Failed to enable compression, aborting", EC_SOFTWARE); syslog(LOG_NOTICE, "Failed to enable compression, continuing uncompressed"); } else { prot_setcompress(sync_backend->in); prot_setcompress(sync_backend->out); } } else if (do_compress) fatal("Backend does not support compression, aborting", EC_SOFTWARE); #endif /* links to sockets */ sync_in = sync_backend->in; sync_out = sync_backend->out; if (verbose > 1) { prot_setlog(sync_in, fileno(stderr)); prot_setlog(sync_out, fileno(stderr)); } /* Set inactivity timer */ timeout = config_getint(IMAPOPT_SYNC_TIMEOUT); if (timeout < 3) timeout = 3; prot_settimeout(sync_in, timeout); /* Force use of LITERAL+ so we don't need two way communications */ prot_setisclient(sync_in, 1); prot_setisclient(sync_out, 1); }
static int backend_authenticate(struct backend *s, const char *userid, sasl_callback_t *cb, const char **status) { struct protocol_t *prot = s->prot; int r; char *mechlist; sasl_security_properties_t secprops = { 0, 0xFF, PROT_BUFSIZE, 0, NULL, NULL }; /* default secprops */ struct sockaddr_storage saddr_l, saddr_r; char remoteip[60], localip[60]; socklen_t addrsize; int local_cb = 0; char buf[2048], optstr[128], *p; const char *mech_conf, *pass; /* set the IP addresses */ addrsize=sizeof(struct sockaddr_storage); if (getpeername(s->sock, (struct sockaddr *)&saddr_r, &addrsize) != 0) return SASL_FAIL; if(iptostring((struct sockaddr *)&saddr_r, addrsize, remoteip, 60) != 0) return SASL_FAIL; addrsize=sizeof(struct sockaddr_storage); if (getsockname(s->sock, (struct sockaddr *)&saddr_l, &addrsize)!=0) return SASL_FAIL; if(iptostring((struct sockaddr *)&saddr_l, addrsize, localip, 60) != 0) return SASL_FAIL; if (!cb) { local_cb = 1; strlcpy(optstr, s->hostname, sizeof(optstr)); p = strchr(optstr, '.'); if (p) *p = '\0'; strlcat(optstr, "_password", sizeof(optstr)); pass = config_getoverflowstring(optstr, NULL); if(!pass) pass = config_getstring(IMAPOPT_PROXY_PASSWORD); cb = mysasl_callbacks(userid, config_getstring(IMAPOPT_PROXY_AUTHNAME), config_getstring(IMAPOPT_PROXY_REALM), pass); } /* Require proxying if we have an "interesting" userid (authzid) */ r = sasl_client_new(prot->sasl_service, s->hostname, localip, remoteip, cb, (userid && *userid ? SASL_NEED_PROXY : 0) | (prot->sasl_cmd.parse_success ? SASL_SUCCESS_DATA : 0), &s->saslconn); if (r != SASL_OK) goto out; r = sasl_setprop(s->saslconn, SASL_SEC_PROPS, &secprops); if (r != SASL_OK) goto out; /* Get SASL mechanism list. We can force a particular mechanism using a <shorthost>_mechs option */ strcpy(buf, s->hostname); p = strchr(buf, '.'); if (p) *p = '\0'; strcat(buf, "_mechs"); mech_conf = config_getoverflowstring(buf, NULL); if (!mech_conf) { mech_conf = config_getstring(IMAPOPT_FORCE_SASL_CLIENT_MECH); } mechlist = backend_get_cap_params(s, CAPA_AUTH); do { /* If we have a mech_conf, use it */ if (mech_conf && mechlist) { char *conf = xstrdup(mech_conf); char *newmechlist = intersect_mechlists( conf, mechlist ); if ( newmechlist == NULL ) { syslog( LOG_INFO, "%s did not offer %s", s->hostname, mech_conf ); } free(conf); free(mechlist); mechlist = newmechlist; } if (mechlist) { /* we now do the actual SASL exchange */ saslclient(s->saslconn, &prot->sasl_cmd, mechlist, s->in, s->out, &r, status); /* garbage collect */ free(mechlist); mechlist = NULL; } else r = SASL_NOMECH; /* If we don't have a usable mech, do TLS and try again */ } while (r == SASL_NOMECH && CAPA(s, CAPA_STARTTLS) && do_starttls(s) != -1 && (mechlist = backend_get_cap_params(s, CAPA_AUTH))); if (r == SASL_OK) { prot_setsasl(s->in, s->saslconn); prot_setsasl(s->out, s->saslconn); } if (mechlist) free(mechlist); out: /* r == SASL_OK on success */ if (local_cb) free_callbacks(cb); return r; }
static int login(struct backend *s, const char *userid, sasl_callback_t *cb, const char **status, int noauth __attribute__((unused))) { int r = 0; socklen_t addrsize; struct sockaddr_storage saddr_l, saddr_r; char remoteip[60], localip[60]; static struct buf buf = BUF_INITIALIZER; sasl_security_properties_t secprops = { 0, 0xFF, PROT_BUFSIZE, 0, NULL, NULL }; /* default secprops */ const char *mech_conf, *pass, *clientout = NULL; struct auth_scheme_t *scheme = NULL; unsigned need_tls = 0, tls_done = 0, auth_done = 0, clientoutlen; hdrcache_t hdrs = NULL; if (status) *status = NULL; /* set the IP addresses */ addrsize = sizeof(struct sockaddr_storage); if (getpeername(s->sock, (struct sockaddr *) &saddr_r, &addrsize) || iptostring((struct sockaddr *) &saddr_r, addrsize, remoteip, 60)) { if (status) *status = "Failed to get remote IP address"; return SASL_FAIL; } addrsize = sizeof(struct sockaddr_storage); if (getsockname(s->sock, (struct sockaddr *) &saddr_l, &addrsize) || iptostring((struct sockaddr *) &saddr_l, addrsize, localip, 60)) { if (status) *status = "Failed to get local IP address"; return SASL_FAIL; } /* Create callbacks, if necessary */ if (!cb) { buf_setmap(&buf, s->hostname, strcspn(s->hostname, ".")); buf_appendcstr(&buf, "_password"); pass = config_getoverflowstring(buf_cstring(&buf), NULL); if (!pass) pass = config_getstring(IMAPOPT_PROXY_PASSWORD); cb = mysasl_callbacks(NULL, /* userid */ config_getstring(IMAPOPT_PROXY_AUTHNAME), config_getstring(IMAPOPT_PROXY_REALM), pass); s->sasl_cb = cb; } /* Create SASL context */ r = sasl_client_new(s->prot->sasl_service, s->hostname, localip, remoteip, cb, SASL_USAGE_FLAGS, &s->saslconn); if (r != SASL_OK) goto done; r = sasl_setprop(s->saslconn, SASL_SEC_PROPS, &secprops); if (r != SASL_OK) goto done; /* Get SASL mechanism list. We can force a particular mechanism using a <shorthost>_mechs option */ buf_setmap(&buf, s->hostname, strcspn(s->hostname, ".")); buf_appendcstr(&buf, "_mechs"); if (!(mech_conf = config_getoverflowstring(buf_cstring(&buf), NULL))) { mech_conf = config_getstring(IMAPOPT_FORCE_SASL_CLIENT_MECH); } do { unsigned code; const char **hdr, *errstr, *serverin; char base64[BASE64_BUF_SIZE+1]; unsigned int serverinlen; struct body_t resp_body; #ifdef SASL_HTTP_REQUEST sasl_http_request_t httpreq = { "OPTIONS", /* Method */ "*", /* URI */ (u_char *) "", /* Empty body */ 0, /* Zero-length body */ 0 }; /* Persistent cxn? */ #endif /* Base64 encode any client response, if necessary */ if (clientout && scheme && (scheme->flags & AUTH_BASE64)) { r = sasl_encode64(clientout, clientoutlen, base64, BASE64_BUF_SIZE, &clientoutlen); if (r != SASL_OK) break; clientout = base64; } /* Send Authorization and/or Upgrade request to server */ prot_puts(s->out, "OPTIONS * HTTP/1.1\r\n"); prot_printf(s->out, "Host: %s\r\n", s->hostname); prot_printf(s->out, "User-Agent: %s\r\n", buf_cstring(&serverinfo)); if (scheme) { prot_printf(s->out, "Authorization: %s %s\r\n", scheme->name, clientout ? clientout : ""); prot_printf(s->out, "Authorize-As: %s\r\n", userid ? userid : "anonymous"); } else { prot_printf(s->out, "Upgrade: %s\r\n", TLS_VERSION); if (need_tls) { prot_puts(s->out, "Connection: Upgrade\r\n"); need_tls = 0; } prot_puts(s->out, "Authorization: \r\n"); } prot_puts(s->out, "\r\n"); prot_flush(s->out); serverin = clientout = NULL; serverinlen = clientoutlen = 0; /* Read response(s) from backend until final response or error */ do { resp_body.flags = BODY_DISCARD; r = http_read_response(s, METH_OPTIONS, &code, NULL, &hdrs, &resp_body, &errstr); if (r) { if (status) *status = errstr; break; } if (code == 101) { /* Switching Protocols */ if (tls_done) { r = HTTP_BAD_GATEWAY; if (status) *status = "TLS already active"; break; } else if (backend_starttls(s, NULL, NULL, NULL)) { r = HTTP_SERVER_ERROR; if (status) *status = "Unable to start TLS"; break; } else tls_done = 1; } } while (code < 200); switch (code) { default: /* Failure */ if (!r) { r = HTTP_BAD_GATEWAY; if (status) { buf_reset(&buf); buf_printf(&buf, "Unexpected status code from backend: %u", code); *status = buf_cstring(&buf); } } break; case 426: /* Upgrade Required */ if (tls_done) { r = HTTP_BAD_GATEWAY; if (status) *status = "TLS already active"; } else need_tls = 1; break; case 200: /* OK */ if (scheme->recv_success && (serverin = scheme->recv_success(hdrs))) { /* Process success data */ serverinlen = strlen(serverin); goto challenge; } break; case 401: /* Unauthorized */ if (auth_done) { r = SASL_BADAUTH; break; } if (!serverin) { int i = 0; hdr = spool_getheader(hdrs, "WWW-Authenticate"); if (!scheme) { unsigned avail_auth_schemes = 0; const char *mech = NULL; size_t len; /* Compare authentication schemes offered in * WWW-Authenticate header(s) to what we support */ buf_reset(&buf); for (i = 0; hdr && hdr[i]; i++) { len = strcspn(hdr[i], " "); for (scheme = auth_schemes; scheme->name; scheme++) { if (!strncmp(scheme->name, hdr[i], len) && !((scheme->flags & AUTH_NEED_PERSIST) && (resp_body.flags & BODY_CLOSE))) { /* Tag the scheme as available */ avail_auth_schemes |= (1 << scheme->idx); /* Add SASL-based schemes to SASL mech list */ if (scheme->saslmech) { if (buf_len(&buf)) buf_putc(&buf, ' '); buf_appendcstr(&buf, scheme->saslmech); } break; } } } /* If we have a mech_conf, use it */ if (mech_conf && buf_len(&buf)) { char *conf = xstrdup(mech_conf); char *newmechlist = intersect_mechlists(conf, (char *) buf_cstring(&buf)); if (newmechlist) { buf_setcstr(&buf, newmechlist); free(newmechlist); } else { syslog(LOG_DEBUG, "%s did not offer %s", s->hostname, mech_conf); buf_reset(&buf); } free(conf); } #ifdef SASL_HTTP_REQUEST /* Set HTTP request as specified above (REQUIRED) */ httpreq.non_persist = (resp_body.flags & BODY_CLOSE); sasl_setprop(s->saslconn, SASL_HTTP_REQUEST, &httpreq); #endif /* Try to start SASL exchange using available mechs */ r = sasl_client_start(s->saslconn, buf_cstring(&buf), NULL, /* no prompts */ NULL, NULL, /* no initial resp */ &mech); if (mech) { /* Find auth scheme associated with chosen SASL mech */ for (scheme = auth_schemes; scheme->name; scheme++) { if (scheme->saslmech && !strcmp(scheme->saslmech, mech)) break; } } else { /* No matching SASL mechs - try Basic */ scheme = &auth_schemes[AUTH_BASIC]; if (!(avail_auth_schemes & (1 << scheme->idx))) { need_tls = !tls_done; break; /* case 401 */ } } /* Find the associated WWW-Authenticate header */ for (i = 0; hdr && hdr[i]; i++) { len = strcspn(hdr[i], " "); if (!strncmp(scheme->name, hdr[i], len)) break; } } /* Get server challenge, if any */ if (hdr) { const char *p = strchr(hdr[i], ' '); serverin = p ? ++p : ""; serverinlen = strlen(serverin); } } challenge: if (serverin) { /* Perform the next step in the auth exchange */ if (scheme->idx == AUTH_BASIC) { /* Don't care about "realm" in server challenge */ const char *authid = callback_getdata(s->saslconn, cb, SASL_CB_AUTHNAME); pass = callback_getdata(s->saslconn, cb, SASL_CB_PASS); buf_reset(&buf); buf_printf(&buf, "%s:%s", authid, pass); clientout = buf_cstring(&buf); clientoutlen = buf_len(&buf); auth_done = 1; } else { /* Base64 decode any server challenge, if necessary */ if (serverin && (scheme->flags & AUTH_BASE64)) { r = sasl_decode64(serverin, serverinlen, base64, BASE64_BUF_SIZE, &serverinlen); if (r != SASL_OK) break; /* case 401 */ serverin = base64; } /* SASL mech (Digest, Negotiate, NTLM) */ r = sasl_client_step(s->saslconn, serverin, serverinlen, NULL, /* no prompts */ &clientout, &clientoutlen); if (r == SASL_OK) auth_done = 1; } } break; /* case 401 */ } } while (need_tls || clientout); done: if (hdrs) spool_free_hdrcache(hdrs); if (r && status && !*status) *status = sasl_errstring(r, NULL, NULL); return r; }
static void replica_connect(const char *channel) { int wait; sasl_callback_t *cb; int timeout; const char *port, *auth_status = NULL; cb = mysasl_callbacks(NULL, get_config(channel, "sync_authname"), get_config(channel, "sync_realm"), get_config(channel, "sync_password")); /* get the right port */ port = get_config(channel, "sync_port"); if (port) { imap_csync_protocol.service = port; csync_protocol.service = port; } for (wait = 15;; wait *= 2) { sync_backend = backend_connect(sync_backend, servername, &imap_csync_protocol, "", cb, &auth_status, (verbose > 1 ? fileno(stderr) : -1)); if (sync_backend) { if (sync_backend->capability & CAPA_REPLICATION) { /* attach our IMAP tag buffer to our protstreams as userdata */ sync_backend->in->userdata = sync_backend->out->userdata = &tagbuf; break; } else { backend_disconnect(sync_backend); sync_backend = NULL; } } sync_backend = backend_connect(sync_backend, servername, &csync_protocol, "", cb, NULL, (verbose > 1 ? fileno(stderr) : -1)); if (sync_backend || auth_status || connect_once || wait > 1000) break; fprintf(stderr, "Can not connect to server '%s', retrying in %d seconds\n", servername, wait); sleep(wait); } free_callbacks(cb); cb = NULL; if (!sync_backend) { fprintf(stderr, "Can not connect to server '%s'\n", servername); syslog(LOG_ERR, "Can not connect to server '%s'", servername); _exit(1); } if (servername[0] != '/' && sync_backend->sock >= 0) { tcp_disable_nagle(sync_backend->sock); tcp_enable_keepalive(sync_backend->sock); } #ifdef HAVE_ZLIB /* Does the backend support compression? */ if (CAPA(sync_backend, CAPA_COMPRESS)) { prot_printf(sync_backend->out, "%s\r\n", sync_backend->prot->u.std.compress_cmd.cmd); prot_flush(sync_backend->out); if (sync_parse_response("COMPRESS", sync_backend->in, NULL)) { if (do_compress) fatal("Failed to enable compression, aborting", EC_SOFTWARE); syslog(LOG_NOTICE, "Failed to enable compression, continuing uncompressed"); } else { prot_setcompress(sync_backend->in); prot_setcompress(sync_backend->out); } } else if (do_compress) fatal("Backend does not support compression, aborting", EC_SOFTWARE); #endif /* links to sockets */ sync_in = sync_backend->in; sync_out = sync_backend->out; if (verbose > 1) { prot_setlog(sync_in, fileno(stderr)); prot_setlog(sync_out, fileno(stderr)); } /* Set inactivity timer */ timeout = config_getint(IMAPOPT_SYNC_TIMEOUT); if (timeout < 3) timeout = 3; prot_settimeout(sync_in, timeout); /* Force use of LITERAL+ so we don't need two way communications */ prot_setisclient(sync_in, 1); prot_setisclient(sync_out, 1); }
static struct backend *restore_connect(const char *servername, struct buf *tagbuf, const struct restore_options *options) { struct backend *backend = NULL; sasl_callback_t *cb; int timeout; const char *auth_status = NULL; cb = mysasl_callbacks(NULL, config_getstring(IMAPOPT_RESTORE_AUTHNAME), config_getstring(IMAPOPT_RESTORE_REALM), config_getstring(IMAPOPT_RESTORE_PASSWORD)); /* try to connect over IMAP */ backend = backend_connect(backend, servername, &imap_csync_protocol, "", cb, &auth_status, (options->verbose > 1 ? fileno(stderr) : -1)); if (backend) { if (backend->capability & CAPA_REPLICATION) { /* attach our IMAP tag buffer to our protstreams as userdata */ backend->in->userdata = backend->out->userdata = tagbuf; } else { backend_disconnect(backend); backend = NULL; } } /* if that didn't work, fall back to csync */ if (!backend) { backend = backend_connect(backend, servername, &csync_protocol, "", cb, NULL, (options->verbose > 1 ? fileno(stderr) : -1)); } free_callbacks(cb); cb = NULL; if (!backend) { fprintf(stderr, "Can not connect to server '%s'\n", servername); syslog(LOG_ERR, "Can not connect to server '%s'", servername); return NULL; } if (servername[0] != '/' && backend->sock >= 0) { tcp_disable_nagle(backend->sock); tcp_enable_keepalive(backend->sock); } #ifdef HAVE_ZLIB /* Does the backend support compression? */ if (CAPA(backend, CAPA_COMPRESS)) { prot_printf(backend->out, "%s\r\n", backend->prot->u.std.compress_cmd.cmd); prot_flush(backend->out); if (sync_parse_response("COMPRESS", backend->in, NULL)) { if (options->require_compression) fatal("Failed to enable compression, aborting", EX_SOFTWARE); syslog(LOG_NOTICE, "Failed to enable compression, continuing uncompressed"); } else { prot_setcompress(backend->in); prot_setcompress(backend->out); } } else if (options->require_compression) { fatal("Backend does not support compression, aborting", EX_SOFTWARE); } #endif if (options->verbose > 1) { /* XXX did we do this during backend_connect already? */ prot_setlog(backend->in, fileno(stderr)); prot_setlog(backend->out, fileno(stderr)); } /* Set inactivity timer */ timeout = config_getint(IMAPOPT_SYNC_TIMEOUT); if (timeout < 3) timeout = 3; prot_settimeout(backend->in, timeout); /* Force use of LITERAL+ so we don't need two way communications */ prot_setisclient(backend->in, 1); prot_setisclient(backend->out, 1); return backend; }