nightmaremail

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit cdbc7bc368ba3d459181b8307c9949c7f578fdb6
parent 27a8117b249633808e6a5d049f29714342f368ee
Author: Ellenor Malik <ellenor@umbrellix.net>
Date:   Fri, 30 Sep 2022 13:37:48 +0000

At this point, I folded in my quest to avoid typeallocs in mxf-remote and merged in the parent qmail's gen_alloc (as typealloc)

Diffstat:
MMakefile.legacy | 24++++++++++++++++--------
Dautobuilds/cirrus.yml | 2--
Dautobuilds/github.yml | 2--
Dautobuilds/solaris.yml | 2--
Mconf-cc | 2+-
Mdoc/THANKS.mxf | 29++++++++++++++++++++++++++++-
Adoc/man/mxf-remote.8 | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdoc/man/qmail-control.9 | 3+++
Adoc/pictures/macro.qmip | 9+++++++++
Minclude/control.h | 8+++++++-
Minclude/fmt.h | 1+
Ainclude/netstrings.h | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/control.c | 194++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/fmt_ulong.c | 12++++++++++++
Asrc/mxf-remote/NOTES-mxfr.md | 40++++++++++++++++++++++++++++++++++++++++
Msrc/mxf-remote/errs.c | 42+++++++++++++++++++++++++++++++++++++-----
Msrc/mxf-remote/errs.h | 5+++++
Msrc/mxf-remote/ga_foreach.h | 3+++
Msrc/mxf-remote/mxf-remote.c | 352+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/mxf-remote/mxf-remote.h | 51++++++++++++++++++++++++++++++++++++++++++++++++++-
Asrc/mxf-remote/typealloc.h | 7+++++++
Asrc/mxf-remote/typeallocdefs.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mxf-remote/uthash.h | 963+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/netstrings.c | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/netstrings.o | 0
Msrc/qmail-qmqpd.c | 24+++++++++++++++++++++++-
Msrc/qmail-qmtpd.c | 77++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/qmail-rspawn.c | 4+---
Msrc/qmail-smtpd.c | 34+++++++++++++++++-----------------
Msrc/realrcptto.c | 1+
30 files changed, 1937 insertions(+), 201 deletions(-)

diff --git a/Makefile.legacy b/Makefile.legacy @@ -1273,10 +1273,11 @@ include/timeoutread.h include/timeoutwrite.h include/auto_qmail.h include/contro qmail-qmqpd: \ load src/qmail-qmqpd.o src/received.o src/date822fmt.o src/qmail.o src/auto_qmail.o \ -src/env.a src/substdio.a src/sig.a src/error.a src/wait.a src/fd.a src/str.a src/datetime.a src/fs.a - ./load qmail-qmqpd src/received.o src/date822fmt.o src/qmail.o \ +src/netstrings.o src/env.a src/substdio.a src/sig.a src/error.a src/wait.a src/fd.a \ +src/str.a src/datetime.a src/fs.a + ./load qmail-qmqpd src/received.o src/date822fmt.o src/qmail.o src/netstrings.o \ src/auto_qmail.o src/env.a src/substdio.a src/sig.a src/error.a src/wait.a src/fd.a \ - src/str.a src/datetime.a src/fs.a + src/str.a src/datetime.a src/fs.a doc/man/qmail-qmqpd.0: \ doc/man/qmail-qmqpd.8 @@ -1288,11 +1289,11 @@ include/sig.h include/substdio.h include/readwrite.h include/exit.h include/now. qmail-qmtpd: \ load src/qmail-qmtpd.o src/rcpthosts.o src/control.o src/constmap.o src/received.o \ -src/date822fmt.o src/qmail.o src/cdb.a src/fd.a src/wait.a src/datetime.a src/open.a \ -src/getln.a src/sig.a src/case.a src/env.a src/stralloc.a src/substdio.a src/error.a \ -src/str.a src/fs.a src/auto_qmail.o src/auto_usera.o src/auto_break.o src/realrcptto.o \ -src/case.a src/stralloc.a src/error.a - ./load qmail-qmtpd src/rcpthosts.o src/control.o src/constmap.o \ +src/netstrings.o src/date822fmt.o src/qmail.o src/cdb.a src/fd.a src/wait.a src/datetime.a \ +src/open.a src/getln.a src/sig.a src/case.a src/env.a src/stralloc.a src/substdio.a \ +src/error.a src/str.a src/fs.a src/auto_qmail.o src/auto_usera.o src/auto_break.o \ +src/realrcptto.o src/case.a src/stralloc.a src/error.a + ./load qmail-qmtpd src/rcpthosts.o src/control.o src/constmap.o src/netstrings.o \ src/received.o src/date822fmt.o src/qmail.o src/cdb.a src/fd.a src/wait.a \ src/datetime.a src/open.a src/getln.a src/sig.a src/case.a src/env.a src/stralloc.a \ src/substdio.a src/error.a src/str.a src/fs.a src/auto_qmail.o \ @@ -1937,3 +1938,10 @@ compile src/wait_nohang.c include/haswaitp.h src/wait_pid.o: \ compile src/wait_pid.c include/error.h include/haswaitp.h ./compile src/wait_pid.c + +# Because we may not have substdio and stralloc in a skalibs future, +# we do not depend on them existing here. If you are not USING_SKALIBS, +# you do need them. +src/netstrings.o: \ +compile src/netstrings.c include/netstrings.h include/fmt.h + ./compile src/netstrings.c diff --git a/autobuilds/cirrus.yml b/autobuilds/cirrus.yml @@ -1 +0,0 @@ -../.cirrus.yml -\ No newline at end of file diff --git a/autobuilds/github.yml b/autobuilds/github.yml @@ -1 +0,0 @@ -../.github/workflows/ccpp.yml -\ No newline at end of file diff --git a/autobuilds/solaris.yml b/autobuilds/solaris.yml @@ -1 +0,0 @@ -../.github/workflows/solaris.yml -\ No newline at end of file diff --git a/conf-cc b/conf-cc @@ -1,3 +1,3 @@ -cc -O3 -Iinclude/ -Iinclude/fmxs -I/package/prog/skalibs/include +cc -O3 -Iinclude/ -Iinclude/fmxs This will be used to compile .c files. diff --git a/doc/THANKS.mxf b/doc/THANKS.mxf @@ -1,7 +1,9 @@ ...:: Thanks Changes to notqmail that make notqmail NightmareMail are licenced to you, the user, as termed in doc/LICENSE.mxf. This is similar to the CDDL, except there -is a resumptibility clause. +is a resumptibility clause. Please take into mind Bernstein's softwarelaw +article - you may still use the program while your licence is terminated. The +licence only governs Notqmail itself appears to be available under the Unlicense. @@ -33,3 +35,28 @@ https://code.dogmap.org./qmail/#realrcptto, is also incorporated into MXF. Paul Jarc placed this patch into the public domain. Thanks Paul! This should reduce backscatter spam, which is a problem my friend Lightning at Ultradian Club has been experiencing with his mailserver. + +Our replacement for qmail-remote, MXF Remote, includes sections lifted +not-quite-verbatim from qmail-remote. Notably, Connector uses the control +file procedure from qmail-remote with few modifications, having split it +to get routes for a protocol and to fetch actual protocols from a new ctrl +file. + + ...:: Libraries, headers, and other dependencies - supply chain statement + +For the whole program: + +Statically-linked copies of Nightmare Mail, compiled with -DUSING_SKALIBS +and linked against libskarnet.a, include software written by Laurent +Bercot de skarnet.org. + +Statically-linked copies of MXF Remote, our replacement for qmail-remote, +include software written by Laurent Bercot de skarnet.org. + +All copies of MXF Remote, our replacement for qmail-remote, include software +written by Troy D. Hansen, who has indicated that a link to +https://troydhanson.github.io/uthash/ will be sufficient credit. + +All copies of MXF Remote, our replacement for qmail-remote, depend on +a UCSPI StartTLS and TLS client. The compiled-in default is to use +software written by Laurent Bercot de skarnet.org. diff --git a/doc/man/mxf-remote.8 b/doc/man/mxf-remote.8 @@ -0,0 +1,59 @@ +.Dd Sep 28, 2022 +.Dt mxf-remote 8 +.Os +.Sh Name +.Nm mxf-remote +.Nd part of the NightmareMail mail transfer suite which refers messages to be dequeued for foreign hosts to appropriate mail protocol clients +.Sh Synopsis +.Nm mxf-remote +.Ar host +.Ar mailfrom +.Ar rcptto +.Op Ar rcptto... +.Sh Description +.Nm +uses its control files to decide what protocols it supports. +If the host argument is a domain, and there are no <protocol>routes entries calling the domain out, +.Nm +looks up SRV records for the TCP protocol and the mail transfer protocols +.Nm +supports, and MX records for the domain. MX records that conform to MXPS are then mapped against the slicemap in the third field of control/protocols. In default of a slice, they are assigned to slice 0, which you should have set as your SMTP client. + +Having obtained a domain or list of domains to look up, or in default of any response to SRV or MX records (in which case, all protocols are tried), AAAA and A lookups are tried. If a mail exchanger can be found, and it is not us, it is then tried. If a connection succeeds within timeoutconnect, the protocol program mentioned in control/protocol (field 6 of the appropriate protocol) is then executed, using the compiled-in paths to a TLS or UCSPITLS client if it is required by the protocol in question to launch them, with file descriptors 6 and 7 set to the TCP connection and environment variables set as if the program was running under a UCSPI-TCP tool. +.Sh "Control files" +.Bl -tag -width "-w size " +.It <conf-qmail>/control/timeoutconnect +As +.Nm +makes connections on behalf of UCSPITLSCLIENT, TLSCLIENT (compiled in) and the protocol program, it must know how long to wait before considering the connection dead and moving onto the next address, or the next protocol if there are no more addresses, or to report non-delivery if we have reached the last protocol without a response. +.It <conf-qmail>/control/protocols +.Nm +does not perform protocol conversations itself. Instead, it calls a program, defined in control/protocols. In default of this file, a QMTP client is tried with and without TLS, then an SMTP client is tried under a STARTTLS client. If none of these are properly installed at your site, remote delivery will fail. In order, the possible parameters in this file are: service (SRV service), defport (port in default of specific), slice (MXPS slice - may be -1), implicit TLS (Y/N), explicit TLS (Y/N), program. +.It <conf-qmail>/control/ucspitlsclient +.It <conf-qmail>/control/tlsclient +These control files govern explicit and implicit TLS. The default listed in the headers is to use slashpackage s6-ucspitlsclient and s6-tlsc. If you would like to use some other explicit or implicit TLS client, the option to do so is provided through these control files. The facility to pass arguments through the control files is not provided, so point to a script if you would like to use custom arguments (such as sending a client certificate). +.El + +These control files are not used by +.Nm +itself, but their purpose is described here. +.Bl -tag -width "-w size " +.It <conf-qmail>/control/helohost +.It <conf-qmail>/control/timeoutremote +This is the maximum amount of time that the protocol conversation is allowed to take. The client program should set a fuse around the time it begins execution, and die with an exit value that indicates timeout should the conversation go on longer than this many seconds. +.It <conf-qmail>/control/<protocol>routes +qmtpsroutes, qmtproutes and smtproutes are used by +.Nm +for one purpose - short circuiting SRV and MX records which are unnecessary, and moving straight to A/AAAA records and performing the connection. +.El +.Sh See also +.Bl -tag -width "-w size " +.It Xr qmail-remote 8 +the original implementation of qmail-remote +.It Xr mxf-remote-smtpc 8 +the smtp client in MXF Remote +.It Xr addresses 5 +.It Xr envelopes 5 +.It Xr qmail-send 5 +.It Xr qmail-smtpd 5 +.It Xr qmail-control 5 diff --git a/doc/man/qmail-control.9 b/doc/man/qmail-control.9 @@ -64,6 +64,8 @@ control default used by .I rcpthosts \fR(none) \fRqmail-smtpd .I smtpgreeting \fIme \fRqmail-smtpd .I smtproutes \fR(none) \fRqmail-remote +.I <protocol>routes \fR(none) \fRmxf-remote-<protocol>c +.I protocols \fR qmtps, qmtp, smtp \fRmxf-remote .I timeoutconnect \fR60 \fRqmail-remote .I timeoutremote \fR1200 \fRqmail-remote .I timeoutsmtpd \fR1200 \fRqmail-smtpd @@ -74,6 +76,7 @@ control default used by qmail-inject(8), qmail-qmqpc(8), qmail-remote(8), +mxf-remote(8), qmail-send(8), qmail-showctl(8), qmail-smtpd(8) diff --git a/doc/pictures/macro.qmip b/doc/pictures/macro.qmip @@ -0,0 +1,9 @@ + + +# The NightmareMail Pictures » The Macro Picture » QMail Internal Protocol + +(This document is prefaced with a newline so that it might be rendered correctly as Headered Markdown. It is regular Markdown, and further, any drawings within are conformed to a 70 character terminal, so that they might be rendered correctly doing `less` on an 80 character terminal.) + +This is very macro (i.e. zoomed in heavily), and there's really little to it. + +Programs that en/dequeue messages in the qmail system seem to have this protocol that faces other qmail programs on the same system. It resembles the error reporting in QMTPD and QMQPD - K, Z and D (oKay (25x), snooZe (4xx), Denied (5xx)) - although it doesn't use netstrings as it's not a network protocol and is CRLF delimited. I call this protocol QMIP. diff --git a/include/control.h b/include/control.h @@ -1,9 +1,15 @@ #ifndef CONTROL_H #define CONTROL_H +#ifdef USING_SKALIBS +#include <skalibs/stralloc.h> +#else +#include "stralloc.h" +#endif + extern int control_init(); extern int control_readline(); -extern int control_rldef(); +extern int control_rldef(stralloc *sa, char *fn, int flagme, char *def); extern int control_readint(); extern int control_readfile(); diff --git a/include/fmt.h b/include/fmt.h @@ -9,6 +9,7 @@ extern unsigned int fmt_uint(char *, unsigned int); extern unsigned int fmt_uint0(char *, unsigned int, unsigned int); extern unsigned int fmt_ulong(char *, unsigned long); +extern unsigned int fmt_ulonglong(char *, unsigned long long); extern unsigned int fmt_str(char *, char *); extern unsigned int fmt_strn(char *, char *, unsigned int); diff --git a/include/netstrings.h b/include/netstrings.h @@ -0,0 +1,73 @@ +#ifndef NETSTRINGS_H +#define NETSTRINGS_H + +/* include/netstrings.h - header, NightmareMail new code for handling + * netstrings + * + * This file may be used, chopped, screwed and copied without heed to + * the general licence conditions of NightmareMail itself. + * + * Alternatively, it may be used under the same conditions as NMM + * itself. + * + * This comment is probably a public domain dedication with MCDDL in + * the alternate. + */ + +/* include/netstrings.h requires Laurent's buffer or Daniel's substdio + * because it defines methods of accessing netstrings fed to it on + * ssios or bios. + * + * Those methods are then elaborated in netstrings.c. + */ + +#ifdef USING_SKALIBS +#include <skalibs/buffer.h> +#include <skalibs/stralloc.h> +#else +#include "substdio.h" +#include "stralloc.h" +#endif + + +// XXX this should be moved to substdio.h +#ifdef USING_SKALIBS +#define _BUFFER buffer +#define _BUFFER_GET(bio, where, len) buffer_get(bio, where, len) +#define _BUFFER_PUT(bio, where, len) buffer_put(bio, where, len) +#else +#define _BUFFER substdio +#define _BUFFER_GET(bio, where, len) substdio_get(bio, where, len) +#define _BUFFER_PUT(bio, where, len) substdio_put(bio, where, len) +#endif + +#define NS_BIOERR -1 +#define NS_PROTOCOL -2 +#define NS_TOOMUCH -3 +#define NS_INVAL -4 +#define NS_SUCCESS 0 + +/* signed char ns_getlength. Returns information as to its success + * in putting the length of the incoming netstring in *length. + * Returns NS_INVAL if called with a nullptr. + */ +signed char ns_getlength (_BUFFER *bio, unsigned long long *length); + +/* signed char ns_getcomma. Returns information as to whether the + * next byte is an ASCII comma. + * Retval: Success if so, protocol if not. + */ +signed char ns_getcomma (_BUFFER *bio); + +/* signed char ns_put. Returns information as to its success in + * pushing the bytes buffer, of length length, onto the buffered I/O + * bio, as a netstring. + */ +signed char ns_put (_BUFFER *bio, char *buf, unsigned long long length); + +/* signed char ns_print. Returns information as to its success in + * pushing the bytes buffer, of length length, onto the fildes fd + */ +signed char ns_print (int fd, char *buf, unsigned long long length); + +#endif diff --git a/src/control.c b/src/control.c @@ -3,129 +3,141 @@ #include "readwrite.h" #include "open.h" #include "getln.h" +#ifdef USING_SKALIBS +#include <skalibs/stralloc.h> +#else #include "stralloc.h" +#endif #include "substdio.h" #include "error.h" #include "alloc.h" #include "scan.h" static char inbuf[64]; +#ifdef USING_SKALIBS +static stralloc line = STRALLOC_ZERO; +static stralloc me = STRALLOC_ZERO; +#else static stralloc line = {0}; static stralloc me = {0}; +#endif static int meok = 0; -static void striptrailingwhitespace(sa) -stralloc *sa; +static void striptrailingwhitespace(stralloc *sa) { - while (sa->len > 0) - switch(sa->s[sa->len - 1]) - { - case '\n': case ' ': case '\t': - --sa->len; - break; - default: - return; - } + while (sa->len > 0) + switch(sa->s[sa->len - 1]) + { + case '\n': case ' ': case '\t': + --sa->len; + break; + default: + return; + } } int control_init() { - int r; - r = control_readline(&me,"control/me"); - if (r == 1) meok = 1; - return r; + int r; + r = control_readline(&me,"control/me"); + if (r == 1) meok = 1; + return r; } -int control_rldef(sa,fn,flagme,def) -stralloc *sa; -char *fn; -int flagme; -char *def; +int control_rldef(stralloc *sa, char *fn, int flagme, char *def) { - int r; - r = control_readline(sa,fn); - if (r) return r; - if (flagme) if (meok) return stralloc_copy(sa,&me) ? 1 : -1; - if (def) return stralloc_copys(sa,def) ? 1 : -1; - return r; + int r; + r = control_readline(sa,fn); + if (r) return r; + if (flagme) if (meok) return stralloc_copy(sa,&me) ? 1 : -1; + if (def) return stralloc_copys(sa,def) ? 1 : -1; + return r; } -int control_readline(sa,fn) -stralloc *sa; -char *fn; +int control_readline(stralloc *sa, char *fn) { - substdio ss; - int fd; - int match; +#ifdef USING_SKALIBS + buffer ss; +#else + substdio ss; +#endif + int fd; + int match; - fd = open_read(fn); - if (fd == -1) { if (errno == error_noent) return 0; return -1; } - - substdio_fdbuf(&ss,read,fd,inbuf,sizeof(inbuf)); + fd = open_read(fn); + if (fd == -1) { if (errno == error_noent) return 0; return -1; } - if (getln(&ss,sa,&match,'\n') == -1) { close(fd); return -1; } +#ifdef USING_SKALIBS + buffer_init(&ss,buffer_read,fd,inbuf,sizeof(inbuf)); +#else + substdio_fdbuf(&ss,read,fd,inbuf,sizeof(inbuf)); +#endif - striptrailingwhitespace(sa); - close(fd); - return 1; + if (getln(&ss,sa,&match,'\n') == -1) { close(fd); return -1; } + + striptrailingwhitespace(sa); + close(fd); + return 1; } -int control_readint(i,fn) -int *i; -char *fn; +int control_readint(int *i, char *fn) { - unsigned long u; - switch(control_readline(&line,fn)) - { - case 0: return 0; - case -1: return -1; - } - if (!stralloc_0(&line)) return -1; - if (!scan_ulong(line.s,&u)) return 0; - *i = u; - return 1; + unsigned long u; + switch(control_readline(&line,fn)) + { + case 0: return 0; + case -1: return -1; + } + if (!stralloc_0(&line)) return -1; + if (!scan_ulong(line.s,&u)) return 0; + *i = u; + return 1; } -int control_readfile(sa,fn,flagme) -stralloc *sa; -char *fn; -int flagme; +int control_readfile(stralloc *sa, char *fn, int flagme) { - substdio ss; - int fd; - int match; - - if (!stralloc_copys(sa,"")) return -1; +#ifdef USING_SKALIBS + buffer ss; +#else + substdio ss; +#endif + int fd; + int match; - fd = open_read(fn); - if (fd == -1) - { - if (errno == error_noent) - { - if (flagme && meok) - { - if (!stralloc_copy(sa,&me)) return -1; - if (!stralloc_0(sa)) return -1; - return 1; - } - return 0; - } - return -1; - } + if (!stralloc_copys(sa,"")) return -1; - substdio_fdbuf(&ss,read,fd,inbuf,sizeof(inbuf)); + fd = open_read(fn); + if (fd == -1) + { + if (errno == error_noent) + { + if (flagme && meok) + { + if (!stralloc_copy(sa,&me)) return -1; + if (!stralloc_0(sa)) return -1; + return 1; + } + return 0; + } + return -1; + } - for (;;) - { - if (getln(&ss,&line,&match,'\n') == -1) break; - if (!match && !line.len) { close(fd); return 1; } - striptrailingwhitespace(&line); - if (!stralloc_0(&line)) break; - if (line.s[0]) - if (line.s[0] != '#') - if (!stralloc_cat(sa,&line)) break; - if (!match) { close(fd); return 1; } - } - close(fd); - return -1; +#ifdef USING_SKALIBS + buffer_init(&ss,buffer_read,fd,inbuf,sizeof(inbuf)); +#else + substdio_fdbuf(&ss,read,fd,inbuf,sizeof(inbuf)); +#endif + for (;;) + { + if (getln(&ss,&line,&match,'\n') == -1) break; + if (!match && !line.len) { close(fd); return 1; } + striptrailingwhitespace(&line); + if (!stralloc_0(&line)) break; + if (line.s[0]) + if (line.s[0] != '#') + if (!stralloc_cat(sa,&line)) break; + if (!match) { close(fd); return 1; } + } + close(fd); + return -1; } diff --git a/src/fmt_ulong.c b/src/fmt_ulong.c @@ -11,3 +11,15 @@ unsigned int fmt_ulong(char *s, unsigned long u) } return len; } + +unsigned int fmt_ulonglong(char *s, unsigned long long u) +{ + unsigned int len; unsigned long long q; + len = 1; q = u; + while (q > 9) { ++len; q /= 10; } + if (s) { + s += len; + do { *--s = '0' + (u % 10); u /= 10; } while(u); /* handles u == 0 */ + } + return len; +} diff --git a/src/mxf-remote/NOTES-mxfr.md b/src/mxf-remote/NOTES-mxfr.md @@ -0,0 +1,40 @@ + +# MXF-Remote Notes + +## A word on memory management and hash tables + +One of the structures we use in MXF Remote Connector is a UTHash hash table. UTHash does its own memory management and doesn't use strallocs like the rest of the program does. There will thus be two kinds of strong pointers into the heap in MXF-Remote - stralloc/genalloc from skalibs (and typealloc, which is gen_alloc from Notqmail - this is the same type of thing), and entries in UTHashes. + +We use UTHash to store information about skadns queries we make, and about fds which we select for readability (I hope that we will only ever select skadns fds for readability; client apps communicate with us the same way qmail-remote communicates with qmail-rspawn). With this, once the skadns fd raises readable, we needn't iterate over a costly array datum - we can just ask the fd hash for the fd and in nearly-constant time know that that's the skadns fd, and then ask the query hash for the query ID and in nearly-constant time get a weak pointer to the protocol that it's referring to (the strong pointer being somewhere on genalloc protocols). + +It's also conceivable that we stub out constmap and use UTHash for that (control/<srvservice>routes - static routing for protocols) too. + +### Notes in mxf-remote.c (MXF Remote Connector) + +## Globals + +Global variables, save for buffer_2_, start with a Capital Letter. Typedefs always end with _t. + +## Data structures + +protocol_t is a typedef struct containing the following fields. + + typedef struct { + char srvservice[MAXSRVLEN]; // by happy coincidence we can also use this as the prefix for control/<service>routes filenames + uint16_t defport; // default port + signed char slice; // plain mx = 0 + unsigned int tls:1; // TLSC + unsigned int starttls:1; // UCSPITLSC + char app[PATH_MAX]; + struct constmap maproutes; // control/<service>routes + } protocol_t; + +(data of type protocol_t).maproutes serves the same purpose as global struct constmap maproutes in qmail-remote.c. It is fetched at the time of inserting the protocol onto the end of genalloc Protocols. + +## A word on type management with genallocs. + +genallocs are just strallocs, and they don't contain any type information. + +## mxf-remote.c:0010 - Address mangling + +Of note here is that we leave address mangling to the client program. We do not have an addrmangle function (although one will be available, surgically taken from qmail-remote.c). diff --git a/src/mxf-remote/errs.c b/src/mxf-remote/errs.c @@ -2,18 +2,18 @@ #include <stdlib.h> extern stralloc host; -//extern buffer errbufsmall; +extern buffer buffer_2_; #define _HSC { void out(char *s) _HSC - if (buffer_puts(errbufsmall,s) == -1) _exit(0); + if (buffer_puts(buffer_2_,s) == -1) _exit(0); } void zero() _HSC - if (buffer_put(errbufsmall,"\0",1) == -1) _exit(0); + if (buffer_put(buffer_2_,"\0",1) == -1) _exit(0); } void _noreturn_ zerodie() _HSC zero(); - buffer_flush(errbufsmall); + buffer_flush(buffer_2_); _exit(0); } void outsafe(stralloc *sa) _HSC @@ -22,7 +22,7 @@ void outsafe(stralloc *sa) _HSC ch = sa->s[i]; if (ch < 33) ch = '?'; if (ch > 126) ch = '?'; - if (buffer_put(errbufsmall,&ch,1) == -1) _exit(0); + if (buffer_put(buffer_2_,&ch,1) == -1) _exit(0); } } @@ -30,10 +30,42 @@ void _noreturn_ temp_nomem() _HSC out("ZOut of memory. (#4.3.0)\n"); zerodie(); } +void _noreturn_ temp_fault() _HSC + out("ZGeneral protection violation in iopause. Attempting to dump core now. (#4.3.0)\n"); + zero(); + buffer_flush(buffer_2_); + abort(); +} +void _noreturn_ temp_runfds() _HSC + out("Zrun_fds called without any descriptors? (#4.3.0)\n"); + zerodie(); +} +void _noreturn_ temp_bug() _HSC + out("ZOS gave us EINVAL that we don't know what to do with. Attempting to dump core now. (#4.3.0)\n"); + zero(); + buffer_flush(buffer_2_); + abort(); +} +void _noreturn_ temp_intr() _HSC + out("ZSignal interruption in iopause. (#4.3.0)\n"); + zerodie(); +} void _noreturn_ temp_oserr() _HSC out("ZSystem resources temporarily unavailable. (#4.3.0)\n"); zerodie(); } +void _noreturn_ temp_hasherr() _HSC + out("Zmxf-remote tried to add an element to a hash table which already existed in that hash table. This is an embarrassing programming error. Attempting to dump core now. (#4.3.0)\n"); + zero(); + buffer_flush(buffer_2_); + abort(); +} +void _noreturn_ temp_finderr() _HSC + out("Zmxf-remote tried to find an element to a hash table which did not exist in that hash table. This is an embarrassing programming error. Attempting to dump core now. (#4.3.0)\n"); + zero(); + buffer_flush(buffer_2_); + abort(); +} void _noreturn_ temp_noconn() _HSC out("ZSorry, I wasn't able to establish a connection to the target mail exchanger. (#4.4.1)\n"); zerodie(); diff --git a/src/mxf-remote/errs.h b/src/mxf-remote/errs.h @@ -8,8 +8,13 @@ void _noreturn_ zerodie() _HSC void outsafe(stralloc *sa) _HSC void _noreturn_ temp_nomem() _HSC void _noreturn_ temp_oserr() _HSC +void _noreturn_ temp_hasherr() _HSC +void _noreturn_ temp_finderr() _HSC +void _noreturn_ temp_fault() _HSC +void _noreturn_ temp_intr() _HSC void _noreturn_ temp_noconn() _HSC void _noreturn_ temp_read() _HSC +void _noreturn_ temp_runfds() _HSC void _noreturn_ temp_dnscanon() _HSC void _noreturn_ temp_dns() _HSC void _noreturn_ temp_chdir() _HSC diff --git a/src/mxf-remote/ga_foreach.h b/src/mxf-remote/ga_foreach.h @@ -1,2 +1,5 @@ +#ifndef _MXF_REMOTE_GA_FOREACH_H +#define _MXF_REMOTE_GA_FOREACH_H #include <skalibs/genalloc.h> #define ga_foreach(type, m, idx) for (idx = 0; (idx * sizeof(type)) < (m)->len; ++idx) +#endif /* _MXF_REMOTE_GA_FOREACH_H */ diff --git a/src/mxf-remote/mxf-remote.c b/src/mxf-remote/mxf-remote.c @@ -1,45 +1,231 @@ /* src/mxf-remote/mxf-remote.c - * requires skalibs, s6-networking and s6-dns; if not, you must - * use regular qmail-remote with the attendant loss of - * functionality with qmtp, tls and ipv6. - */ + * requires skalibs, s6-networking and s6-dns; if not, you must + * use regular qmail-remote with the attendant loss of + * functionality with qmtp, tls and ipv6. + */ #include "mxf-remote.h" #include "errs.h" //#include <s6-dns/skadns.h> -tain deadline, stamp; +tain Deadline, Stamp; //protocol_t protocols[SLICE_LAST]; -genalloc protocols = GENALLOC_ZERO; // genalloc_*(protocol_t -genalloc maproutes = GENALLOC_ZERO; // genalloc_*(constmap -char errbuf[512]; -buffer buffer_2_ = BUFFER_INIT(&buffer_write, 2, errbuf, 512); -char errbufsmall[256]; -skadns_t dnsres = SKADNS_ZERO; +genalloc Protocols = GENALLOC_ZERO; // genalloc_*(protocol_t +//genalloc maproutes = GENALLOC_ZERO; // genalloc_*(constmap +char Errbuf[512]; +buffer buffer_2_ = BUFFER_INIT(&buffer_write, 2, Errbuf, 512); +//char Errbufsmall[256]; // no use yet +skadns_t Dnsres = SKADNS_ZERO; //char namechosen[513]; // That's more bytes than we will ever need. //char progname[61] = "mxf-remote"; //stralloc sa = STRALLOC_ZERO; // ? -int timeoutconnect = 60; int mtpfd; -stralloc host = STRALLOC_ZERO, sender = STRALLOC_ZERO, protocolsraw = STRALLOC_ZERO; +int Timeoutconnect = 60; int Mtpfd; +stralloc Host = STRALLOC_ZERO, Sender = STRALLOC_ZERO, Protocolsraw = STRALLOC_ZERO; +stralloc Ucspitlsclient = STRALLOC_ZERO, Tlsclient = STRALLOC_ZERO; //genalloc recips = GENALLOC_ZERO; // will be filled with strallocs //struct constmap maproutes[SLICE_LAST]; char PROTOMAPTEXT[] = "qmtps 6209 3 Y N mxf-remote-qmtpc\n" \ "qmtp 209 1 N N mxf-remote-qmtpc\n" \ "smtp 25 0 N Y mxf-remote-smtpc\n"; -protocol_t slicemap[MAXSLICES]; // on solaris this'll be about 18kB. No idea if it'll be used. +protocol_t Slicemap[MAXSLICES]; // quite a bloated thing really; maybe should use a UTHash +//genalloc Descriptors; // of type descriptor_t +int Sigfd; +descriptor_t *Descriptortab = NULL; // NOTES-mxfr - A word on memory management - strong pointers +process_t *Protegetab = NULL; // NOTES-mxfr - A word on memory management +dnsq_t *Dnsqtab = NULL; // NOTES-mxfs - A word on memory management + +// in order: pollfd (single entry), opaque (may be NULL), fdcb(descriptor_t*, void*) +int descriptor_add (struct pollfd fd, void* opaque, + int (*fdcb)(descriptor_t*, short, void*)) +{ + descriptor_t *descriptor = NULL; + + HASH_FIND_INT(Descriptortab, &(fd.fd), descriptor); + if (descriptor != NULL) temp_hasherr(); + // Otherwise... + descriptor = malloc(sizeof(descriptor_t)); // strong pointer; if we cannot add this to the hash, we must free it + descriptor->fd = fd; + descriptor->opaque = opaque; + descriptor->fdcb = fdcb; + HASH_ADD_INT(Descriptortab, fd.fd, descriptor); + return 1; // if we are returning at all, we have succeeded. +} + +// in order: descriptortab, pollfd (single entry) +// returns true on success, false on nonexistent +int descriptor_del (struct pollfd fd) +{ + descriptor_t *descriptor = NULL; + + HASH_FIND_INT(Descriptortab, &(fd.fd), descriptor); + if (descriptor == NULL) return FALSE; + // Otherwise... + HASH_DEL(Descriptortab, descriptor); + free(descriptor); + return TRUE; // if we are returning at all, we have succeeded. +} + +// in order: processtab, process ID, opaque to be passed to the report callback, report callback +int protege_add (pid_t pid, void* opaque, + int (*pdiecb)(process_t*, int, void*)) +{ + process_t *protege = NULL; + + HASH_FIND(hh, Protegetab, &pid, sizeof(pid_t), protege); + if (protege != NULL) temp_hasherr(); + // Otherwise... + protege = malloc(sizeof(descriptor_t)); // strong pointer; if we cannot add this to the hash, we must free it + protege->pid = pid; + protege->opaque = opaque; + protege->pdiecb = pdiecb; + HASH_ADD(hh, Protegetab, pid, sizeof(pid_t), protege); + return 1; // if we are returning at all, we have succeeded. +} +// in order: descriptortab, pollfd (single entry) +// returns true on success, false on nonexistent +int protege_del (pid_t pid) +{ + process_t *protege = NULL; + + HASH_FIND(hh, Protegetab, &(pid), sizeof(pid_t), protege); + if (protege == NULL) return FALSE; + HASH_DEL(Protegetab, protege); + free(protege); + return TRUE; +} + +// in order: dnsqtab, process ID, opaque to be passed to the report callback, report callback +int dnsq_add (uint16_t dnsqid, void* opaque, + int (*qcb)(skadns_t*, dnsq_t*, void*)) +{ + dnsq_t *dnsq = NULL; + + HASH_FIND(hh, Dnsqtab, &pid, sizeof(uint16_t), dnsq); + if (dnsq != NULL) temp_hasherr(); + // Otherwise... + dnsq = malloc(sizeof(descriptor_t)); // strong pointer; if we cannot add this to the hash, we must free it + dnsq->dnsq = dnsqid; + dnsq->opaque = opaque; + dnsq->qcb = qcb; + HASH_ADD(hh, Dnsqtab, dnsq, sizeof(uint16_t), dnsq); + return TRUE; // if we are returning at all, we have succeeded. +} +// in order: dnsqtab, pollfd (single entry) +// returns true on success, false on nonexistent +int dnsq_del (uint16_t dnsqid) +{ + dnsq_t *dnsq = NULL; + + HASH_FIND(hh, Dnsqtab, &(dnsqid), sizeof(uint16_t), dnsq); + if (dnsq == NULL) return FALSE; + HASH_DEL(Dnsqtab, dnsq); + free(dnsq); + return TRUE; +} + +int run_fds (tain *deadline, tain *stamp) +{ + genalloc fds = GENALLOC_ZERO; // pollfd + descriptor_t *des = NULL, *tmp = NULL; + struct pollfd *pfds = NULL; size_t pfdsiz = 0, size_t i = 0; + int pollret = 0; + HASH_ITER(hh, Descriptortab, des, tmp) { + if (!genalloc_catb((struct pollfd), &fds, des->fd, 1)) temp_nomem(); + des->fd.revents = 0; + ++pfdsiz; + } + if (pfdsiz == 0) temp_runfds(); + des = NULL; tmp = NULL; + pfds = (struct pollfd *)fds.s; + pollret = iopause_stamp(pfds, pfdsiz, deadline, stamp); + // and then we wait for iopause to do its thing + // and then iopause wakes us up + switch (pollret) { + case 0: + // we literally have nothing to do. we return. this isn't an error state. + return pollret; + break; + case -1: + // errors AGAIN, FAULT, INTR, INVAL + switch (errno) { + case EAGAIN: + return 0; // we will just get called again + break; + case EFAULT: + temp_fault(); + break; + case EINTR: + temp_intr(); // an uncaught signal has come our way + break; + case EINVAL: + temp_bug(); // invalid data + break; + } + default : + for (i = 0; i < pfdsiz; i++) { + HASH_FIND_INT(Descriptortab, &(pfds[i]->fd), des); + if (des == NULL) { + temp_finderr(); // could not find the file descriptor in our tables - what kind of wiseguy do we have on here? + } + if ((pfds[i]->fd.revents & POLLNVAL) != 0) { + // file descriptor is not known to the system; bug + temp_bug(); + } else if (pfds[i]->fd.revents != 0) { + (*(des->fdcb))(des, pfds[i]->fd.revents, des->opaque); + } + } + } + // done with fds now? right, we return the number of FDs we processed. + genalloc_free((struct pollfd), fds) + return pollret; +} + +int protocol_run (protocol_t *proto, int mtpfd) +{ + // we'll have to use a selfpipe of some description elsewhere to understand process termination +} + +int handledeadprocess () +{ + process_t *protege = NULL; + pid_t pid = 0; + int wstat; + while ((pid = wait_nohang(&wstat)) > 0) { + HASH_FIND(hh, Protegetab, &pid, sizeof(pid_t), protege); + if (protege == NULL) continue; // doesn't really matter + (*protege->pdiecb)(protege, wstat, protege->opaque); + protege_del(pid); + } + return TRUE; +} + +int checksignals (descriptor_t *descr, int revents, void* unused) +{ + int sig; + sig = selfpipe_read(); + + switch (sig) { + case SIGCHLD: + return handledeadprocess(); + break; + default : + return FALSE;// Advanced wiseguy shit + } +} int protocols_init (genalloc *protos, char *protomaptext, size_t length) { size_t i = 0; char field = 0; /* there are six fields, numbered zero to five - * their identities are: - * protoname, port, protoslice, cantls, musttls, executable-filename - */ + * their identities are: + * protoname, port, protoslice, cantls, musttls, executable-filename + */ size_t lpos = 0, fpos = 0; // record the position we are at in the line char comment = 0; //true if line is comment for (i = 0; i < MAXSLICES; i++) { - slicemap[i] = {"", 0, -1, 0, 0, ""}; + memset(&(Slicemap[i]), 0, sizeof(protocol_t)); + (Slicemap[i]).slice = -1; } if (length == 0) { protomaptext = PROTOMAPTEXT; @@ -47,7 +233,7 @@ int protocols_init (genalloc *protos, char *protomaptext, size_t length) } protocol_t proto; memset(&proto, 0, sizeof(protocol_t)); - proto.slice = 1; + proto.slice = -1; for {i = lpos = fpos = 0; i < length; ++i) { switch (protomaptext[i]) { @@ -63,10 +249,10 @@ int protocols_init (genalloc *protos, char *protomaptext, size_t length) field = 0; comment = 0; getroutes(&proto); - if (proto.srvservice[0] != 0) if (!genalloc_catb(protocol_t, protocols, &proto, 1)) return 0; - if (proto.slice > -1 && proto.slice < 16) memcpy(&(slicemap[proto.slice]), &proto, sizeof(protocol_t)); + if (proto.srvservice[0] != 0) if (!genalloc_catb(protocol_t, protos, &proto, 1)) return 0; + if (proto.slice > -1 && proto.slice < 16) memcpy(&(Slicemap[proto.slice]), &proto, sizeof(protocol_t)); memset(&proto, 0, sizeof(protocol_t)); - proto.slice = 1; + proto.slice = -1; break; case ' ': switch (comment) { @@ -105,7 +291,9 @@ int protocols_init (genalloc *protos, char *protomaptext, size_t length) switch (field) { case 5: if (fpos > PATH_MAX) temp_control(); // malformed - proto.app[fpos] = protomaptext[i]; + if (i >= length) temp_control(); // malformed + if (protomaptext[i] == 0) temp_control(); // malformed + proto.app[fpos] = protomaptext[i]; // there is no conceivable reason to have a backslash unless it's part of your fname, so we don't eat it. break; case 0: proto.srvservice[fpos] = protomaptext[++i]; @@ -190,7 +378,10 @@ int protocols_init (genalloc *protos, char *protomaptext, size_t length) // lpos and fpos only increase 1 for every 2 characters if every first character is a \ } // the music is over. we're at the end of the file, on a partial line. hopefully app is at least an app we can try. - if (proto.app[0] != 0) if (!genalloc_catb(protocol_t, &proto, 1)) return 0; + if (proto.app[0] != 0) { + getroutes(&proto); + if (!genalloc_catb(protocol_t, protos, &proto, 1)) return 0; + } return 1; } @@ -205,18 +396,23 @@ void getcontrols() if (control_init() == -1) temp_control(); //if (control_readint(&timeout,"control/timeoutremote") == -1) temp_control(); // The client application is responsible for this. - if (control_readint(&timeoutconnect,"control/timeoutconnect") == -1) + if (control_readint(&Timeoutconnect,"control/Timeoutconnect") == -1) + temp_control(); +// if (control_rldef(&helohost,"control/helohost",1,NULL) != 1) +// temp_control(); + if (control_rldef(&Ucspitlsclient,"control/ucspitlsclient",0,UCSPITLSC) != 1) temp_control(); - if (control_rldef(&helohost,"control/helohost",1,NULL) != 1) + if (control_rldef(&Ucspitlsclient,"control/tlsclient",0,TLSC) != 1) temp_control(); - switch(control_readfile(&protocolsraw,"control/protocols",0)) { + switch(control_readfile(&Protocolsraw,"control/protocols",0)) { case -1: temp_control(); case 0: - if (!protocols_init(&(protocols),"",0)) temp_nomem(); break; + if (!protocols_init(&(Protocols),"",0)) temp_nomem(); break; case 1: - if (!protocols_init(&(protocols),protocolsraw.s,protocolsraw.len)) temp_nomem(); break; + if (!protocols_init(&(Protocols),protocolsraw.s,protocolsraw.len)) temp_nomem(); break; } + // It strikes me that Protocolsraw is used nowhere else in the file. } void getroutes (protocol_t *protocol) @@ -236,32 +432,106 @@ void getroutes (protocol_t *protocol) stralloc_free(&ctrl); } +int checkdns (descriptor_t *descr, int revents, void* dnsresv) +{ + skadns_t *dnsres = (skadns_t *)dnsresv; + int upd; + upd = skadns_update(dnsres); +} + +void startdns (skadns_t *dnsres) { + if (!skadns_startf(dnsres, &Deadline, &Stamp)) { + buffer_puts(buffer_2, "ZDNS resolution startup timeout\n"); + buffer_flush(buffer_2); + exit(111); + } +} + +void stopdns (skadns_t *dnsres) { + skadns_end(dnsres); +} + // so we need to deduce the hostname to feed to tcpclient, then we need to launch tcpclient (s/qmtpc), wait on its exit, and maybe try again with a different hostname // we could also, if we wanted, be tcpclient ourselves, and just launch things under ucspitlsclient as relevant int main (int argcount, char **args) { - char **rrecips, *relayhost; - int i; + char **rrecips, *relayhost; int ; + int i, port; char skipmx = 0; protocol_t *routesproto = NULL, *proto = NULL; + sigset_t sigset; + PROG = "mxf-remote"; - tain_now(&stamp); - tain_addsecond_deadline(&deadline, &stamp, 2); // eh, the manpages at skarnet's website said to do this so I will - if (!skadns_startf(&dnsres, &deadline, &stamp)) { - buffer_puts(buffer_2, "ZDNS resolution startup timeout\n"); - buffer_flush(buffer_2); - exit(111); - } + tain_now(&Stamp); + tain_addsecond_deadline(&Deadline, &Stamp, 2); // eh, the manpages at skarnet's website said to do this so I will - sig_ignore(SIGPIPE); + // check basic configuration if (argcount < 4) perm_usage(); if (chdir(auto_qmail) == -1) temp_chdir(); - if (!stralloc_copys(&host,args[1])) temp_nomem(); + if (!stralloc_copys(&Host,args[1])) temp_nomem(); + if (!stralloc_copys(&Sender,args[2])) temp_nomem(); getcontrols(); + // trap signals + sig_ignore(SIGPIPE); + Sigfd = selfpipe_init(); // selfpipe.h + sigemptyset(&sigset); + sigaddset(&sigset, SIGCHLD); + if (0 == selfpipe_trapset(&sigset)) temp_oserr(); + struct pollfd sigpfd; + sigpfd.fd = Sigfd; + sigpfd.events = POLLIN; + sigpfd.revents = 0; + descriptor_add(sigpfd, (void *)&Protegetab, &checksignals); + + /* Of interest here is that ... NOTES-mxfr #0010 */ + + /* So at this stage we know what the protocols are and we can decide + * whether we want to skip SRV and MX. + * + * If we do, it's because we have a <protocol>routes file. protocol->maproutes + * will have the scoop. + * ga_foreach doesn't support non-pointer structs, so we make a pointer here. + */ + ga_foreach (protocol_t, &(Protocols), i) { + proto = Protocols.s + (i * sizeof(protocol_t)); + // This is lifted from qmail-remote.c:333 with minimal changes + // to fit the new system. + // I do not purport to understand it. + for (i = 0;i <= Host.len;++i) { + if ((i == 0) || (i == Host.len) || (Host.s[i] == '.')) + if ((relayhost = constmap(&(proto->maproutes),Host.s + i,Host.len - i))) { + skipmx = 1; + routesproto = proto; + } + } + if (relayhost && !*relayhost) relayhost = 0; + + if (relayhost) { + i = str_chr(relayhost,':'); + if (relayhost[i]) { + scan_ulong(relayhost + i + 1,&port); + relayhost[i] = 0; + } + if (!stralloc_copys(&Host,relayhost)) temp_nomem(); + } + if (skipmx) break; + } + + startdns(&Dnsres); + struct pollfd dnspfd; + dnspfd.fd = skadns_fd(&Dnsres); + dnspfd.events = POLLIN; + dnspfd.revents = 0; + descriptor_add(dnspfd, (void *)&Dnsres, &checkdns); + switch (skipmx) { + case 0: + case 1: + } + //for (i = 0; i * sizeof(protocol_t) < + /* TODO: actually send the letter */ - skadns_end(&dnsres); } #endif diff --git a/src/mxf-remote/mxf-remote.h b/src/mxf-remote/mxf-remote.h @@ -2,23 +2,42 @@ #define MXF_REMOTE_H #include "ga_foreach.h" + +#include "typeallocdefs.h" +#include "typealloc.h" +// I fold, I will use TypeAlloc + #include "controls.h" // Needs control.o from NMMail + #include "netstrings.h" // Needs netstrings.o from NMMail + #include "auto_qmail.h" // Needs auto-qmail from NMMail + #include "constmap.h" // Needs constmap from NMMail + #include "ipme.h" // Needs IPMe from NMMail + +#include "scan.h" +// Needs scan from NMMail + +#include "uthash.h" +#undef uthash_fatal +#define uthash_fatal(msg) temp_nomem() +// Special - needs UTHash for mutable hash tables. #ifndef USING_SKALIBS #error "You must use skalibs, s6-networking and s6-dns for Nightmare Remote." #else #include <stdlib.h> #include <signal.h> +#include <poll.h> #include <limits.h> #include <skalibs/tai.h> +#include <sys/types.h> #include <skalibs/types.h> #include <skalibs/iopause.h> #include <skalibs/stralloc.h> @@ -105,9 +124,39 @@ typedef struct { unsigned int tls:1; // TLSC unsigned int starttls:1; // UCSPITLSC char app[PATH_MAX]; - constmap maproutes; // control/<service>routes + struct constmap maproutes; // control/<service>routes } protocol_t; +struct descriptor_t_s; +typedef struct descriptor_t_s descriptor_t; + +struct process_t_s; +typedef struct process_t_s process_t; + +struct dnsq_t_s; +typedef struct dnsq_t_s dnsq_t; + +struct descriptor_t_s { + struct pollfd fd; // stored directly in this struct; used solely as an index for the uthash + void* opaque; // a WEAK pointer to an opaque; may be anything or NULL + int (*fdcb) (descriptor_t *, short, void *); + UT_hash_handle hh; +}; + +struct process_t_s { + pid_t pid; + void* opaque; // a WEAK pointer to an opaque + int (*pdiecb) (process_t *, int, void *); + UT_hash_handle hh; +}; // callback when a protegé reports ("child dies") + +struct dnsq_t_s { + uint16_t dnsq; + void* opaque; // a WEAK pointer to an opaque + int (*qcb) (skadns_t *, dnsq_t *, void *); + UT_hash_handle hh; +}; + typedef struct { uint16_t port; uint16_t weight; // 0 for MX, A/AAAA, and SLIP; as published for SRV diff --git a/src/mxf-remote/typealloc.h b/src/mxf-remote/typealloc.h @@ -0,0 +1,7 @@ +#ifndef TypeAlloc_H // this will be fine though it's nonstandard +#define TypeAlloc_H + +#define TypeAlloc_typedef(ta,type,field,len,a) \ + typedef struct ta { type *field; unsigned int len; unsigned int a; } ta; + +#endif diff --git a/src/mxf-remote/typeallocdefs.h b/src/mxf-remote/typeallocdefs.h @@ -0,0 +1,55 @@ +#ifndef TypeAlloc_DEFS_H +#define TypeAlloc_DEFS_H +/* de https://github.com/notqmail/notqmail */ + +//#include "alloc.h" +#include "error.h" +// needed from qmail + +#include "oflops.h" +// unclear + +#define TypeAlloc_readyplus(ta,type,field,len,a,base,ta_rplus) \ +static int ta_rplus ## _internal (ta *x, unsigned int n, unsigned int pluslen) \ +{ \ + unsigned int nlen; \ + errno = error_nomem; \ + if (x->field) { \ + unsigned int nnum; \ + type *nfield; \ + if (__builtin_add_overflow(n, pluslen, &n)) \ + return 0; \ + if (n <= x->a) \ + return 1; \ + if (__builtin_add_overflow(n, (n >> 3) + base, &nnum)) \ + return 0; \ + if (__builtin_mul_overflow(nnum, sizeof(type), &nlen)) \ + return 0; \ + nfield = realloc(x->field, nlen); \ + if (nfield == NULL) \ + return 0; \ + x->field = nfield; \ + x->a = nnum; \ + return 1; } \ + x->len = 0; \ + if (__builtin_mul_overflow(n, sizeof(type), &nlen)) \ + return 0; \ + x->field = (type *) malloc(nlen); \ + if (!x->field) \ + return 0; \ + x->a = n; \ + return 1; } \ +int ta_rplus(ta *x, unsigned int n) \ +{ return ta_rplus ## _internal (x, n, x->len); } + +/* this needs a TypeAlloc_readyplus call before as it reuses the internal helper + * function. */ +#define TypeAlloc_ready(ta,type,field,len,a,base,ta_ready) \ +int ta_ready(ta *x, unsigned int n) \ +{ return ta_ready ## plus_internal (x, n, 0); } + +#define TypeAlloc_append(ta,type,field,len,a,base,ta_rplus,ta_append) \ +int ta_append(ta *x, type *i) \ +{ if (!ta_rplus(x,1)) return 0; x->field[x->len++] = *i; return 1; } + +#endif diff --git a/src/mxf-remote/uthash.h b/src/mxf-remote/uthash.h @@ -0,0 +1,963 @@ +/* +Copyright (c) 2003-2014, Troy D. Hanson http://troydhanson.github.com/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#include <string.h> /* memcmp,strlen */ +#include <stddef.h> /* ptrdiff_t */ +#include <stdlib.h> /* exit() */ + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#define DECLTYPE(x) +#endif +#elif defined(__BORLANDC__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#define DECLTYPE(x) +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while(0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while(0) +#endif + +/* a number of the hash function use uint32_t which isn't defined on Pre VS2010 */ +#if defined (_WIN32) +#if defined(_MSC_VER) && _MSC_VER >= 1600 +#include <stdint.h> +#elif defined(__WATCOMC__) +#include <stdint.h> +#else +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#endif +#else +#include <stdint.h> +#endif + +#define UTHASH_VERSION 1.9.9 + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal error (out of memory,etc) */ +#endif +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhe */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + out=NULL; \ + if (head != NULL) { \ + unsigned _hf_bkt,_hf_hashv; \ + HASH_FCN(keyptr,keylen, (head)->hh.tbl->num_buckets, _hf_hashv, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, _hf_hashv) != 0) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], \ + keyptr,keylen,out); \ + } \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!((tbl)->bloom_bv)) { uthash_fatal( "out of memory"); } \ + memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc( \ + sizeof(UT_hash_table)); \ + if (!((head)->hh.tbl)) { uthash_fatal( "out of memory"); } \ + memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); } \ + memset((head)->hh.tbl->buckets, 0, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ +} while(0) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh,head,&((add)->fieldname),keylen_in,add) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + replaced=NULL; \ + HASH_FIND(hh,head,&((add)->fieldname),keylen_in,replaced); \ + if (replaced!=NULL) { \ + HASH_DELETE(hh,head,replaced); \ + } \ + HASH_ADD(hh,head,fieldname,keylen_in,add); \ +} while(0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_bkt; \ + (add)->hh.next = NULL; \ + (add)->hh.key = (char*)(keyptr); \ + (add)->hh.keylen = (unsigned)(keylen_in); \ + if (!(head)) { \ + head = (add); \ + (head)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh,head); \ + } else { \ + (head)->hh.tbl->tail->next = (add); \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail = &((add)->hh); \ + } \ + (head)->hh.tbl->num_items++; \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_FCN(keyptr,keylen_in, (head)->hh.tbl->num_buckets, \ + (add)->hh.hashv, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt],&(add)->hh); \ + HASH_BLOOM_ADD((head)->hh.tbl,(add)->hh.hashv); \ + HASH_EMIT_KEY(hh,head,keyptr,keylen_in); \ + HASH_FSCK(hh,head); \ +} while(0) + +#define HASH_TO_BKT( hashv, num_bkts, bkt ) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while(0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ +do { \ + struct UT_hash_handle *_hd_hh_del; \ + if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) ) { \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + head = NULL; \ + } else { \ + unsigned _hd_bkt; \ + _hd_hh_del = &((delptr)->hh); \ + if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) { \ + (head)->hh.tbl->tail = \ + (UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ + (head)->hh.tbl->hho); \ + } \ + if ((delptr)->hh.prev != NULL) { \ + ((UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ + (head)->hh.tbl->hho))->next = (delptr)->hh.next; \ + } else { \ + DECLTYPE_ASSIGN(head,(delptr)->hh.next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + ((UT_hash_handle*)((ptrdiff_t)_hd_hh_del->next + \ + (head)->hh.tbl->hho))->prev = \ + _hd_hh_del->prev; \ + } \ + HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh,head); \ +} while (0) + + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ + HASH_FIND(hh,head,findstr,(unsigned)strlen(findstr),out) +#define HASH_ADD_STR(head,strfield,add) \ + HASH_ADD(hh,head,strfield[0],(unsigned int)strlen(add->strfield),add) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ + HASH_REPLACE(hh,head,strfield[0],(unsigned)strlen(add->strfield),add,replaced) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count; \ + char *_prev; \ + _count = 0; \ + for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("invalid hh_prev %p, actual %p\n", \ + _thh->hh_prev, _prev ); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("invalid bucket count %u, actual %u\n", \ + (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("invalid hh item count %u, actual %u\n", \ + (head)->hh.tbl->num_items, _count ); \ + } \ + /* traverse hh in app order; check next/prev integrity, count */ \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev !=(char*)(_thh->prev)) { \ + HASH_OOPS("invalid prev %p, actual %p\n", \ + _thh->prev, _prev ); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = ( _thh->next ? (UT_hash_handle*)((char*)(_thh->next) + \ + (head)->hh.tbl->hho) : NULL ); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("invalid app item count %u, actual %u\n", \ + (head)->hh.tbl->num_items, _count ); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include <unistd.h> to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ +#ifdef HASH_FUNCTION +#define HASH_FCN HASH_FUNCTION +#else +#define HASH_FCN HASH_JEN +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _hb_keylen=(unsigned)keylen; \ + const unsigned char *_hb_key=(const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ + bkt = (hashv) & (num_bkts-1U); \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ +#define HASH_SAX(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ + bkt = hashv & (num_bkts-1U); \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key=(const unsigned char*)(key); \ + hashv = 2166136261U; \ + for(_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ + bkt = hashv & (num_bkts-1U); \ +} while(0) + +#define HASH_OAT(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ + bkt = hashv & (num_bkts-1U); \ +} while(0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + bkt = hashv & (num_bkts-1U); \ +} while(0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ + bkt = hashv & (num_bkts-1U); \ +} while(0) + +#ifdef HASH_USING_NO_STRICT_ALIASING +/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads. + * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. + * MurmurHash uses the faster approach only on CPU's where we know it's safe. + * + * Note the preprocessor built-in defines can be emitted using: + * + * gcc -m64 -dM -E - < /dev/null (on gcc) + * cc -## a.c (where a.c is a simple test file) (Sun Studio) + */ +#if (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86)) +#define MUR_GETBLOCK(p,i) p[i] +#else /* non intel */ +#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 3UL) == 0UL) +#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 3UL) == 1UL) +#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 3UL) == 2UL) +#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 3UL) == 3UL) +#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL)) +#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__)) +#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8)) +#else /* assume little endian non-intel */ +#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8)) +#endif +#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \ + (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \ + (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \ + MUR_ONE_THREE(p)))) +#endif +#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) +#define MUR_FMIX(_h) \ +do { \ + _h ^= _h >> 16; \ + _h *= 0x85ebca6bu; \ + _h ^= _h >> 13; \ + _h *= 0xc2b2ae35u; \ + _h ^= _h >> 16; \ +} while(0) + +#define HASH_MUR(key,keylen,num_bkts,hashv,bkt) \ +do { \ + const uint8_t *_mur_data = (const uint8_t*)(key); \ + const int _mur_nblocks = (int)(keylen) / 4; \ + uint32_t _mur_h1 = 0xf88D5353u; \ + uint32_t _mur_c1 = 0xcc9e2d51u; \ + uint32_t _mur_c2 = 0x1b873593u; \ + uint32_t _mur_k1 = 0; \ + const uint8_t *_mur_tail; \ + const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+(_mur_nblocks*4)); \ + int _mur_i; \ + for(_mur_i = -_mur_nblocks; _mur_i!=0; _mur_i++) { \ + _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + \ + _mur_h1 ^= _mur_k1; \ + _mur_h1 = MUR_ROTL32(_mur_h1,13); \ + _mur_h1 = (_mur_h1*5U) + 0xe6546b64u; \ + } \ + _mur_tail = (const uint8_t*)(_mur_data + (_mur_nblocks*4)); \ + _mur_k1=0; \ + switch((keylen) & 3U) { \ + case 3: _mur_k1 ^= (uint32_t)_mur_tail[2] << 16; /* FALLTHROUGH */ \ + case 2: _mur_k1 ^= (uint32_t)_mur_tail[1] << 8; /* FALLTHROUGH */ \ + case 1: _mur_k1 ^= (uint32_t)_mur_tail[0]; \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + _mur_h1 ^= _mur_k1; \ + } \ + _mur_h1 ^= (uint32_t)(keylen); \ + MUR_FMIX(_mur_h1); \ + hashv = _mur_h1; \ + bkt = hashv & (num_bkts-1U); \ +} while(0) +#endif /* HASH_USING_NO_STRICT_ALIASING */ + +/* key comparison function; return 0 if keys equal */ +#define HASH_KEYCMP(a,b,len) memcmp(a,b,(unsigned long)(len)) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,out) \ +do { \ + if (head.hh_head != NULL) { DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,head.hh_head)); } \ + else { out=NULL; } \ + while (out != NULL) { \ + if ((out)->hh.keylen == (keylen_in)) { \ + if ((HASH_KEYCMP((out)->hh.key,keyptr,keylen_in)) == 0) { break; } \ + } \ + if ((out)->hh.hh_next != NULL) { DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,(out)->hh.hh_next)); } \ + else { out = NULL; } \ + } \ +} while(0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,addhh) \ +do { \ + head.count++; \ + (addhh)->hh_next = head.hh_head; \ + (addhh)->hh_prev = NULL; \ + if (head.hh_head != NULL) { (head).hh_head->hh_prev = (addhh); } \ + (head).hh_head=addhh; \ + if ((head.count >= ((head.expand_mult+1U) * HASH_BKT_CAPACITY_THRESH)) \ + && ((addhh)->tbl->noexpand != 1U)) { \ + HASH_EXPAND_BUCKETS((addhh)->tbl); \ + } \ +} while(0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(hh,head,hh_del) \ + (head).count--; \ + if ((head).hh_head == hh_del) { \ + (head).hh_head = hh_del->hh_next; \ + } \ + if (hh_del->hh_prev) { \ + hh_del->hh_prev->hh_next = hh_del->hh_next; \ + } \ + if (hh_del->hh_next) { \ + hh_del->hh_next->hh_prev = hh_del->hh_prev; \ + } + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(tbl) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + 2UL * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + if (!_he_new_buckets) { uthash_fatal( "out of memory"); } \ + memset(_he_new_buckets, 0, \ + 2UL * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + tbl->ideal_chain_maxlen = \ + (tbl->num_items >> (tbl->log2_num_buckets+1U)) + \ + (((tbl->num_items & ((tbl->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + tbl->nonideal_items = 0; \ + for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++) \ + { \ + _he_thh = tbl->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[ _he_bkt ]); \ + if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) { \ + tbl->nonideal_items++; \ + _he_newbkt->expand_mult = _he_newbkt->count / \ + tbl->ideal_chain_maxlen; \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { _he_newbkt->hh_head->hh_prev = \ + _he_thh; } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ + tbl->num_buckets *= 2U; \ + tbl->log2_num_buckets++; \ + tbl->buckets = _he_new_buckets; \ + tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ? \ + (tbl->ineff_expands+1U) : 0U; \ + if (tbl->ineff_expands > 1U) { \ + tbl->noexpand=1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ +} while(0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for ( _hs_i = 0; _hs_i < _hs_insize; _hs_i++ ) { \ + _hs_psize++; \ + _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + if (! (_hs_q) ) { break; } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize > 0U) || ((_hs_qsize > 0U) && (_hs_q != NULL))) {\ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_qsize--; \ + } else if ( (_hs_qsize == 0U) || (_hs_q == NULL) ) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL){ \ + _hs_p = (UT_hash_handle*)((_hs_p->next != NULL) ? \ + ((void*)((char*)(_hs_p->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + } \ + _hs_psize--; \ + } else if (( \ + cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \ + ) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL){ \ + _hs_p = (UT_hash_handle*)((_hs_p->next != NULL) ? \ + ((void*)((char*)(_hs_p->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL){ \ + _hs_tail->next = NULL; \ + } \ + if ( _hs_nmerges <= 1U ) { \ + _hs_looping=0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh,head); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt=NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if (src != NULL) { \ + for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { _last_elt_hh->next = _elt; } \ + if (dst == NULL) { \ + DECLTYPE_ASSIGN(dst,_elt); \ + HASH_MAKE_TABLE(hh_dst,dst); \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh); \ + (dst)->hh_dst.tbl->num_items++; \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst,dst); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if (head != NULL) { \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)=NULL; \ + } \ +} while(0) + +#define HASH_OVERHEAD(hh,head) \ + ((head != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/src/netstrings.c b/src/netstrings.c @@ -0,0 +1,60 @@ +#include "netstrings.h" +#include "fmt.h" +#include "unistd.h" + +signed char ns_getlength (_BUFFER *bio, unsigned long long *length) +{ + char ch; + + if (length == NULL) return NS_INVAL; + *length = 0; + for (;;) { + _BUFFER_GET(bio, &ch, 1); + if (ch == ':') return NS_SUCCESS; // *length is already set correctly + if (ch < '0') return NS_PROTOCOL; + if (ch > '9') return NS_PROTOCOL; // ':', which is > '9', is already covered + if (*length > 4294967296) return NS_TOOMUCH; // Netstrings longer than 4GiB are valid, but we have no interest in processing them here. + // should be if (*length > 9223372036854775808) return NS_TOOMUCH; // which is 2^63, but at that point it's simply not rational. No-one has 2 to the 63 space available to them. + *length = 10 * (*length) + (ch - '0'); + } +} + +signed char ns_getcomma (_BUFFER *bio) +{ + // This can effectively be verbatim from qmtpd. + char ch; + _BUFFER_GET(bio, &ch, 1); + return (ch == ',')? NS_SUCCESS : NS_PROTOCOL; +} + +// it is expected that ns_put will only be used for strings of up to size_t. +// if you somehow find a way to exceed that, good job. we won't help you. +signed char ns_put (_BUFFER *bio, char *buf, unsigned long long length) +{ + char buf2[100]; + unsigned long long blen = 0; + blen += fmt_ulonglong(buf2+blen, length); + blen += fmt_str(buf2+blen, ":"); + buf2[blen] = 0; + if (_BUFFER_PUT(bio, buf2, blen) == -1) return NS_BIOERR; + // buf2 is now irrelevant + if (_BUFFER_PUT(bio, buf, length) == -1) return NS_BIOERR; + if (_BUFFER_PUT(bio, ",", 1) == -1) return NS_BIOERR; + return NS_SUCCESS; // writing may still yet fail +} + +// it is expected that ns_put will only be used for strings of up to size_t. +// if you somehow find a way to exceed that, good job. we won't help you. +signed char ns_print (int fd, char *buf, unsigned long long length) +{ + char buf2[100]; + unsigned long long blen = 0; + blen += fmt_ulonglong(buf2+blen, length); + blen += fmt_str(buf2+blen, ":"); + buf2[blen] = 0; + if (write(fd, buf2, blen) == -1) return NS_BIOERR; + // buf2 is now irrelevant + if (write(fd, buf, length) == -1) return NS_BIOERR; + if (write(fd, ",", 1) == -1) return NS_BIOERR; + return NS_SUCCESS; // writing may still yet fail +} diff --git a/src/netstrings.o b/src/netstrings.o Binary files differ. diff --git a/src/qmail-qmqpd.c b/src/qmail-qmqpd.c @@ -1,3 +1,4 @@ +#include "noreturn.h" #include "auto_qmail.h" #include "qmail.h" #include "received.h" @@ -10,8 +11,11 @@ #include "byte.h" #include "env.h" #include "str.h" +#include "netstrings.h" -void resources() { _exit(111); } +void _noreturn_ resources() { _exit(111); } +void _noreturn_ badproto() { _exit(100); } +void _noreturn_ die_wtf() { _exit(100); } ssize_t safewrite(int fd, const void *buf, size_t len) { @@ -45,6 +49,16 @@ char *ch; unsigned long getlen() { unsigned long len = 0; + switch (ns_getlength(&ssin,&len)) { + case NS_SUCCESS: return len; break; + case NS_TOOMUCH: resources(); break; + case NS_PROTOCOL: badproto(); break; + case NS_INVAL: die_wtf(); break; + case NS_BIOERR: die_wtf(); break; + } + // we won't reach here. + return len; +#if 0 char ch; for (;;) { @@ -53,13 +67,21 @@ unsigned long getlen() if (len > 200000000) resources(); len = 10 * len + (ch - '0'); } +#endif } void getcomma() { + switch (ns_getcomma(&ssin)) { + case NS_SUCCESS: break; + case NS_PROTOCOL: badproto(); break; + } + +#if 0 char ch; getbyte(&ch); if (ch != ',') _exit(100); +#endif } struct qmail qq; diff --git a/src/qmail-qmtpd.c b/src/qmail-qmtpd.c @@ -15,13 +15,17 @@ #include "control.h" #include "received.h" #include "exit.h" +#include "netstrings.h" -void _noreturn_ badproto() { _exit(100); } +#define _BADPROTOCOL "29:Z Bad protocol! Crashing now.," + +void _noreturn_ badproto(); void _noreturn_ resources() { _exit(111); } void die_nomem() { resources(); } void die_control() { resources(); } void die_cdb() { resources(); } void die_sys() { resources(); } +void die_wtf() { resources(); } // this should never, EVER be called // XXX: should be in realrcptto.h @@ -48,23 +52,44 @@ ssize_t saferead(int fd, void *buf, size_t len) char ssinbuf[512]; substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof(ssinbuf)); -unsigned long getlen() +unsigned long long getlen() { - unsigned long len = 0; + unsigned long long len = 0; +#if 0 char ch; for (;;) { substdio_get(&ssin,&ch,1); if (ch == ':') return len; if (len > 200000000) resources(); - len = 10 * len + (ch - '0'); + len = 10 * len + (ch - '0'); // This depends upon a quirk of ASCII. ch - 0 will be 9 for a value of '9'. + // This isn't robust! + } +#else + switch (ns_getlength(&ssin,&len)) { + case NS_SUCCESS: return len; break; + case NS_TOOMUCH: resources(); break; + case NS_PROTOCOL: badproto(); break; + case NS_INVAL: die_wtf(); break; + case NS_BIOERR: die_wtf(); break; } + // we won't reach here. + return len; +#endif } +void _noreturn_ badproto() { write(1, _BADPROTOCOL, strlen(_BADPROTOCOL)); _exit(100); } void getcomma() { +#if 0 char ch; substdio_get(&ssin,&ch,1); if (ch != ',') badproto(); +#else + switch (ns_getcomma(&ssin)) { + case NS_SUCCESS: break; + case NS_PROTOCOL: badproto(); break; + } +#endif } unsigned int databytes = 0; @@ -98,20 +123,20 @@ main() char *result; char *x; unsigned long u; - + sig_pipeignore(); sig_alarmcatch(resources); alarm(3600); - + if (chdir(auto_qmail) == -1) resources(); - + if (control_init() == -1) resources(); if (rcpthosts_init() == -1) resources(); relayclient = env_get("RELAYCLIENT"); relayclientlen = relayclient ? str_len(relayclient) : 0; realrcptto_init(); - + if (control_readint(&databytes,"control/databytes") == -1) resources(); x = env_get("DATABYTES"); if (x) { scan_ulong(x,&u); databytes = u; } @@ -125,29 +150,31 @@ main() local = env_get("TCPLOCALHOST"); if (!local) local = env_get("TCPLOCALIP"); if (!local) local = "unknown"; - + for (;;) { realrcptto_start(); if (!stralloc_copys(&failure,"")) resources(); flagsenderok = 1; - + len = getlen(); - if (len == 0) badproto(); - + if (len == 0) { + badproto(); + } + if (databytes) bytestooverflow = databytes + 1; if (qmail_open(&qq) == -1) resources(); qp = qmail_qp(&qq); - + substdio_get(&ssin,&ch,1); --len; if (ch == 10) flagdos = 0; else if (ch == 13) flagdos = 1; else badproto(); - + received(&qq,"QMTP",local,remoteip,remotehost,remoteinfo,NULL); - + /* XXX: check for loops? only if len is big? */ - + if (flagdos) while (len > 0) { substdio_get(&ssin,&ch,1); @@ -175,9 +202,9 @@ main() } } getcomma(); - + len = getlen(); - + if (len >= 1000) { buf[0] = 0; flagsenderok = 0; @@ -192,15 +219,15 @@ main() buf[len] = 0; } getcomma(); - + flagbother = 0; qmail_from(&qq,buf); if (!flagsenderok) qmail_fail(&qq); - + biglen = getlen(); while (biglen > 0) { if (!stralloc_append(&failure,"")) resources(); - + len = 0; for (;;) { if (!biglen) badproto(); @@ -222,7 +249,7 @@ main() if (!buf[i]) failure.s[failure.len - 1] = 'N'; } buf[len] = 0; - + if (relayclient) str_copy(buf + len,relayclient); else @@ -234,7 +261,7 @@ main() if (!failure.s[failure.len - 1]) if (!realrcptto(buf)) failure.s[failure.len - 1] = 'D'; - + if (!failure.s[failure.len - 1]) { qmail_to(&qq,buf); flagbother = 1; @@ -244,7 +271,7 @@ main() biglen -= (len + 1); } getcomma(); - + if (!flagbother) qmail_fail(&qq); result = qmail_close(&qq); if (!flagsenderok) result = "DUnacceptable sender (#5.1.7)"; @@ -282,7 +309,7 @@ main() substdio_puts(&ssout,"47:DSorry, I can't handle that recipient. (#5.1.3),"); break; } - + /* ssout will be flushed when we read from the network again */ } } diff --git a/src/qmail-rspawn.c b/src/qmail-rspawn.c @@ -93,9 +93,7 @@ static char *setup_qrargs() return qr; } -int spawn(fdmess,fdout,s,r,at) -int fdmess; int fdout; -char *s; char *r; int at; +int spawn(int fdmess, int fdout, char *s, char *r, int at) { int f; char *(args[5]); diff --git a/src/qmail-smtpd.c b/src/qmail-smtpd.c @@ -40,23 +40,23 @@ void flush() { substdio_flush(&ssout); } void out(s) char *s; { substdio_puts(&ssout,s); } void die_read() { _exit(1); } -void die_alarm() { out("451 timeout (#4.4.2)\r\n"); flush(); _exit(1); } -void die_nomem() { out("421 out of memory (#4.3.0)\r\n"); flush(); _exit(1); } -void die_control() { out("421 unable to read controls (#4.3.0)\r\n"); flush(); _exit(1); } -void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); } -void die_ip6me() { out("421 unable to figure out my IPv6 addresses (#4.3.0)\r\n"); flush(); _exit(1); } -void straynewline() { out("451 See https://cr.yp.to/docs/smtplf.html.\r\n"); flush(); _exit(1); } - -void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); } -void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); } -void err_unimpl(char *arg) { out("502 unimplemented (#5.5.1)\r\n"); } -void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); } -void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); } -void err_wantrcpt() { out("503 RCPT first (#5.5.1)\r\n"); } -void die_cdb() { out("421 unable to read cdb user database (#4.3.0)\r\n"); flush(); _exit(1); } -void die_sys() { out("421 unable to read system user database (#4.3.0)\r\n"); flush(); _exit(1); } -void err_noop(char *arg) { out("250 ok\r\n"); } -void err_vrfy(char *arg) { out("252 send some mail, i'll try my best\r\n"); } +void die_alarm() { out("451 Timeout (#4.4.2)\r\n"); flush(); _exit(1); } +void die_nomem() { out("421 Out of memory (#4.3.0)\r\n"); flush(); _exit(1); } +void die_control() { out("421 Unable to read controls (#4.3.0)\r\n"); flush(); _exit(1); } +void die_ipme() { out("421 Unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); } +void die_ip6me() { out("421 Unable to figure out my IPv6 addresses (#4.3.0)\r\n"); flush(); _exit(1); } +void straynewline() { out("451 You sent a stray newline. See https://cr.yp.to/docs/smtplf.html.\r\n"); flush(); _exit(1); } + +void err_bmf() { out("553 Sorry, your envelope sender is in my badmailfrom list. (#5.7.1)\r\n"); } +void err_nogateway() { out("553 Sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); } +void err_unimpl(char *arg) { out("502 Unimplemented (#5.5.1)\r\n"); } +void err_syntax() { out("555 Syntax error (#5.5.4)\r\n"); } +void err_wantmail() { out("503 You must execute MAIL first (#5.5.1)\r\n"); } +void err_wantrcpt() { out("503 You must execute RCPT first (#5.5.1)\r\n"); } +void die_cdb() { out("421 Unable to read cdb user database (#4.3.0)\r\n"); flush(); _exit(1); } +void die_sys() { out("421 Unable to read system user database (#4.3.0)\r\n"); flush(); _exit(1); } +void err_noop(char *arg) { out("250 As requested, no operation performed.\r\n"); } +void err_vrfy(char *arg) { out("252 Send something and we'll give it a shot.\r\n"); } void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); } void die_dnsbl(char *arg) { diff --git a/src/realrcptto.c b/src/realrcptto.c @@ -16,6 +16,7 @@ #include "uint32.h" #include "substdio.h" #include "env.h" +#include "control.h" extern void die_nomem(); extern void die_control();