/* 3APA3A simplest proxy server (c) 2002-2026 by Vladimir Dubrovin please read License Agreement */ #include "proxy.h" #ifdef WITH_SMTPP #define RETURN(xxx) { param->res = xxx; goto CLEANRET; } #ifdef WITHMAIN #define NOSTARTTLS 1 #else #define NOSTARTTLS param->srv->nostarttls #endif char ehlo[] = "250-Proxy\r\n" "250-AUTH PLAIN LOGIN\r\n" "250-8BITMIME\r\n" "250 DSN\r\n"; char ehlotls[] = "250-Proxy\r\n" "250-AUTH PLAIN LOGIN\r\n" "250-8BITMIME\r\n" "250-STARTTLS\r\n" "250 DSN\r\n"; int readreply (struct clientparam* param) { unsigned char * buf; int res, i, bufsize = 640; buf = param->tmpbuf; /* a filter may replace it with one of its own */ do { i = sockgetlinebuf(param, SERVER, buf, bufsize-1, '\n', conf.timeouts[STRING_L]); if(i < 1) break; #ifndef WITHMAIN res = handlehdrfilterssrv(param, &buf, &bufsize, 0, &i); if(res != PASS) { if(buf != param->tmpbuf) free(buf); return -1; } #endif socksend(param, param->clisock, buf, i, conf.timeouts[STRING_S]); } while (i > 3 && buf[3] == '-'); if(i < 3) { if(buf != param->tmpbuf) free(buf); return 0; } buf[i] = 0; res = atoi((char *)buf); if(buf != param->tmpbuf) free(buf); return res; } int readcommand (struct clientparam* param) { unsigned char * buf; int i, bufsize = 320; #ifndef WITHMAIN int res; #endif int ret = 1; buf = param->tmpbuf; /* a filter may replace it with one of its own */ i = sockgetlinebuf(param, CLIENT, buf, bufsize-1, '\n', conf.timeouts[STRING_L]); if(i < 4) return 0; #ifndef WITHMAIN if(!strncasecmp((char *)buf, "MAIL", 4) || !strncasecmp((char *)buf, "RCPT", 4) || !strncasecmp((char *)buf, "STARTTLS", 8) || !strncasecmp((char *)buf, "TURN", 4)){ res = handlehdrfilterscli(param, &buf, &bufsize, 0, &i); if(res != PASS) { if(buf != param->tmpbuf) free(buf); if(res == HANDLED) return 2; return -1; } } #endif socksend(param, param->remsock, buf, i, conf.timeouts[STRING_S]); if(!strncasecmp((char *)buf, "STARTTLS", 8) || !strncasecmp((char *)buf, "TURN", 4)){ ret = 22; } if(buf != param->tmpbuf) free(buf); return ret; } int readdata (struct clientparam* param) { unsigned char * buf; int i, bufsize = 4096; #ifndef WITHMAIN int res; #endif buf = param->tmpbuf; /* a filter may replace it with one of its own */ while ((i = sockgetlinebuf(param, CLIENT, buf, bufsize-1, '\n', conf.timeouts[STRING_L])) > 0 && !(i==3 && buf[0] == '.')){ #ifndef WITHMAIN res = handledatfltcli(param, &buf, &bufsize, 0, &i); if(res != PASS) { if(buf != param->tmpbuf) free(buf); if(res == HANDLED) return 1; return -1; } #endif socksend(param, param->remsock, buf, i, conf.timeouts[STRING_S]); } if(i < 1) { if(buf != param->tmpbuf) free(buf); return 0; } socksend(param, param->remsock, buf, i, conf.timeouts[STRING_S]); if(buf != param->tmpbuf) free(buf); return 1; } void * smtppchild(struct clientparam* param) { int i=0, res; unsigned char buf[320]; unsigned char username[256]; char * command = NULL; int login = 0; if(socksend(param, param->clisock, (unsigned char *)"220 Proxy\r\n", 11, conf.timeouts[STRING_S])!=11) {RETURN (611);} i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]); while(i > 4 && (strncasecmp((char *)buf, "AUTH PLAIN", 10) || !(login = 2)) && (strncasecmp((char *)buf, "AUTH LOGIN", 10) || !(login = 1))){ if(!strncasecmp((char *)buf, "QUIT", 4)){ socksend(param, param->clisock, (unsigned char *)"221 Proxy\r\n", 11,conf.timeouts[STRING_S]); RETURN(0); } else if(!strncasecmp((char *)buf, "HELO ", 5)){ socksend(param, param->clisock, (unsigned char *)"250 Proxy\r\n", 11,conf.timeouts[STRING_S]); } else if(!strncasecmp((char *)buf, "EHLO ", 5)){ if(!NOSTARTTLS) socksend(param, param->clisock, (unsigned char *)ehlotls, sizeof(ehlotls) - 1,conf.timeouts[STRING_S]); else socksend(param, param->clisock, (unsigned char *)ehlo, sizeof(ehlo) - 1,conf.timeouts[STRING_S]); } #ifndef WITHMAIN else if(!strncasecmp((char *)buf, "STARTTLS", 8) && !param->srv->nostarttls){ if(socksend(param, param->clisock, (unsigned char *)"220 2.0.0 Ready to start TLS\r\n", 30, conf.timeouts[STRING_S])!=30) {RETURN(673);} param->clientstarttls = S_SMTPP; if(!param->srv->targetport) param->srv->targetport = htons(587); return (void *)tlsprchild; } #endif else if(!*param->hostname) socksend(param, param->clisock, (unsigned char *)"571 need AUTH first\r\n", 22, conf.timeouts[STRING_S]); else { login = -1; buf[i] = 0; command = strdup((char *)buf); break; } i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]); } if(!login) {RETURN(662);} if(login == 1){ socksend(param, param->clisock, (unsigned char *)"334 VXNlcm5hbWU6\r\n", 18,conf.timeouts[STRING_S]); i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]); if(i < 3) {RETURN(663);} buf[i-2] = 0; i = de64(buf,username,255); if(i < 1) {RETURN(664);} username[i] = 0; /* The name has to carry the host to connect to, and the answer says whether it did: without one there is nowhere to go, and what follows reads the name as if there were. */ if(parseconnusername((char *)username, param, 0, 587)) {RETURN(669);} socksend(param, param->clisock, (unsigned char *)"334 UGFzc3dvcmQ6\r\n", 18,conf.timeouts[STRING_S]); i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]); if(i < 2) {RETURN(665);} buf[i-2] = 0; i = de64(buf,username,255); if(i < 0) {RETURN(666);} username[i] = 0; if(PARAMSTR(param, extpassword, username)) {RETURN(666);} } else if(login == 2){ if(i > 13) { buf[i-2] = 0; i = de64(buf+11,username,255); } else { socksend(param, param->clisock, (unsigned char *)"334\r\n", 5,conf.timeouts[STRING_S]); i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]); if(i < 3) {RETURN(667);} buf[i-2] = 0; i = de64(buf,username,255); } if(i < 3 || *username) {RETURN(668);} username[i] = 0; if(parseconnusername((char *)username+1, param, 0, 587)) {RETURN(670);} res = (int)strlen((char *)username+1) + 2; if(res < i){ if(PARAMSTR(param, extpassword, username + res)) {RETURN(670);} } } #ifndef WITHMAIN { /* the host is filtered as a request, in the scratch buffer, where a filter may also hand back a buffer of its own */ int action, reqbufsize, reqsize; unsigned char *req = param->tmpbuf; reqsize = (int)strlen((char *)param->hostname) + 1; reqbufsize = CLIENTTMPBUFSIZE; memcpy(req, param->hostname, reqsize); action = handlereqfilters(param, &req, &reqbufsize, 0, &reqsize); if(action == PASS && PARAMSTR(param, hostname, req)) action = REJECT; if(req != param->tmpbuf) free(req); if(action == HANDLED){ RETURN(0); } if(action != PASS) RETURN(617); } #endif param->operation = CONNECT; res = (*param->srv->authfunc)(param); if(res) {RETURN(res);} i = getmultiline(param, SERVER, buf, sizeof(buf) - 1, NULL, NULL); if( i < 3 ) {RETURN(671);} buf[i] = 0; if(strncasecmp((char *)buf, "220", 3)||!strncasecmp((char *)buf+4, "PROXY", 5)){RETURN(672);} i = sprintf((char *)buf, "EHLO ["); i += myinet_ntop(*SAFAMILY(¶m->sinsl), SAADDR(¶m->sinsl), (char *)buf+strlen((char *)buf), 64); i += sprintf((char *)buf+strlen((char *)buf), "]\r\n"); if(socksend(param, param->remsock, buf, i, conf.timeouts[STRING_S])!= i) {RETURN(673);} param->statscli64+=i; param->nwrites++; login = 0; do { i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', conf.timeouts[STRING_L]); if(i < 1) break; buf[i] = 0; if(strstr((char *)buf, "LOGIN")) login |= 1; if(strstr((char *)buf, "PLAIN")) login |= 2; } while (i > 3 && buf[3] == '-'); if(i<3) {RETURN(672);} if(!command || (*param->extusername && *param->extpassword)){ if(!*param->extusername || !*param->extpassword || !login){ socksend(param, param->clisock, (unsigned char *)"235 auth required\r\n", 19,conf.timeouts[STRING_S]); } if ((login & 1)) { socksend(param, param->remsock, (unsigned char *)"AUTH LOGIN\r\n", 12, conf.timeouts[STRING_S]); param->statscli64+=12; param->nwrites++; i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', conf.timeouts[STRING_L]); if(i<4 || strncasecmp((char *)buf, "334", 3)) {RETURN(680);} if(!en64(param->extusername, buf, (int)strlen((char *)param->extusername), (int)sizeof(buf))) {RETURN(683);} socksend(param, param->remsock, buf, (int)strlen((char *)buf), conf.timeouts[STRING_S]); socksend(param, param->remsock, (unsigned char *)"\r\n", 2, conf.timeouts[STRING_S]); param->statscli64+=(i+2); param->nwrites+=2; i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', conf.timeouts[STRING_L]); if(i<4 || strncasecmp((char *)buf, "334", 3)) {RETURN(681);} if(!en64(param->extpassword, buf, (int)strlen((char *)param->extpassword), (int)sizeof(buf))) {RETURN(683);} socksend(param, param->remsock, buf, (int)strlen((char *)buf), conf.timeouts[STRING_S]); socksend(param, param->remsock, (unsigned char *)"\r\n", 2, conf.timeouts[STRING_S]); param->statscli64+=(i+2); param->nwrites+=2; } else if((login & 2)){ socksend(param, param->remsock, (unsigned char *)"AUTH PLAIN\r\n", 12, conf.timeouts[STRING_S]); param->statscli64+=(12); param->nwrites++; i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', conf.timeouts[STRING_L]); if(i<4 || strncasecmp((char *)buf, "334", 3)) {RETURN(682);} *username = 0; i = (int)strlen((char *)param->extusername) + 1; res = (int)strlen((char *)param->extpassword); if(i + 1 + res >= (int)sizeof(username)) {RETURN(683);} memcpy(username+1, param->extusername, i); i++; memcpy(username + i, param->extpassword, res); i+=res; if(!en64(username, buf, i, (int)sizeof(buf))) {RETURN(683);} i = (int)strlen((char *)buf); socksend(param, param->remsock, buf, i, conf.timeouts[STRING_S]); socksend(param, param->remsock, (unsigned char *)"\r\n", 2, conf.timeouts[STRING_S]); param->statscli64+=(i+2); param->nwrites+=2; } if(command) { i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', conf.timeouts[STRING_L]); } res = 1; } if(command) { res = 1; #ifndef WITHMAIN if(!strncasecmp(command, "MAIL", 4) || !strncasecmp(command, "RCPT", 4) || !strncasecmp(command, "STARTTLS", 8) || !strncasecmp(command, "TURN", 4)){ int clen = (int)strlen(command), csize = clen + 1; res = handlehdrfilterscli(param, (unsigned char **)&command, &csize, 0, &clen); if(res != PASS) { if(res == HANDLED) res = 2; else RETURN(677); } else if(!strncasecmp(command, "STARTTLS", 8) || !strncasecmp(command, "TURN", 4)) res = 22; } #endif i = (int)strlen(command); if(res != 2) socksend(param, param->remsock, (unsigned char *)command, i, conf.timeouts[STRING_S]); } #ifndef WITHMAIN if(param->nhdrfilterscli || param->nhdrfilterssrv || param->ndatfilterscli || param->ndatfilterssrv){ do { if(res == 22) RETURN (mapsocket(param, 180)); if(res != 2 && (res = readreply(param)) <= 0) break; if(res == 221) RETURN(0); if(res == 354) res = readdata(param); else res = readcommand(param); } while(res > 0); if(res == 22) if(res >= 0) RETURN(0); RETURN(676); } else #endif RETURN (mapsocket(param, 180)); CLEANRET: if(*param->hostname&&*param->extusername) { sprintf((char *)buf, "%.128s@%.128s%c%hu", param->extusername, param->hostname, *SAPORT(¶m->sinsr)==587?0:':',ntohs(*SAPORT(¶m->sinsr))); dolog(param, buf); } else dolog(param, NULL); if(param->clisock != INVALID_SOCKET) { if ((param->res > 0 && param->res < 100) || (param->res > 661 && param->res <700)) socksend(param, param->clisock, (unsigned char *)"571 \r\n", 6,conf.timeouts[STRING_S]); } if(command) free(command); return (NULL); } #ifdef WITHMAIN struct proxydef childdef = { smtppchild, 587, 0, S_SMTPP, " -hdefault_host[:port] - use this host and port as default if no host specified\n -x - disable STARTTLS\n" }; #include "proxymain.c" #endif #else /* Built without this proxy of its own. The command and the redirect naming it are the STARTTLS proxy speaking that protocol, which negotiates the same way and passes the session on: what "tlspr -Xsmtp" does, under the name a configuration already uses. */ void * smtppchild(struct clientparam * param){ param->starttls = S_SMTPP; return (void *)tlsprchild; } #endif