/* pop3proxyd.c -- POP3 server protocol parsing (proxy) * * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any other legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * $Id: pop3proxyd.c,v 1.62.2.1 2004/02/16 22:13:04 rjs3 Exp $ */ #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "prot.h" #include #include #include "acl.h" #include "util.h" #include "auth.h" #include "imapconf.h" #include "tls.h" #include "iptostring.h" #include "exitcodes.h" #include "imap_err.h" #include "mailbox.h" #include "version.h" #include "xmalloc.h" #include "mboxlist.h" #include "telemetry.h" #ifdef HAVE_KRB /* kerberos des is purported to conflict with OpenSSL DES */ #define DES_DEFS #include /* MIT's kpop authentication kludge */ char klrealm[REALM_SZ]; AUTH_DAT kdata; #endif /* HAVE_KRB */ static int kflag = 0; extern int optind; extern char *optarg; extern int opterr; #ifdef HAVE_SSL static SSL *tls_conn; #endif /* HAVE_SSL */ sasl_conn_t *popd_saslconn; /* the sasl connection context */ char *popd_userid = 0; struct sockaddr_storage popd_localaddr, popd_remoteaddr; int popd_haveaddr = 0; char popd_clienthost[NI_MAXHOST*2+1] = "[local]"; static int popd_logfd = -1; struct protstream *popd_out = NULL; struct protstream *popd_in = NULL; int popd_starttls_done = 0; int popd_auth_done = 0; struct protstream *backend_out, *backend_in; int backend_sock; sasl_conn_t *backend_saslconn; /* the sasl proxy policy context */ static struct proxy_context popd_proxyctx = { "pop3", 0, 0, 0, NULL, NULL, NULL }; /* current namespace */ static struct namespace popd_namespace; static void cmd_apop(char *response); static int apop_enabled(void); static char popd_apop_chal[45 + MAXHOSTNAMELEN + 1]; /* */ static void cmd_auth(); static void cmd_capa(); static void cmd_pass(); static void cmd_user(); static void cmd_starttls(int pop3s); static void cmdloop(void); static void kpop(void); static void usage(void); static void openproxy(void); static void bitpipe(void); extern void setproctitle_init(int argc, char **argv, char **envp); extern int proc_register(const char *progname, const char *clienthost, const char *userid, const char *mailbox); extern void proc_cleanup(void); void shut_down(int code) __attribute__ ((noreturn)); extern int saslserver(sasl_conn_t *conn, const char *mech, const char *init_resp, const char *continuation, struct protstream *pin, struct protstream *pout, int *sasl_result, char **success_data); /* Enable the resetting of a sasl_conn_t */ static int reset_saslconn(sasl_conn_t **conn); static struct { char *ipremoteport; char *iplocalport; sasl_ssf_t ssf; char *authid; } saslprops = {NULL,NULL,0,NULL}; static struct sasl_callback mysasl_cb[] = { { SASL_CB_GETOPT, &mysasl_config, NULL }, { SASL_CB_PROXY_POLICY, &mysasl_proxy_policy, (void*) &popd_proxyctx }, { SASL_CB_CANON_USER, &mysasl_canon_user, NULL }, { SASL_CB_LIST_END, NULL, NULL } }; /* * run once when process is forked; * MUST NOT exit directly; must return with non-zero error code */ int service_init(int argc, char **argv, char **envp) { int r; config_changeident("pop3d"); if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE); setproctitle_init(argc, argv, envp); /* set signal handlers */ signals_set_shutdown(&shut_down); signals_add_handlers(); signal(SIGPIPE, SIG_IGN); /* set the SASL allocation functions */ sasl_set_alloc((sasl_malloc_t *) &xmalloc, (sasl_calloc_t *) &calloc, (sasl_realloc_t *) &xrealloc, (sasl_free_t *) &free); /* load the SASL plugins */ if ((r = sasl_server_init(mysasl_cb, "Cyrus")) != SASL_OK) { syslog(LOG_ERR, "SASL failed initializing: sasl_server_init(): %s", sasl_errstring(r, NULL, NULL)); return 2; } /* open the mboxlist, we'll need it for real work */ mboxlist_init(0); mboxlist_open(NULL); /* Set namespace */ if ((r = mboxname_init_namespace(&popd_namespace, 0)) != 0) { syslog(LOG_ERR, error_message(r)); fatal(error_message(r), EC_CONFIG); } return 0; } /* * run for each accepted connection */ int service_main(int argc, char **argv, char **envp __attribute__((unused))) { int pop3s = 0; int opt; socklen_t salen; char hbuf[NI_MAXHOST]; char localip[60], remoteip[60]; int timeout; sasl_security_properties_t *secprops=NULL; signals_poll(); popd_in = prot_new(0, 0); popd_out = prot_new(1, 1); while ((opt = getopt(argc, argv, "sk")) != EOF) { switch(opt) { case 's': /* pop3s (do starttls right away) */ pop3s = 1; if (!tls_enabled("pop3")) { syslog(LOG_ERR, "pop3s: required OpenSSL options not present"); fatal("pop3s: required OpenSSL options not present", EC_CONFIG); } break; case 'k': kflag++; break; default: usage(); } } /* Find out name of client host */ salen = sizeof(popd_remoteaddr); if (getpeername(0, (struct sockaddr *)&popd_remoteaddr, &salen) == 0 && (popd_remoteaddr.ss_family == AF_INET || popd_remoteaddr.ss_family == AF_INET6)) { if (getnameinfo((struct sockaddr *)&popd_remoteaddr, salen, hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD) == 0) { strncpy(popd_clienthost, hbuf, sizeof(hbuf)); } else { popd_clienthost[0] = '\0'; } getnameinfo((struct sockaddr *)&popd_remoteaddr, salen, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID); strcat(popd_clienthost, "["); strcat(popd_clienthost, hbuf); strcat(popd_clienthost, "]"); salen = sizeof(popd_localaddr); if (getsockname(0, (struct sockaddr *)&popd_localaddr, &salen) == 0) { popd_haveaddr = 1; } } /* other params should be filled in */ if (sasl_server_new("pop", config_servername, NULL, NULL, NULL, NULL, 0, &popd_saslconn) != SASL_OK) fatal("SASL failed initializing: sasl_server_new()",EC_TEMPFAIL); /* will always return something valid */ secprops = mysasl_secprops(SASL_SEC_NOPLAINTEXT); sasl_setprop(popd_saslconn, SASL_SEC_PROPS, secprops); if(iptostring((struct sockaddr *)&popd_localaddr, salen, localip, 60) == 0) { sasl_setprop(popd_saslconn, SASL_IPLOCALPORT, localip); saslprops.iplocalport = xstrdup(localip); } if(iptostring((struct sockaddr *)&popd_remoteaddr, salen, remoteip, 60) == 0) { sasl_setprop(popd_saslconn, SASL_IPREMOTEPORT, remoteip); saslprops.ipremoteport = xstrdup(remoteip); } proc_register("pop3d", popd_clienthost, NULL, NULL); /* Set inactivity timer */ timeout = config_getint("poptimeout", 10); if (timeout < 10) timeout = 10; prot_settimeout(popd_in, timeout*60); prot_setflushonread(popd_in, popd_out); if (kflag) kpop(); /* we were connected on pop3s port so we should do TLS negotiation immediatly */ if (pop3s == 1) cmd_starttls(1); /* Create APOP challenge for banner */ if (!sasl_mkchal(popd_saslconn, popd_apop_chal, sizeof(popd_apop_chal), 1)) { syslog(LOG_ERR, "APOP disabled: can't create challenge"); *popd_apop_chal = 0; } prot_printf(popd_out, "+OK %s Cyrus POP3 Murder %s server ready %s\r\n", config_servername, CYRUS_VERSION, apop_enabled() ? popd_apop_chal : ""); cmdloop(); /* xxx no process reuse for you */ shut_down(0); /* return 0; */ } /* Called by service API to shut down the service */ void service_abort(int error) { shut_down(error); } void usage(void) { prot_printf(popd_out, "-ERR usage: pop3proxyd [-C ]" " [-k] [-s]\r\n"); prot_flush(popd_out); exit(EC_USAGE); } /* * Cleanly shut down and exit */ void shut_down(int code) { proc_cleanup(); mboxlist_close(); mboxlist_done(); if (popd_in) { prot_NONBLOCK(popd_in); prot_fill(popd_in); prot_free(popd_in); } if (popd_out) { prot_flush(popd_out); prot_free(popd_out); } if(popd_logfd != -1) close(popd_logfd); #ifdef HAVE_SSL tls_shutdown_serverengine(); #endif exit(code); } void fatal(const char* s, int code) { static int recurse_code = 0; if (recurse_code) { /* We were called recursively. Just give up */ proc_cleanup(); exit(recurse_code); } recurse_code = code; if (popd_out) { prot_printf(popd_out, "-ERR [SYS/PERM] Fatal error: %s\r\n", s); prot_flush(popd_out); } shut_down(code); } /* * Found a shutdown file: Spit out an untagged BYE and shut down */ void shutdown_file(void) { int fd; struct protstream *shutdown_in; char buf[1024]; char *p; static char shutdownfilename[1024]; if (!shutdownfilename[0]) snprintf(shutdownfilename, sizeof(shutdownfilename), "%s/msg/shutdown", config_dir); if ((fd = open(shutdownfilename, O_RDONLY, 0)) == -1) return; shutdown_in = prot_new(fd, 0); prot_fgets(buf, sizeof(buf), shutdown_in); if ((p = strchr(buf, '\r')) != NULL) *p = 0; if ((p = strchr(buf, '\n')) != NULL) *p = 0; for (p = buf; *p == '['; p++); /* can't have [ be first char, sigh */ prot_printf(popd_out, "-ERR [SYS/TEMP] %s\r\n", p); shut_down(0); } #ifdef HAVE_KRB /* translate IPv4 mapped IPv6 address to IPv4 address */ #ifdef IN6_IS_ADDR_V4MAPPED static void sockaddr_unmapped(struct sockaddr *sa, socklen_t *len) { struct sockaddr_in6 *sin6; struct sockaddr_in *sin4; uint32_t addr; int port; if (sa->sa_family != AF_INET6) return; sin6 = (struct sockaddr_in6 *)sa; if (!IN6_IS_ADDR_V4MAPPED((&sin6->sin6_addr))) return; sin4 = (struct sockaddr_in *)sa; addr = *(uint32_t *)&sin6->sin6_addr.s6_addr[12]; port = sin6->sin6_port; memset(sin4, 0, sizeof(struct sockaddr_in)); sin4->sin_addr.s_addr = addr; sin4->sin_port = port; sin4->sin_family = AF_INET; #ifdef HAVE_SOCKADDR_SA_LEN sin4->sin_len = sizeof(struct sockaddr_in); #endif *len = sizeof(struct sockaddr_in); } #else static void sockaddr_unmapped(struct sockaddr *sa __attribute__((unused)), socklen_t *len __attribute__((unused))) { return; } #endif /* * MIT's kludge of a kpop protocol * Client does a krb_sendauth() first thing */ static void kpop(void) { Key_schedule schedule; KTEXT_ST ticket; char instance[INST_SZ]; char version[9]; const char *srvtab; int r; socklen_t len; if (!popd_haveaddr) { fatal("Cannot get client's IP address", EC_OSERR); } srvtab = config_getstring("srvtab", ""); sockaddr_unmapped((struct sockaddr *)&popd_remoteaddr, &len); if (popd_remoteaddr.ss_family != AF_INET) { prot_printf(popd_out, "-ERR [AUTH] Kerberos authentication failure: %s\r\n", "not an IPv4 connection"); shut_down(0); } strcpy(instance, "*"); r = krb_recvauth(0L, 0, &ticket, "pop", instance, (struct sockaddr_in *) &popd_remoteaddr, (struct sockaddr_in *) NULL, &kdata, (char*) srvtab, schedule, version); if (r) { prot_printf(popd_out, "-ERR [AUTH] Kerberos authentication failure: %s\r\n", krb_err_txt[r]); syslog(LOG_NOTICE, "badlogin: %s kpop ? %s%s%s@%s %s", popd_clienthost, kdata.pname, kdata.pinst[0] ? "." : "", kdata.pinst, kdata.prealm, krb_err_txt[r]); shut_down(0); } r = krb_get_lrealm(klrealm,1); if (r) { prot_printf(popd_out, "-ERR [AUTH] Kerberos failure: %s\r\n", krb_err_txt[r]); syslog(LOG_NOTICE, "badlogin: %s kpop ? %s%s%s@%s krb_get_lrealm: %s", popd_clienthost, kdata.pname, kdata.pinst[0] ? "." : "", kdata.pinst, kdata.prealm, krb_err_txt[r]); shut_down(0); } } #else static void kpop(void) { usage(); } #endif /* * Top-level command loop parsing */ static void cmdloop(void) { char inputbuf[8192]; char *p, *arg; for (;;) { signals_poll(); if (popd_auth_done) { bitpipe(); return; } /* check for shutdown file */ shutdown_file(); if (!prot_fgets(inputbuf, sizeof(inputbuf), popd_in)) { return; } p = inputbuf + strlen(inputbuf); if (p > inputbuf && p[-1] == '\n') *--p = '\0'; if (p > inputbuf && p[-1] == '\r') *--p = '\0'; /* Parse into keword and argument */ for (p = inputbuf; *p && !isspace((int) *p); p++); if (*p) { *p++ = '\0'; arg = p; if (strcasecmp(inputbuf, "pass") != 0) { while (*arg && isspace((int) *arg)) { arg++; } } if (!*arg) { prot_printf(popd_out, "-ERR Syntax error\r\n"); continue; } } else { arg = 0; } lcase(inputbuf); if (!strcmp(inputbuf, "quit")) { if (!arg) { prot_printf(popd_out, "+OK\r\n"); shut_down(0); } else prot_printf(popd_out, "-ERR Unexpected extra argument\r\n"); } else if (!strcmp(inputbuf, "capa")) { if (arg) { prot_printf(popd_out, "-ERR Unexpected extra argument\r\n"); } else { cmd_capa(); } } else if (!strcmp(inputbuf, "user")) { if (!arg) { prot_printf(popd_out, "-ERR Missing argument\r\n"); } else { cmd_user(arg); } } else if (!strcmp(inputbuf, "pass")) { if (!arg) prot_printf(popd_out, "-ERR Missing argument\r\n"); else cmd_pass(arg); } else if (!strcmp(inputbuf, "apop") && apop_enabled()) { if (!arg) prot_printf(popd_out, "-ERR Missing argument\r\n"); else cmd_apop(arg); } else if (!strcmp(inputbuf, "auth")) { cmd_auth(arg); } else if (!strcmp(inputbuf, "stls") && tls_enabled("pop3")) { if (arg) { prot_printf(popd_out, "-ERR STLS doesn't take any arguements\r\n"); } else { cmd_starttls(0); } } else { prot_printf(popd_out, "-ERR Unrecognized command\r\n"); } } } #ifdef HAVE_SSL static void cmd_starttls(int pop3s) { char *tls_cert, *tls_key; int result; int *layerp; char *auth_id; sasl_ssf_t ssf; /* SASL and openssl have different ideas about whether ssf is signed */ layerp = (int *) &ssf; if (popd_starttls_done == 1) { prot_printf(popd_out, "-ERR %s\r\n", "Already successfully executed STLS"); return; } tls_cert = (char *)config_getstring("tls_pop3_cert_file", config_getstring("tls_cert_file", "")); tls_key = (char *)config_getstring("tls_pop3_key_file", config_getstring("tls_key_file", "")); result=tls_init_serverengine("pop3", 5, /* depth to verify */ !pop3s, /* can client auth? */ !pop3s); /* TLSv1 only? */ if (result == -1) { syslog(LOG_ERR, "[pop3d] error initializing TLS"); if (pop3s == 0) prot_printf(popd_out, "-ERR [SYS/PERM] %s\r\n", "Error initializing TLS"); else fatal("tls_init() failed",EC_TEMPFAIL); return; } if (pop3s == 0) { prot_printf(popd_out, "+OK %s\r\n", "Begin TLS negotiation now"); /* must flush our buffers before starting tls */ prot_flush(popd_out); } result=tls_start_servertls(0, /* read */ 1, /* write */ layerp, &auth_id, &tls_conn); /* if error */ if (result==-1) { if (pop3s == 0) { prot_printf(popd_out, "-ERR [SYS/PERM] Starttls failed\r\n"); syslog(LOG_NOTICE, "[pop3d] STARTTLS failed: %s", popd_clienthost); } else { syslog(LOG_NOTICE, "pop3s failed: %s", popd_clienthost); fatal("tls_start_servertls() failed", EC_TEMPFAIL); } return; } /* tell SASL about the negotiated layer */ result = sasl_setprop(popd_saslconn, SASL_SSF_EXTERNAL, &ssf); if (result != SASL_OK) { fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL); } saslprops.ssf = ssf; result = sasl_setprop(popd_saslconn, SASL_AUTH_EXTERNAL, auth_id); if (result != SASL_OK) { fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL); } if(saslprops.authid) { free(saslprops.authid); saslprops.authid = NULL; } if(auth_id) saslprops.authid = xstrdup(auth_id); /* tell the prot layer about our new layers */ prot_settls(popd_in, tls_conn); prot_settls(popd_out, tls_conn); popd_starttls_done = 1; } #else static void cmd_starttls(int pop3s) { fatal("cmd_starttls() called, but no OpenSSL", EC_SOFTWARE); } #endif /* HAVE_SSL */ static int apop_enabled(void) { /* Check if pseudo APOP mechanism is enabled (challenge == NULL) */ if (sasl_checkapop(popd_saslconn, NULL, 0, NULL, 0) != SASL_OK) return 0; /* Check if we have a challenge string */ if (!*popd_apop_chal) return 0; return 1; } static void cmd_apop(char *response) { int fd; struct protstream *shutdown_in; char buf[1024]; char *p; char shutdownfilename[1024]; int sasl_result; char *canon_user; assert(response != NULL); if (popd_userid) { prot_printf(popd_out, "-ERR [AUTH] Must give PASS command\r\n"); return; } /* Check if it is enabled (challenge == NULL) */ if(sasl_checkapop(popd_saslconn, NULL, 0, NULL, 0) != SASL_OK) fatal("cmd_apop called without working sasl_checkapop", EC_SOFTWARE); snprintf(shutdownfilename, sizeof(shutdownfilename), "%s/msg/shutdown", config_dir); if ((fd = open(shutdownfilename, O_RDONLY, 0)) != -1) { shutdown_in = prot_new(fd, 0); prot_fgets(buf, sizeof(buf), shutdown_in); if ((p = strchr(buf, '\r'))!=NULL) *p = 0; if ((p = strchr(buf, '\n'))!=NULL) *p = 0; for(p = buf; *p == '['; p++); /* can't have [ be first char */ prot_printf(popd_out, "-ERR [SYS/TEMP] %s\r\n", p); prot_flush(popd_out); shut_down(0); } sasl_result = sasl_checkapop(popd_saslconn, popd_apop_chal, strlen(popd_apop_chal), response, strlen(response)); /* failed authentication */ if (sasl_result != SASL_OK) { sleep(3); prot_printf(popd_out, "-ERR [AUTH] authenticating: %s\r\n", sasl_errstring(sasl_result, NULL, NULL)); syslog(LOG_NOTICE, "badlogin: %s APOP (%s) %s", popd_clienthost, popd_apop_chal, sasl_errdetail(popd_saslconn)); return; } /* successful authentication */ /* * get the userid from SASL --- already canonicalized from * mysasl_proxy_policy() */ sasl_result = sasl_getprop(popd_saslconn, SASL_USERNAME, (const void **) &canon_user); popd_userid = xstrdup(canon_user); if (sasl_result != SASL_OK) { prot_printf(popd_out, "-ERR [AUTH] weird SASL error %d getting SASL_USERNAME\r\n", sasl_result); return; } syslog(LOG_NOTICE, "login: %s %s APOP %s", popd_clienthost, popd_userid, "User logged in"); openproxy(); /* Create telemetry log */ popd_logfd = telemetry_log(popd_userid, popd_in, popd_out, 0); popd_auth_done = 1; } void cmd_user(user) char *user; { char *p; /* possibly disallow USER */ if (!(kflag || popd_starttls_done || config_getswitch("allowplaintext", 1))) { prot_printf(popd_out, "-ERR [AUTH] USER command only available under a layer\r\n"); return; } if (popd_userid) { prot_printf(popd_out, "-ERR [AUTH] Must give PASS command\r\n"); return; } shutdown_file(); /* check for shutdown file */ if (!(p = auth_canonifyid(user,0)) || /* '.' isn't allowed if '.' is the hierarchy separator */ (popd_namespace.hier_sep == '.' && strchr(p, '.')) || strlen(p) + 6 > MAX_MAILBOX_PATH) { prot_printf(popd_out, "-ERR [AUTH] Invalid user\r\n"); syslog(LOG_NOTICE, "badlogin: %s plaintext %s invalid user", popd_clienthost, beautify_string(user)); } else { popd_userid = xstrdup(p); prot_printf(popd_out, "+OK Name is a valid mailbox\r\n"); } } void cmd_pass(char *pass) { char *reply = 0; int plaintextloginpause; if (!popd_userid) { prot_printf(popd_out, "-ERR [AUTH] Must give USER command\r\n"); return; } #ifdef HAVE_KRB if (kflag) { if (strcmp(popd_userid, kdata.pname) != 0 || kdata.pinst[0] || strcmp(klrealm, kdata.prealm) != 0) { prot_printf(popd_out, "-ERR [AUTH] Invalid login\r\n"); syslog(LOG_NOTICE, "badlogin: %s kpop %s %s%s%s@%s access denied", popd_clienthost, popd_userid, kdata.pname, kdata.pinst[0] ? "." : "", kdata.pinst, kdata.prealm); return; } openproxy(); syslog(LOG_NOTICE, "login: %s %s kpop", popd_clienthost, popd_userid); /* Create telemetry log */ popd_logfd = telemetry_log(popd_userid, popd_in, popd_out, 0); popd_auth_done = 1; return; } #endif if (!strcmp(popd_userid, "anonymous")) { if (config_getswitch("allowanonymouslogin", 0)) { pass = beautify_string(pass); if (strlen(pass) > 500) pass[500] = '\0'; syslog(LOG_NOTICE, "login: %s anonymous %s", popd_clienthost, pass); } else { syslog(LOG_NOTICE, "badlogin: %s anonymous login refused", popd_clienthost); prot_printf(popd_out, "-ERR [AUTH] Invalid login\r\n"); return; } } else if (sasl_checkpass(popd_saslconn, popd_userid, strlen(popd_userid), pass, strlen(pass))!=SASL_OK) { if (reply) { syslog(LOG_NOTICE, "badlogin: %s plaintext %s %s", popd_clienthost, popd_userid, reply); } sleep(3); prot_printf(popd_out, "-ERR [AUTH] Invalid login\r\n"); free(popd_userid); popd_userid = 0; return; } else { syslog(LOG_NOTICE, "login: %s %s plaintext%s %s", popd_clienthost, popd_userid, popd_starttls_done ? "+TLS" : "", reply ? reply : ""); plaintextloginpause = config_getint("plaintextloginpause", 0); if (plaintextloginpause) sleep(plaintextloginpause); } openproxy(); /* Create telemetry log */ popd_logfd = telemetry_log(popd_userid, popd_in, popd_out, 0); popd_auth_done = 1; } /* Handle the POP3 Extension extension. */ void cmd_capa() { int minpoll = config_getint("popminpoll", 0) * 60; int expire = config_getint("popexpiretime", -1); unsigned mechcount; const char *mechlist; prot_printf(popd_out, "+OK List of capabilities follows\r\n"); /* SASL special case: print SASL, then a list of supported capabilities */ if (!popd_auth_done && sasl_listmech(popd_saslconn, NULL, /* should be id string */ "SASL ", " ", "\r\n", &mechlist, NULL, &mechcount) == SASL_OK && mechcount > 0) { prot_write(popd_out, mechlist, strlen(mechlist)); } if (tls_enabled("pop3") && !popd_starttls_done && !popd_auth_done) { prot_printf(popd_out, "STLS\r\n"); } if (expire < 0) { prot_printf(popd_out, "EXPIRE NEVER\r\n"); } else { prot_printf(popd_out, "EXPIRE %d\r\n", expire); } prot_printf(popd_out, "LOGIN-DELAY %d\r\n", minpoll); prot_printf(popd_out, "TOP\r\n"); prot_printf(popd_out, "UIDL\r\n"); prot_printf(popd_out, "PIPELINING\r\n"); prot_printf(popd_out, "RESP-CODES\r\n"); prot_printf(popd_out, "AUTH-RESP-CODE\r\n"); if (!popd_auth_done && (kflag || popd_starttls_done || config_getswitch("allowplaintext", 1))) { prot_printf(popd_out, "USER\r\n"); } prot_printf(popd_out, "IMPLEMENTATION Cyrus POP3 proxy server %s\r\n", CYRUS_VERSION); prot_printf(popd_out, ".\r\n"); prot_flush(popd_out); } /* according to RFC 2449, since we advertise the "SASL" capability, we * must accept an optional second argument of the initial client * response (base64 encoded!). */ void cmd_auth(char *arg) { int r, sasl_result; char *authtype; /* if client didn't specify an argument we give them the list */ if (!arg) { const char *sasllist; unsigned int mechnum; prot_printf(popd_out, "+OK List of supported mechanisms follows\r\n"); /* CRLF seperated, dot terminated */ if (sasl_listmech(popd_saslconn, NULL, "", "\r\n", "\r\n", &sasllist, NULL, &mechnum) == SASL_OK) { if (mechnum>0) { prot_printf(popd_out,"%s",sasllist); } } prot_printf(popd_out, ".\r\n"); return; } authtype = arg; while (*arg && !isspace((int) *arg)) { arg++; } if (isspace((int) *arg)) { /* null terminate authtype, get argument */ *arg++ = '\0'; } else { /* no optional client response */ arg = NULL; } r = saslserver(popd_saslconn, authtype, arg, "+ ", popd_in, popd_out, &sasl_result, NULL); if (r) { const char *errorstring = NULL; switch (r) { case IMAP_SASL_CANCEL: prot_printf(popd_out, "-ERR [AUTH] Client canceled authentication\r\n"); break; case IMAP_SASL_PROTERR: errorstring = prot_error(popd_in); prot_printf(popd_out, "-ERR [AUTH] Error reading client response: %s\r\n", errorstring ? errorstring : ""); break; default: /* failed authentication */ sleep(3); prot_printf(popd_out, "-ERR [AUTH] authenticating: %s\r\n", sasl_errstring(sasl_result, NULL, NULL)); if (authtype) { syslog(LOG_NOTICE, "badlogin: %s %s %s", popd_clienthost, authtype, sasl_errstring(sasl_result, NULL, NULL)); } else { syslog(LOG_NOTICE, "badlogin: %s %s", popd_clienthost, authtype); } } reset_saslconn(&popd_saslconn); return; } /* successful authentication */ /* get the userid from SASL --- already canonicalized from * mysasl_proxy_policy() */ /* FIXME XXX: popd_userid is NOT CONST */ sasl_result = sasl_getprop(popd_saslconn, SASL_USERNAME, (const void **) &popd_userid); if (sasl_result != SASL_OK) { prot_printf(popd_out, "-ERR [AUTH] weird SASL error %d getting SASL_USERNAME\r\n", sasl_result); return; } proc_register("pop3d", popd_clienthost, popd_userid, NULL); syslog(LOG_NOTICE, "login: %s %s %s %s", popd_clienthost, popd_userid, authtype, "User logged in"); openproxy(); prot_setsasl(popd_in, popd_saslconn); prot_setsasl(popd_out, popd_saslconn); /* Create telemetry log */ popd_logfd = telemetry_log(popd_userid, popd_in, popd_out, 0); popd_auth_done = 1; } /* status is only set *if* it is non-null *and* we return something other * than SASL_CONTINUE */ static int mysasl_getauthline(struct protstream *p, char **line, unsigned int *linelen, char **status) { char buf[2096]; char *str = (char *) buf; if(status) *status = NULL; if (!prot_fgets(str, sizeof(buf), p)) { return SASL_FAIL; } if (!strncasecmp(str, "+OK", 3)) { if(status) *status = xstrdup(str + 3); return SASL_OK; } if (!strncasecmp(str, "-ERR", 4)) { if(status) *status = xstrdup(str + 4); return SASL_BADAUTH; } if (str[0] == '+' && str[1] == ' ') { size_t len; str += 2; /* jump past the "+ " */ len = strlen(str) + 1; *line = xmalloc(strlen(str) + 1); if (*str != '\r') { /* decode it */ int r; r = sasl_decode64(str, strlen(str), *line, len, linelen); if (r != SASL_OK) { return r; } return SASL_CONTINUE; } else { /* blank challenge */ *line = NULL; *linelen = 0; return SASL_CONTINUE; } } else { /* huh??? */ if(status) *status = xstrdup(" Unknown Error"); return SASL_FAIL; } } extern sasl_callback_t *mysasl_callbacks(const char *username, const char *authname, const char *realm, const char *password); extern void free_callbacks(sasl_callback_t *in); /* status is only set *if* it is non-null *and* we return something other * than SASL_CONTINUE */ static int proxy_authenticate(const char *hostname, char **authline_status) { int r; sasl_security_properties_t *secprops = NULL; struct sockaddr_storage saddr_l; struct sockaddr_storage saddr_r; socklen_t addrsize; sasl_callback_t *cb; char buf[2048]; char optstr[128]; char *in, *p; const char *out; unsigned int inlen, outlen; const char *mechusing; unsigned b64len; char localip[60], remoteip[60]; const char *pass; strcpy(optstr, hostname); p = strchr(optstr, '.'); if (p) *p = '\0'; strcat(optstr, "_password"); pass = config_getstring(optstr, NULL); cb = mysasl_callbacks(popd_userid, config_getstring("proxy_authname", "proxy"), config_getstring("proxy_realm", NULL), pass); r = sasl_client_new("pop", hostname, NULL, NULL, cb, 0, &backend_saslconn); if (r != SASL_OK) { return r; } secprops = mysasl_secprops(0); r = sasl_setprop(backend_saslconn, SASL_SEC_PROPS, secprops); if (r != SASL_OK) { return r; } /* set the IP addresses */ addrsize=sizeof(struct sockaddr_storage); if (getpeername(backend_sock, (struct sockaddr *)&saddr_r, &addrsize) != 0) return SASL_FAIL; addrsize=sizeof(struct sockaddr_storage); if (getsockname(backend_sock, (struct sockaddr *)&saddr_l,&addrsize)!=0) return SASL_FAIL; if (iptostring((struct sockaddr *)&saddr_r, addrsize, remoteip, 60) != 0) return SASL_FAIL; if (iptostring((struct sockaddr *)&saddr_l, addrsize, localip, 60) != 0) return SASL_FAIL; r = sasl_setprop(backend_saslconn, SASL_IPLOCALPORT, localip); if (r != SASL_OK) return r; r = sasl_setprop(backend_saslconn, SASL_IPREMOTEPORT, remoteip); if (r != SASL_OK) return r; /* read the initial greeting */ if (!prot_fgets(buf, sizeof(buf), backend_in)) { return SASL_FAIL; } strcpy(buf, hostname); p = strchr(buf, '.'); if(p) *p = '\0'; strcat(buf, "_mechs"); /* we now do the actual SASL exchange */ r = sasl_client_start(backend_saslconn, config_getstring(buf, NULL), NULL, &out, &outlen, &mechusing); if ((r != SASL_OK) && (r != SASL_CONTINUE)) { return r; } if (out == NULL || outlen == 0) { prot_printf(backend_out, "AUTH %s\r\n", mechusing); } else { /* send initial challenge */ r = sasl_encode64(out, outlen, buf, sizeof(buf), &b64len); if (r != SASL_OK) return r; prot_printf(backend_out, "AUTH %s %s\r\n", mechusing, buf); } in = NULL; inlen = 0; r = mysasl_getauthline(backend_in, &in, &inlen, authline_status); while (r == SASL_CONTINUE) { r = sasl_client_step(backend_saslconn, in, inlen, NULL, &out, &outlen); if (in) { free(in); } if (r != SASL_OK && r != SASL_CONTINUE) { return r; } r = sasl_encode64(out, outlen, buf, sizeof(buf), &b64len); if (r != SASL_OK) { return r; } prot_write(backend_out, buf, b64len); prot_printf(backend_out, "\r\n"); r = mysasl_getauthline(backend_in, &in, &inlen, authline_status); } /* Done with callbacks */ free_callbacks(cb); /* r == SASL_OK on success */ return r; } static void openproxy(void) { struct addrinfo hints, *res0 = NULL, *res; char inboxname[MAX_MAILBOX_PATH]; int r; char *server = NULL; char *statusline = NULL; /* have to figure out what server to connect to */ strcpy(inboxname, "user."); strcat(inboxname, popd_userid); /* Translate any separators in userid part of inboxname (we need the original userid for AUTH to backend) */ mboxname_hiersep_tointernal(&popd_namespace, inboxname+5); r = mboxlist_lookup(inboxname, &server, NULL, NULL); if (r) fatal("couldn't find backend server", EC_CONFIG); /* xxx hide the fact that we are storing partitions */ if(server) { char *c; c = strchr(server, '!'); if(c) *c = '\0'; } memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(server, "110", &hints, &res0)) fatal("getaddrinfo failed", EC_CONFIG); for (res = res0; res; res = res->ai_next) { backend_sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (backend_sock < 0) continue; if (connect(backend_sock, res->ai_addr, res->ai_addrlen) >= 0) break; close(backend_sock); backend_sock = -1; } freeaddrinfo(res0); if (backend_sock < 0) { syslog(LOG_ERR, "connect() failed: %m"); fatal("connect failed", 1); } backend_in = prot_new(backend_sock, 0); backend_out = prot_new(backend_sock, 1); prot_setflushonread(backend_in, backend_out); if (proxy_authenticate(server, &statusline) != SASL_OK) { syslog(LOG_ERR, "couldn't authenticate to backend server"); prot_printf(popd_out, "-ERR%s", statusline ? statusline : " Authentication to backend server failed\r\n"); prot_flush(popd_out); if(statusline) free(statusline); shut_down(0); /* no process reuse */ } else { prot_setsasl(backend_in, backend_saslconn); prot_setsasl(backend_out, backend_saslconn); } prot_printf(popd_out, "+OK%s", statusline ? statusline : " Mailbox locked and ready"); prot_flush(popd_out); free(statusline); return; } /* we've authenticated the client, we've connected to the backend. now it's all up to them */ static void bitpipe(void) { fd_set read_set, rset; int nfds, r; char buf[4096]; FD_ZERO(&read_set); FD_SET(0, &read_set); FD_SET(backend_sock, &read_set); nfds = backend_sock + 1; for (;;) { rset = read_set; r = select(nfds, &rset, NULL, NULL, NULL); /* if select() failed it's not worth trying to figure anything out */ if (r < 0) goto done; if (FD_ISSET(0, &rset)) { do { int c = prot_read(popd_in, buf, sizeof(buf)); if (c == 0 || c < 0) goto done; prot_write(backend_out, buf, c); } while (popd_in->cnt > 0); prot_flush(backend_out); } if (FD_ISSET(backend_sock, &rset)) { do { int c = prot_read(backend_in, buf, sizeof(buf)); if (c == 0 || c < 0) goto done; prot_write(popd_out, buf, c); } while (backend_in->cnt > 0); prot_flush(popd_out); } } done: /* ok, we're done. close backend connection */ prot_free(backend_in); prot_free(backend_out); cyrus_close_sock(backend_sock); backend_in = backend_out = NULL; /* close the connection to the client */ prot_free(popd_in); prot_free(popd_out); cyrus_close_sock(0); cyrus_close_sock(1); cyrus_close_sock(2); popd_in = popd_out = NULL; return; } /* Reset the given sasl_conn_t to a sane state */ static int reset_saslconn(sasl_conn_t **conn) { int ret; sasl_security_properties_t *secprops = NULL; sasl_dispose(conn); /* do initialization typical of service_main */ ret = sasl_server_new("pop", config_servername, NULL, NULL, NULL, NULL, 0, conn); if(ret != SASL_OK) return ret; if(saslprops.ipremoteport) ret = sasl_setprop(*conn, SASL_IPREMOTEPORT, saslprops.ipremoteport); if(ret != SASL_OK) return ret; if(saslprops.iplocalport) ret = sasl_setprop(*conn, SASL_IPLOCALPORT, saslprops.iplocalport); if(ret != SASL_OK) return ret; secprops = mysasl_secprops(SASL_SEC_NOPLAINTEXT); ret = sasl_setprop(*conn, SASL_SEC_PROPS, secprops); if(ret != SASL_OK) return ret; /* end of service_main initialization excepting SSF */ /* If we have TLS/SSL info, set it */ ret = sasl_setprop(*conn, SASL_IPLOCALPORT, saslprops.iplocalport); if(ret != SASL_OK) return ret; secprops = mysasl_secprops(SASL_SEC_NOPLAINTEXT); ret = sasl_setprop(*conn, SASL_SEC_PROPS, secprops); if(ret != SASL_OK) return ret; /* end of service_main initialization excepting SSF */ /* If we have TLS/SSL info, set it */ if(saslprops.ssf) { ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &saslprops.ssf); } if(ret != SASL_OK) return ret; if(saslprops.authid) { ret = sasl_setprop(*conn, SASL_AUTH_EXTERNAL, saslprops.authid); if(ret != SASL_OK) return ret; } /* End TLS/SSL Info */ return SASL_OK; }