diff options
Diffstat (limited to 'src/qmail-remote/qmail-remote-io.c')
| -rw-r--r-- | src/qmail-remote/qmail-remote-io.c | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/src/qmail-remote/qmail-remote-io.c b/src/qmail-remote/qmail-remote-io.c new file mode 100644 index 0000000..3f55f87 --- /dev/null +++ b/src/qmail-remote/qmail-remote-io.c @@ -0,0 +1,245 @@ +/* ISC license. */ + +#include <unistd.h> + +#include <skalibs/gccattributes.h> +#include <skalibs/types.h> +#include <skalibs/buffer.h> +#include <skalibs/gol.h> +#include <skalibs/tai.h> +#include <skalibs/unix-timed.h> + +#include "qmailr.h" + +enum gola_e +{ + GOLA_TIMEOUT, + GOLA_FDR, + GOLA_FDW, + GOLA_N +} ; + +static inline unsigned int qgol_argv (char const *const *argv, gol_bool const *b, unsigned int bn, gol_arg const *a, unsigned int an, uint64_t *br, char const **ar) +{ + int problem = 0 ; + int r = gol(argv, b, bn, a, an, br, ar, &problem) ; + + if (r < 0) + { + if (problem > 0) + { + char s[2] = { argv[-r-1][problem], 0 } ; + qmailr_perm("qmail-remote-io: ", "unrecognized ", "short ", "option: ", s) ; + } + else if (!problem) + qmailr_perm("qmail-remote-io: ", "invalid ", "option: ", argv[-r-1]) ; + else if (problem == -1) + qmailr_perm("qmail-remote-io: ", "unrecognized ", "boolean ", "option: ", argv[-r-1]) ; + else + qmailr_perm("qmail-remote-io: ", "unrecognized ", "option with argument: ", argv[-r-1]) ; + } + else return r ; +} + +static inline unsigned int qgol_main (int argc, char const *const *argv, gol_bool const *b, unsigned int bn, gol_arg const *a, unsigned int an, uint64_t *br, char const **ar) +{ + if (argc < 1 || argv[argc]) qmailr_perm("qmail-remote-io: ", "invalid argc/argv") ; + if (argc == 1) return 1 ; + return 1 + qgol_argv(argv + 1, b, bn, a, an, br, ar) ; +} + +static unsigned int read_answer_options (buffer *in, unsigned int timeout, char *buf, size_t buflen, char const *fmtip, unsigned int flags) +{ + int r = qmailr_smtp_read_answer(in, buf, buflen, timeout) ; + if (r == -1) qmailr_tempsys("qmail-remote-io: ", "unable to ", "read SMTP answer from ", fmtip, flags & 1 ? " (Possible duplicate!)" : "") ; + if (!r) qmailr_tempsys("qmail-remote-io: ", fmtip, " closed the connection early", flags & 1 ? " (Possible duplicate!)" : "") ; + return r ; +} + +static unsigned read_answer (buffer *in, unsigned int timeout, char *buf, size_t buflen, char const *fmtip) +{ + return read_answer_options(in, timeout, buf, buflen, fmtip, 0) ; +} + +static void put (buffer *out, char const *s) +{ + if (buffer_puts(out, s) < 0) + qmailr_tempsys("qmail-remote-io: ", "unable to ", "queue SMTP command") ; +} + +static void datachar (buffer *out, char c) +{ + if (buffer_put(out, &c, 1) < 1) + qmailr_tempsys("qmail-remote-io: ", "unable to ", "send character after DATA") ; +} + +/* + small DFA for blast() to quote dots and newlines + + 0 1 2 3 +st\ev EOF . \n other + +0 qp rp p +START END INLINE START INLINE + +1 p rp p +INLINE X INLINE START INLINE + +END=2 X=3 + +0x10 q quote with . +0x20 r quote with \r +0x40 p print char + +*/ + +static inline uint8_t cclass (char c) +{ + return c == '.' ? 1 : c == '\n' ? 2 : 3 ; +} + +static void blast (buffer *out, unsigned int timeout) +{ + static uint8_t const table[2][4] = + { + { 0x02, 0x51, 0x60, 0x41 }, + { 0x03, 0x41, 0x60, 0x41 } + } ; + uint8_t state = 0 ; + while (state < 2) + { + char c ; + uint8_t val ; + ssize_t r = buffer_get(buffer_0, &c, 1) ; + if (r == -1) qmailr_tempsys("qmail-remote-io: ", "unable to ", "read message") ; + val = r ? table[state][cclass(c)] : table[state][0] ; + state = val & 3 ; + if (val & 0x10) datachar(out, '.') ; + if (val & 0x20) datachar(out, '\r') ; + if (val & 0x40) datachar(out, c) ; + } + if (state > 2) qmailr_perm("qmail-remote-io: ", "SMTP cannot transfer messages with partial final lines") ; + if (buffer_putflush(out, ".\r\n", 3) < 0) qmailr_tempsys("qmail-remote-io: ", "unable to ", "finalize message data") ; +} + +static void smtp_body (buffer *in, buffer *out, char const *fmtip, char const *sender, char const *const *recip, unsigned int n, unsigned int timeout) gccattr_noreturn ; +static void smtp_body (buffer *in, buffer *out, char const *fmtip, char const *sender, char const *const *recip, unsigned int n, unsigned int timeout) +{ + tain deadline ; + unsigned int code ; + int flagbother = 0 ; + char buf[4096] ; + + put(out, "MAIL FROM:<") ; + put(out, sender) ; + put(out, ">\r\n") ; + tain_addsec_g(&deadline, timeout) ; + if (!buffer_timed_flush_g(out, &deadline)) + qmailr_tempsys("qmail-remote-io: ", "unable to ", "send command to ", fmtip) ; + code = read_answer(in, timeout, buf, 4096, fmtip) ; + if (code >= 500) + { + qmailr_smtp_quit(out, timeout) ; + qmailr_perm("qmail-remote-io: ", "connected to ", fmtip, " but sender was rejected") ; + } + else if (code >= 400) + { + qmailr_smtp_quit(out, timeout) ; + qmailr_temp("qmail-remote-io: ", "connected to ", fmtip, " but sender was rejected") ; + } + + for (unsigned int i = 0 ; i < n ; i++) + { + put(out, "RCPT TO:<") ; + put(out, recip[i]) ; + put(out, ">\r\n") ; + tain_addsec_g(&deadline, timeout) ; + if (!buffer_timed_flush_g(out, &deadline)) + qmailr_tempsys("qmail-remote-io: ", "unable to ", "send command to ", fmtip) ; + code = read_answer(in, timeout, buf, 4096, fmtip) ; + if (code >= 500) + { + qmailr_smtp_quit(out, timeout) ; + qmailr_die('h', fmtip, " does not like recipient.\n", "Remote host said: ", buf+4) ; + } + else if (code >= 400) + { + qmailr_smtp_quit(out, timeout) ; + qmailr_die('s', fmtip, " does not like recipient.\n", "Remote host said: ", buf+4) ; + } + else + { + buffer_put(buffer_1, "r", 2) ; + flagbother = 1 ; + } + } + if (!flagbother) + { + qmailr_smtp_quit(out, timeout) ; + qmailr_perm("Giving up on ", fmtip) ; + } + + put(out, "DATA\r\n") ; + tain_addsec_g(&deadline, timeout) ; + if (!buffer_timed_flush_g(out, &deadline)) + qmailr_tempsys("qmail-remote-io: ", "unable to ", "send command to ", fmtip) ; + code = read_answer(in, timeout, buf, 4096, fmtip) ; + if (code >= 500) + { + qmailr_smtp_quit(out, timeout) ; + qmailr_perm(fmtip, " failed on DATA command") ; + } + else if (code >= 400) + { + qmailr_smtp_quit(out, timeout) ; + qmailr_temp(fmtip, " failed on DATA command") ; + } + + blast(out, timeout) ; + code = read_answer_options(in, timeout, buf, 4096, fmtip, 1) ; + if (code >= 500) + { + qmailr_smtp_quit(out, timeout) ; + qmailr_perm(fmtip, " failed after I sent the message") ; + } + else if (code >= 400) + { + qmailr_smtp_quit(out, timeout) ; + qmailr_temp(fmtip, " failed after I sent the message") ; + } + + qmailr_smtp_quit(out, timeout) ; + qmailr_die('K', fmtip, " accepted message") ; +} + +int main (int argc, char const *const *argv) +{ + static gol_arg const rgola[] = + { + { .so = 't', .lo = "timeoutremote", .i = GOLA_TIMEOUT }, + { .so = '6', .lo = "fdr", .i = GOLA_FDR }, + { .so = '7', .lo = "fdw", .i = GOLA_FDW }, + } ; + char const *wgola[GOLA_N] = { 0 } ; + unsigned int fdr = 6, fdw = 7 ; + unsigned int timeoutremote = 1200 ; + buffer in, out ; + char inbuf[1024] ; + char outbuf[BUFFER_OUTSIZE] ; + unsigned int golc = qgol_main(argc, argv, 0, 0, rgola, 3, 0, wgola) ; + argc -= golc ; argv += golc ; + if (argc < 3) qmailr_perm("qmail-remote-io: ", "too few arguments") ; + + if (wgola[GOLA_TIMEOUT] && !uint0_scan(wgola[GOLA_TIMEOUT], &timeoutremote)) + qmailr_perm("qmail-remote-io: ", "invalid timeoutremote") ; + if (wgola[GOLA_FDR] && !uint0_scan(wgola[GOLA_FDR], &fdr)) + qmailr_perm("qmail-remote-io: ", "invalid fdr") ; + if (wgola[GOLA_FDW] && !uint0_scan(wgola[GOLA_FDW], &fdw)) + qmailr_perm("qmail-remote-io: ", "invalid fdw") ; + + buffer_init(&in, &buffer_read, fdr, inbuf, 1024) ; + buffer_init(&out, &buffer_write, fdw, outbuf, BUFFER_OUTSIZE) ; + + tain_now_set_stopwatch_g() ; + smtp_body(&in, &out, argv[0], argv[1], argv + 2, argc - 2, timeoutremote) ; +} |
