summaryrefslogtreecommitdiff
path: root/src/qmail-qmtpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmail-qmtpd.c')
-rw-r--r--src/qmail-qmtpd.c358
1 files changed, 358 insertions, 0 deletions
diff --git a/src/qmail-qmtpd.c b/src/qmail-qmtpd.c
new file mode 100644
index 0000000..f2a49cb
--- /dev/null
+++ b/src/qmail-qmtpd.c
@@ -0,0 +1,358 @@
+#include <unistd.h>
+#include "stralloc.h"
+#include "buffer.h"
+#include "qmail.h"
+#include "now.h"
+#include "str.h"
+#include "fmt.h"
+#include "env.h"
+#include "sig.h"
+#include "case.h"
+#include "exit.h"
+#include "scan.h"
+#include "rcpthosts.h"
+#include "auto_qmail.h"
+#include "control.h"
+#include "received.h"
+#include "ip.h"
+#include "byte.h"
+
+#define PORT_QMTPS "6209" // string compare
+#define QMTP_SIZE 200000000 // 23 MB transfer limit
+#define QMTP_TIMEOUT 3600 // 1 hour
+
+/** @file qmail-qmtpd.c -- QMTP/QMTPS server
+ @brief requires sslserver */
+
+void badproto() { _exit(100); }
+void resources() { _exit(111); }
+
+ssize_t safewrite(int fd,char *buf,int len)
+{
+ int r;
+ r = write(fd,buf,len);
+ if (r <= 0) _exit(0);
+ return r;
+}
+
+char outbuf[BUFSIZE_LINE];
+buffer bo = BUFFER_INIT(safewrite,1,outbuf,sizeof(outbuf));
+
+ssize_t saferead(int fd,char *buf,int len)
+{
+ int r;
+ buffer_flush(&bo);
+ r = read(fd,buf,len);
+ if (r <= 0) _exit(0);
+ return r;
+}
+
+char inbuf[BUFSIZE_MESS]; // at least 2*outbuf
+buffer bi = BUFFER_INIT(saferead,0,inbuf,sizeof(inbuf));
+
+unsigned long getlen()
+{
+ unsigned long len = 0;
+ char ch;
+ for (;;) {
+ buffer_get(&bi,&ch,1);
+ if (ch == ':') return len;
+ if (ch < '0' || ch > '9') resources();
+ if (len > QMTP_SIZE) resources();
+ len = 10 * len + (ch - '0');
+ }
+}
+
+void getcomma()
+{
+ char ch;
+ buffer_get(&bi,&ch,1);
+ if (ch != ',') badproto();
+}
+
+unsigned int databytes = 0;
+unsigned int bytestooverflow = 0;
+struct qmail qq;
+
+char buf[BUFSIZE_LINE]; // sender/recipient buffer
+char bufd[BUFSIZE_MESS]; // temporary data buffer
+char buf2[BUFFER_SMALL]; // QMTP message buffer
+
+char *remotehost;
+char *remoteinfo;
+char *remoteip;
+char *localport;
+char *local;
+
+stralloc failure = {0};
+stralloc protocol = {0};
+stralloc tlsinfo = {0};
+
+char *relayclient;
+int relayclientlen = 0;
+
+char *ucspitls;
+char *tlsversion;
+char *cipher;
+char *cipherperm;
+char *cipherused;
+char *clientdn;
+char *clientcn;
+char *dnemail;
+
+int seentls = 0;
+
+int modssl_info()
+{
+ tlsversion = env_get("SSL_PROTOCOL");
+ if (!tlsversion) return 0;
+ seentls = 1;
+
+ cipher = env_get("SSL_CIPHER");
+ if (!cipher) cipher = "unknown";
+ cipherperm = env_get("SSL_CIPHER_ALGKEYSIZE");
+ if (!cipherperm) cipherperm = "unknown";
+ cipherused = env_get("SSL_CIPHER_USEKEYSIZE");
+ if (!cipherused) cipherused = "unknown";
+ clientdn = env_get("SSL_CLIENT_S_DN");
+ if (clientdn) seentls = 2;
+ else
+ clientdn = "none";
+
+ if (!stralloc_copys(&tlsinfo,tlsversion)) resources();
+ if (!stralloc_cats(&tlsinfo,": ")) resources();
+ if (!stralloc_cats(&tlsinfo,cipher)) resources();
+ if (!stralloc_cats(&tlsinfo," [")) resources();
+ if (!stralloc_cats(&tlsinfo,cipherused)) resources();
+ if (!stralloc_cats(&tlsinfo,"/")) resources();
+ if (!stralloc_cats(&tlsinfo,cipherperm)) resources();
+ if (!stralloc_cats(&tlsinfo,"] \n")) resources();
+ if (!stralloc_cats(&tlsinfo," DN=")) resources();
+ if (!stralloc_cats(&tlsinfo,clientdn)) resources();
+ if (!stralloc_0(&tlsinfo)) resources();
+
+ if (!stralloc_append(&protocol,"S")) resources();
+
+ if (seentls == 2) {
+ clientcn = env_get("SSL_CLIENT_S_DN_CN");
+ remoteinfo = clientcn ? clientcn : clientdn;
+ dnemail = env_get("SSL_CLIENT_S_DN_Email");
+ if (!dnemail) dnemail = "unknown";
+ if (!stralloc_append(&protocol,"A")) resources();
+ relayclient = "";
+ }
+ return 1;
+}
+
+int main()
+{
+ char ch;
+ int i;
+ unsigned long biglen;
+ unsigned long dlen;
+ unsigned long len;
+ int flagdos;
+ int flagsenderok;
+ int flagbother;
+ unsigned long qp;
+ char *result;
+ char *x;
+ unsigned long u;
+
+ sig_pipeignore();
+ sig_alarmcatch(resources);
+ alarm(QMTP_TIMEOUT);
+
+ if (chdir(auto_qmail) == -1) resources();
+
+ if (control_init() == -1) resources();
+ if (rcpthosts_init() == -1) resources();
+
+ if (control_readint(&databytes,"control/databytes") == -1) resources();
+ x = env_get("DATABYTES");
+ if (x) { scan_ulong(x,&u); databytes = u; }
+ if (!(databytes + 1)) --databytes;
+
+ relayclient = env_get("RELAYCLIENT");
+ remotehost = env_get("TCP6REMOTEHOST");
+ if (!remotehost) remotehost = env_get("TCPREMOTEHOST");
+ if (!remotehost) remotehost = "unknown";
+ remoteinfo = env_get("TCP6REMOTEINFO");
+ if (!remoteinfo) remoteinfo = env_get("TCPREMOTEINFO");
+ remoteip = env_get("TCP6REMOTEIP");
+ if (!remoteip) remoteip = env_get("TCPREMOTEIP");
+ if (remoteip && byte_equal(remoteip,7,V4MAPPREFIX)) remoteip = remoteip + 7;
+ if (!remoteip) remoteip = "unknown";
+ local = env_get("TCP6LOCALHOST");
+ if (!local) local = env_get("TCPLOCALHOST");
+ if (!local) local = env_get("TCP6LOCALIP");
+ if (!local) local = env_get("TCPLOCALIP");
+ if (!local) local = "unknown";
+ localport = env_get("TCP6LOCALPORT");
+ if (!localport) localport = env_get("TCPLOCALPORT");
+ if (!localport) localport = "0";
+
+ if (!stralloc_copys(&protocol,"QMTP")) resources();
+ if (!case_diffs(localport,PORT_QMTPS))
+ if (!modssl_info()) resources();
+
+ if (relayclient)
+ relayclientlen = str_len(relayclient);
+
+ for (;;) { // https://cr.yp.to/proto/qmtp.txt
+ if (!stralloc_copys(&failure,"")) resources();
+ flagsenderok = 1;
+
+ len = getlen(); // package to read
+ if (len == 0) badproto();
+
+ if (databytes) bytestooverflow = databytes + 1;
+ if (qmail_open(&qq) == -1) resources();
+ qp = qmail_qp(&qq);
+
+ buffer_get(&bi,&ch,1);
+ --len;
+ if (ch == 10) flagdos = 0;
+ else if (ch == 13) flagdos = 1;
+ else badproto();
+
+ /* no fakehelo, no spfinfo */
+
+ received(&qq,protocol.s,local,remoteip,remotehost,remoteinfo,(char *) 0,tlsinfo.s,(char *) 0);
+
+ /* XXX: check for loops? only if len is big? - message */
+
+ if (flagdos)
+ while (len > 0) {
+ buffer_get(&bi,&ch,1);
+ --len;
+ while ((ch == 13) && len) {
+ buffer_get(&bi,&ch,1);
+ --len;
+ if (ch == 10) break;
+ if (bytestooverflow) if (!--bytestooverflow) qmail_fail(&qq);
+ qmail_put(&qq,"\015",1);
+ }
+ if (bytestooverflow) if (!--bytestooverflow) qmail_fail(&qq);
+ qmail_put(&qq,&ch,1);
+ }
+ else {
+ if (databytes)
+ if (len > databytes) {
+ bytestooverflow = 0;
+ qmail_fail(&qq);
+ }
+ while (len > 0) { /* XXX: DJB: could speed this up, obviously; FEH: done */
+ dlen = (len < BUFSIZE_LINE) ? len : BUFSIZE_LINE;
+ buffer_get(&bi,bufd,dlen);
+ qmail_put(&qq,bufd,dlen);
+ len -= dlen;
+ }
+ }
+ getcomma();
+
+ len = getlen(); // QMTP sender
+
+ if (len >= BUFSIZE_LINE) {
+ buf[0] = 0;
+ flagsenderok = 0;
+ for (i = 0; i < len; ++i)
+ buffer_get(&bi,&ch,1);
+ }
+ else {
+ for (i = 0; i < len; ++i) {
+ buffer_get(&bi,buf + i,1);
+ if (!buf[i]) flagsenderok = 0;
+ }
+ buf[len] = 0;
+ }
+ getcomma();
+
+ flagbother = 0;
+ qmail_from(&qq,buf);
+ if (!flagsenderok) qmail_fail(&qq);
+
+ biglen = getlen(); // QMTP recipients
+ while (biglen > 0) {
+ if (!stralloc_append(&failure,"")) resources();
+
+ len = 0;
+ for (;;) {
+ if (!biglen) badproto();
+ buffer_get(&bi,&ch,1);
+ --biglen;
+ if (ch == ':') break;
+ if (ch < '0' || ch > '9') resources();
+ if (len > QMTP_SIZE) resources();
+ len = 10 * len + (ch - '0');
+ }
+ if (len >= biglen) badproto();
+ if (len + relayclientlen >= BUFSIZE_LINE) {
+ failure.s[failure.len - 1] = 'L';
+ for (i = 0; i < len; ++i)
+ buffer_get(&bi,&ch,1);
+ }
+ else {
+ for (i = 0; i < len; ++i) {
+ buffer_get(&bi,buf + i,1);
+ if (!buf[i]) failure.s[failure.len - 1] = 'N';
+ }
+ buf[len] = 0;
+
+ if (relayclientlen)
+ str_copy(buf + len,relayclient);
+ if (!relayclient)
+ switch (rcpthosts(buf,len)) {
+ case -1: resources();
+ case 0: failure.s[failure.len - 1] = 'D';
+ }
+
+ if (!failure.s[failure.len - 1]) {
+ qmail_to(&qq,buf);
+ flagbother = 1;
+ }
+ }
+ getcomma();
+ biglen -= (len + 1);
+ }
+ getcomma();
+
+ if (!flagbother) qmail_fail(&qq);
+ result = qmail_close(&qq);
+ if (!flagsenderok) result = "D Unacceptable sender (#5.1.7)";
+ if (databytes) if (!bytestooverflow) result = "D Sorry, that message size exceeds my databytes limit (#5.3.4)";
+
+ if (*result)
+ len = str_len(result);
+ else {
+ /* success! */
+ len = 0;
+ len += fmt_str(buf2 + len,"K Ok ");
+ len += fmt_ulong(buf2 + len,(unsigned long) now());
+ len += fmt_str(buf2 + len," qp ");
+ len += fmt_ulong(buf2 + len,qp);
+ buf2[len] = 0;
+ result = buf2;
+ }
+
+ len = fmt_ulong(buf,len);
+ buf[len++] = ':';
+ len += fmt_str(buf + len,result);
+ buf[len++] = ',';
+
+ for (i = 0; i < failure.len; ++i)
+ switch (failure.s[i]) {
+ case 0:
+ buffer_put(&bo,buf,len);
+ break;
+ case 'D':
+ buffer_puts(&bo,"66:D Sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1),");
+ break;
+ default:
+ buffer_puts(&bo,"46:D Sorry, I can't handle that recipient (#5.1.3),");
+ break;
+ }
+
+ /* bo will be flushed when we read from the network again */
+ }
+}