Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
SMTP tools whose future is https://schmonz.com/qmail/acceptutils
#include "exit.h"
#include "readwrite.h"
#include "substdio.h"
char sserrbuf[128];
substdio sserr = SUBSTDIO_FDBUF(write,2,sserrbuf,sizeof sserrbuf);
void err(char *s) {
substdio_puts(&sserr,s);
substdio_puts(&sserr,"\n");
substdio_flush(&sserr);
}
void die() { _exit(1); }
void die_usage() { err("usage: checkpassword-rejectroot subprogram"); die(); }
void die_root() { err("checkpassword-rejectroot: am root"); die(); }
int main(int argc,char **argv) {
char **childargs;
childargs = argv + 1;
if (!*childargs) die_usage();
if (getuid() == 0) die_root();
execvp(*childargs,childargs);
die();
}
#!/usr/bin/env perl
use warnings;
use strict;
use POSIX qw(_exit);
my $exitcode = 0;
sub accepted_data { my ($response) = @_;
return $response =~ /^354 /;
}
sub munge_timeout { my ($response) = @_;
$exitcode = 16;
return $response;
}
sub munge_greeting { my ($response) = @_;
if ($response =~ /^4[0-9]{2} /) {
$exitcode = 14;
} elsif ($response =~ /^5[0-9]{2} /) {
$exitcode = 15;
} else {
$response = q{235 ok};
$response .= qq{, $ENV{AUTHUSER},} if defined $ENV{AUTHUSER};
$response .= qq{ go ahead $< (#2.0.0)};
}
return $response;
}
sub munge_help { my ($response) = @_;
$response = q{214 fixsmtpio.pl home page: }
. q{https://schmonz.com/qmail/authutils}
. "\r\n"
. $response;
return $response;
}
sub munge_test { my ($response) = @_;
$response .= q{ and also it's mungeable};
return $response;
}
sub munge_ehlo { my ($response) = @_;
my @lines = split(/\r\n/, $response);
@lines = grep { ! /^250.AUTH / } @lines;
@lines = grep { ! /^250.STARTTLS/ } @lines;
$response = join("\r\n", @lines);
return $response;
}
sub change_every_line_fourth_char_to_dash { my ($multiline) = @_;
$multiline =~ s/^(.{3})./$1-/msg;
return $multiline;
}
sub change_last_line_fourth_char_to_space { my ($multiline) = @_;
$multiline =~ s/(.{3})-(.+)$/$1 $2/;
return $multiline;
}
sub reformat_multiline_response { my ($response) = @_;
# XXX maybe fix up line breaks? or just keep them correct all along
$response = change_every_line_fourth_char_to_dash($response);
$response = change_last_line_fourth_char_to_space($response);
$response .= "\r\n";
return $response;
}
sub strip_last_eol { my ($string) = @_;
$string =~ s/\r?\n$//;
return $string;
}
sub munge_response { my ($response, $verb, $arg) = @_;
$response = strip_last_eol($response);
$response = munge_timeout($response) if '' eq $verb;
$response = munge_greeting($response) if 'greeting' eq $verb;
$response = munge_help($response) if 'help' eq $verb;
$response = munge_test($response) if 'test' eq $verb;
$response = munge_ehlo($response) if 'ehlo' eq $verb;
return reformat_multiline_response($response);
}
sub could_be_final_response_line { my ($line) = @_;
return length($line) >= 4 && substr($line, 3, 1) eq " ";
}
sub is_entire_response { my ($response) = @_;
my @lines = split(/\r\n/, $response);
return could_be_final_response_line($lines[-1]) && is_entire_line($response);
}
#####
sub die_usage { die "usage: fixsmtpio.pl prog [ arg ... ]\n"; }
sub die_pipe { die "fixsmtpio.pl: unable to open pipe: $!\n"; }
sub die_fork { die "fixsmtpio.pl: unable to fork: $!\n"; }
sub die_read { die "fixsmtpio.pl: unable to read: $!\n"; }
sub die_write { die "fixsmtpio.pl: unable to write: $!\n"; }
sub use_as_stdin { my ($fd) = @_;
open(STDIN, '<&=', $fd) || die_pipe();
}
sub use_as_stdout { my ($fd) = @_;
open(STDOUT, '>>&=', $fd) || die_pipe();
}
sub mypipe { my ($from_ref, $to_ref) = @_;
pipe(${$from_ref}, ${$to_ref}) or die_pipe();
}
sub setup_server { my ($from_proxy, $to_server,
$from_server, $to_proxy) = @_;
close($from_server);
close($to_server);
use_as_stdin($from_proxy);
use_as_stdout($to_proxy);
}
sub exec_server_and_never_return { my (@argv) = @_;
exec @argv;
die;
}
sub be_child { my ($from_proxy, $to_proxy,
$from_server, $to_server,
@args) = @_;
setup_server($from_proxy, $to_server, $from_server, $to_proxy);
exec_server_and_never_return(@args);
}
sub setup_proxy { my ($from_proxy, $to_proxy) = @_;
close($from_proxy);
close($to_proxy);
}
sub is_entire_line { my ($string) = @_;
return substr($string, -1, 1) eq "\n";
}
my $want_to_read_bits;
sub want_to_read { my ($fd) = @_;
vec($want_to_read_bits, fileno($fd), 1) = 1;
}
my $can_read_bits;
sub can_read { my ($fd) = @_;
return 1 == vec($can_read_bits, fileno($fd), 1);
}
sub can_read_something {
my $ready;
$ready = select($can_read_bits = $want_to_read_bits, undef, undef, undef);
die_read() if $ready == -1 && ! $!{EINTR};
return $ready;
}
sub saferead { my ($fd, $buf_ref, $len) = @_;
my $r;
$r = sysread($fd, ${$buf_ref}, $len);
die_read() if $r == -1 && ! $!{EINTR};
return $r;
}
sub safeappend { my ($string_ref, $fd) = @_;
my ($r, $buf);
$r = saferead($fd, \$buf, 128);
${$string_ref} .= $buf;
return $r;
}
sub is_last_line_of_data { my ($r) = @_;
return $r =~ /^\.\r$/;
}
sub parse_request { my ($request, $verb_ref, $arg_ref) = @_;
my $chomped;
$chomped = strip_last_eol($request);
(${$verb_ref}, ${$arg_ref}) = split(/ /, $chomped, 2);
${$verb_ref} ||= '';
${$arg_ref} ||= '';
${$verb_ref} = lc(${$verb_ref});
}
sub logit { my ($logprefix, $s) = @_;
print STDERR "$logprefix: $s";
}
sub write_to_client { my ($client, $response) = @_;
syswrite($client, $response) || die_write();
logit('O', $response);
}
sub write_to_server { my ($server, $request) = @_;
syswrite($server, $request) || die_write();
logit('I', $request);
}
sub smtp_test { my ($verb, $arg) = @_;
return "250 fixsmtpio.pl test ok: $arg";
}
sub smtp_unimplemented { my ($verb, $arg) = @_;
return "502 unimplemented (#5.5.1)";
}
sub verb_matches { my ($s, $sa) = @_;
return 0 unless length $sa;
return lc $s eq lc $sa;
}
sub handle_internally { my ($request, $verb_ref, $arg_ref) = @_;
parse_request($request, $verb_ref, $arg_ref);
return smtp_test(${$verb_ref}, ${$arg_ref}) if verb_matches('test', ${$verb_ref});
return smtp_unimplemented(${$verb_ref}, ${$arg_ref}) if verb_matches('auth', ${$verb_ref});
return smtp_unimplemented(${$verb_ref}, ${$arg_ref}) if verb_matches('starttls', ${$verb_ref});
return "";
}
sub handle_request { my ($from_client, $to_server, $to_client,
$request, $verb_ref, $arg_ref,
$want_data_ref, $in_data_ref) = @_;
my $internal_response;
$request = strip_last_eol($request) . "\r\n";
if (${$in_data_ref}) {
write_to_server($to_server, $request);
if (is_last_line_of_data($request)) {
${$in_data_ref} = 0;
}
} else {
if ($internal_response = handle_internally($request, $verb_ref, $arg_ref)) {
logit('I', $request);
write_to_client($to_client,
munge_response($internal_response, ${$verb_ref}, ${$arg_ref}));
} else {
${$want_data_ref} = 1 if (verb_matches('data', ${$verb_ref}));
write_to_server($to_server, $request);
}
}
}
sub handle_response { my ($to_client, $response, $verb, $arg,
$want_data_ref, $in_data_ref) = @_;
if (${$want_data_ref}) {
${$want_data_ref} = 0;
if (accepted_data($response)) {
${$in_data_ref} = 1;
}
}
write_to_client($to_client, munge_response($response, $verb, $arg));
}
sub do_proxy_stuff { my ($from_client, $to_server,
$from_server, $to_client) = @_;
my ($request, $verb, $arg, $response) = ('', '', '', '');
my ($want_data, $in_data) = (0, 0);
handle_request($from_client, $to_server, $to_client,
'greeting', \$verb, \$arg,
\$want_data, \$in_data);
want_to_read($from_client);
want_to_read($from_server);
for (;;) {
next unless can_read_something();
if (can_read($from_client)) {
last unless safeappend(\$request, $from_client);
if (is_entire_line($request)) {
handle_request($from_client, $to_server, $to_client,
$request, \$verb, \$arg,
\$want_data, \$in_data);
$request = '';
}
}
if (can_read($from_server)) {
last unless safeappend(\$response, $from_server);
if (is_entire_response($response)) {
handle_response($to_client, $response, $verb, $arg,
\$want_data, \$in_data);
$response = '';
($verb, $arg) = ('','');
}
}
}
}
sub wait_crashed { my ($wstat) = @_;
return $wstat & 127;
}
sub teardown_proxy_and_exit { my ($child, $from_server, $to_server) = @_;
my $wstat;
close($from_server);
close($to_server);
die if (waitpid($child, 0) == -1); $wstat = $?;
die if (wait_crashed($wstat));
_exit($exitcode);
}
sub be_parent { my ($from_client, $to_client,
$from_proxy, $to_proxy,
$from_server, $to_server,
$child) = @_;
setup_proxy($from_proxy, $to_proxy);
do_proxy_stuff($from_client, $to_server, $from_server, $to_client);
teardown_proxy_and_exit($child, $from_server, $to_server);
}
sub main { my (@args) = @_;
my $from_client;
my ($from_proxy, $to_server);
my ($from_server, $to_proxy);
my $to_client;
my $child;
die_usage() unless @args >= 1;
$from_client = \*STDIN;
mypipe(\$from_proxy, \$to_server);
mypipe(\$from_server, \$to_proxy);
$to_client = \*STDOUT;
if ($child = fork()) {
be_parent($from_client, $to_client,
$from_proxy, $to_proxy,
$from_server, $to_server,
$child);
} elsif (defined $child) {
be_child($from_proxy, $to_proxy,
$from_server, $to_server,
@args);
} else {
die_fork();
}
}
main(@ARGV);
#include "auto_qmail.h"
#include "commands.h"
#include "fd.h"
#include "sig.h"
#include "stralloc.h"
#include "substdio.h"
#include "alloc.h"
#include "wait.h"
#include "str.h"
#include "byte.h"
#include "now.h"
#include "fmt.h"
#include "scan.h"
#include "exit.h"
#include "readwrite.h"
#include "timeoutread.h"
#include "timeoutwrite.h"
#include "base64.h"
#include "case.h"
#include "env.h"
#include "control.h"
#include "error.h"
static int timeout = 1200;
void die() { _exit(1); }
void die_noretry() { _exit(12); }
int safewrite(int fd,char *buf,int len) {
int r;
r = timeoutwrite(timeout,fd,buf,len);
if (r <= 0) die();
return r;
}
char ssoutbuf[128];
substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof ssoutbuf);
void puts(char *s) { substdio_puts(&ssout,s); }
void flush() { substdio_flush(&ssout); }
void pop3_err(char *s) { puts("-ERR "); puts(s); puts("\r\n"); flush(); }
void smtp_out(char *s) { puts(s); puts("\r\n"); flush(); }
struct authup_error {
char *name;
char *message;
char *smtpcode;
char *smtperror;
void (*die)();
};
struct authup_error e[] = {
{ "control", "unable to read controls", "421", "4.3.0", die }
, { "nomem", "out of memory", "451", "4.3.0", die }
, { "alarm", "timeout", "451", "4.4.2", die_noretry }
, { "pipe", "unable to open pipe", "454", "4.3.0", die }
, { "write", "unable to write pipe", "454", "4.3.0", die }
, { "fork", "unable to fork", "454", "4.3.0", die }
, { "child", "aack, child crashed", "454", "4.3.0", die }
, { "badauth", "authorization failed", "535", "5.7.0", die }
, { "noauth", "auth type unimplemented", "504", "5.5.1", die }
, { "input", "malformed auth input", "501", "5.5.4", die }
, { "authabrt","auth exchange cancelled", "501", "5.0.0", die }
, { 0, "unknown or unspecified error", "421", "4.3.0", die }
};
void pop3_auth_error(struct authup_error ae) {
puts("-ERR");
puts(" qmail-authup ");
puts(ae.message);
}
void smtp_auth_error(struct authup_error ae) {
puts(ae.smtpcode);
puts(" qmail-authup ");
puts(ae.message);
puts(" (#");
puts(ae.smtperror);
puts(")");
}
void (*protocol_error)();
void authup_die(const char *name) {
int i;
for (i = 0;e[i].name;++i) if (case_equals(e[i].name,name)) break;
protocol_error(e[i]);
puts("\r\n");
flush();
e[i].die();
}
void die_usage() { puts("usage: qmail-authup <pop3|smtp> subprogram\n"); flush(); die(); }
void smtp_err_authoriz() { smtp_out("530 qmail-authup authentication required (#5.7.1)"); }
void pop3_err_authoriz() { pop3_err("qmail-authup authorization first"); }
void pop3_err_syntax() { pop3_err("qmail-authup syntax error"); }
void pop3_err_wantuser() { pop3_err("qmail-authup USER first"); }
int saferead(int fd,char *buf,int len) {
int r;
r = timeoutread(timeout,fd,buf,len);
if (r == -1) if (errno == error_timeout) authup_die("alarm");
if (r <= 0) die();
return r;
}
char ssinbuf[128];
substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof ssinbuf);
stralloc hostname = {0};
char **childargs;
substdio ssup;
char upbuf[128];
void pop3_okay() { puts("+OK \r\n"); flush(); }
void pop3_quit() { pop3_okay(); _exit(0); }
void smtp_quit() { puts("221 "); smtp_out(hostname.s); _exit(0); }
int is_checkpassword_failure(int exitcode) {
return (exitcode == 1 || exitcode == 2 || exitcode == 111);
}
stralloc username = {0};
stralloc password = {0};
stralloc timestamp = {0};
void checkpassword(stralloc *username,stralloc *password,stralloc *timestamp) {
int child;
int wstat;
int pi[2];
close(3);
if (pipe(pi) == -1) authup_die("pipe");
if (pi[0] != 3) authup_die("pipe");
switch(child = fork()) {
case -1:
authup_die("fork");
case 0:
close(pi[1]);
sig_pipedefault();
if (!env_put2("AUTHUSER",username->s)) authup_die("nomem");
execvp(*childargs,childargs);
_exit(1);
}
close(pi[0]);
substdio_fdbuf(&ssup,write,pi[1],upbuf,sizeof upbuf);
if (!stralloc_0(username)) authup_die("nomem");
if (substdio_put(&ssup,username->s,username->len) == -1) authup_die("write");
byte_zero(username->s,username->len);
if (!stralloc_0(password)) authup_die("nomem");
if (substdio_put(&ssup,password->s,password->len) == -1) authup_die("write");
byte_zero(password->s,password->len);
if (!stralloc_0(timestamp)) authup_die("nomem");
if (substdio_put(&ssup,timestamp->s,timestamp->len) == -1) authup_die("write");
byte_zero(timestamp->s,timestamp->len);
if (substdio_flush(&ssup) == -1) authup_die("write");
close(pi[1]);
byte_zero(upbuf,sizeof upbuf);
if (wait_pid(&wstat,child) == -1) die();
if (wait_crashed(wstat)) authup_die("child");
if (is_checkpassword_failure(wait_exitcode(wstat))) authup_die("badauth");
die_noretry();
}
static char unique[FMT_ULONG + FMT_ULONG + 3];
void pop3_greet() {
char *s;
s = unique;
s += fmt_uint(s,getpid());
*s++ = '.';
s += fmt_ulong(s,(unsigned long) now());
*s++ = '@';
*s++ = 0;
puts("+OK <");
puts(unique);
puts(hostname.s);
puts(">\r\n");
flush();
}
static int seenuser = 0;
void pop3_user(char *arg) {
if (!*arg) { pop3_err_syntax(); return; }
pop3_okay();
seenuser = 1;
if (!stralloc_copys(&username,arg)) authup_die("nomem");
}
void pop3_pass(char *arg) {
if (!seenuser) { pop3_err_wantuser(); return; }
if (!*arg) { pop3_err_syntax(); return; }
if (!stralloc_copys(&password,arg)) authup_die("nomem");
byte_zero(arg,str_len(arg));
if (!stralloc_copys(&timestamp,"<")) authup_die("nomem");
if (!stralloc_cats(&timestamp,unique)) authup_die("nomem");
if (!stralloc_cats(&timestamp,hostname.s)) authup_die("nomem");
if (!stralloc_cats(&timestamp,">")) authup_die("nomem");
checkpassword(&username,&password,&timestamp);
}
void smtp_greet() {
puts("220 ");
puts(hostname.s);
puts(" ESMTP\r\n");
flush();
}
void smtp_helo(char *arg) {
puts("250 ");
smtp_out(hostname.s);
}
void smtp_ehlo(char *arg) {
puts("250-");
puts(hostname.s);
puts("\r\n250-AUTH LOGIN PLAIN");
puts("\r\n250-AUTH=LOGIN PLAIN");
smtp_out("\r\n250-PIPELINING\r\n250 8BITMIME");
}
static stralloc authin = {0};
void smtp_authgetl() {
int i;
if (!stralloc_copys(&authin,"")) authup_die("nomem");
for (;;) {
if (!stralloc_readyplus(&authin,1)) authup_die("nomem"); /* XXX */
i = substdio_get(&ssin,authin.s + authin.len,1);
if (i != 1) die();
if (authin.s[authin.len] == '\n') break;
++authin.len;
}
if (authin.len > 0) if (authin.s[authin.len - 1] == '\r') --authin.len;
authin.s[authin.len] = 0;
if (*authin.s == '*' && *(authin.s + 1) == 0) authup_die("authabrt");
if (authin.len == 0) authup_die("input");
}
void auth_login(char *arg) {
int r;
if (*arg) {
if ((r = b64decode(arg,str_len(arg),&username)) == 1) authup_die("input");
}
else {
smtp_out("334 VXNlcm5hbWU6"); /* Username: */
smtp_authgetl();
if ((r = b64decode(authin.s,authin.len,&username)) == 1) authup_die("input");
}
if (r == -1) authup_die("nomem");
smtp_out("334 UGFzc3dvcmQ6"); /* Password: */
smtp_authgetl();
if ((r = b64decode(authin.s,authin.len,&password)) == 1) authup_die("input");
if (r == -1) authup_die("nomem");
if (!username.len || !password.len) authup_die("input");
checkpassword(&username,&password,&timestamp);
}
static stralloc resp = {0};
void auth_plain(char *arg) {
int r, id = 0;
if (*arg) {
if ((r = b64decode(arg,str_len(arg),&resp)) == 1) authup_die("input");
}
else {
smtp_out("334 ");
smtp_authgetl();
if ((r = b64decode(authin.s,authin.len,&resp)) == 1) authup_die("input");
}
if (r == -1 || !stralloc_0(&resp)) authup_die("nomem");
while (resp.s[id]) id++; /* ignore authorize-id */
if (resp.len > id + 1)
if (!stralloc_copys(&username,resp.s + id + 1)) authup_die("nomem");
if (resp.len > id + username.len + 2)
if (!stralloc_copys(&password,resp.s + id + username.len + 2)) authup_die("nomem");
if (!username.len || !password.len) authup_die("input");
checkpassword(&username,&password,&timestamp);
}
void smtp_auth(char *arg) {
int i;
char *cmd = arg;
i = str_chr(cmd,' ');
arg = cmd + i;
while (*arg == ' ') ++arg;
cmd[i] = 0;
if (case_equals("login",cmd)) auth_login(arg);
if (case_equals("plain",cmd)) auth_plain(arg);
authup_die("noauth");
}
void smtp_help() {
smtp_out("214 qmail-authup home page: https://schmonz.com/qmail/authutils");
}
struct commands pop3commands[] = {
{ "user", pop3_user, 0 }
, { "pass", pop3_pass, 0 }
, { "quit", pop3_quit, 0 }
, { "noop", pop3_okay, 0 }
, { 0, pop3_err_authoriz, 0 }
};
struct commands smtpcommands[] = {
{ "ehlo", smtp_ehlo, 0 }
, { "helo", smtp_helo, 0 }
, { "auth", smtp_auth, flush }
, { "help", smtp_help, 0 }
, { "quit", smtp_quit, 0 }
, { 0, smtp_err_authoriz, 0 }
};
struct protocol {
char *name;
void (*error)();
void (*greet)();
struct commands *c;
};
struct protocol p[] = {
{ "pop3", pop3_auth_error, pop3_greet, pop3commands }
, { "smtp", smtp_auth_error, smtp_greet, smtpcommands }
, { 0, die_usage, die_usage, 0 }
};
int control_readgreeting(char *p) {
stralloc file = {0};
int retval;
if (!stralloc_copys(&file,"control/")) authup_die("nomem");
if (!stralloc_cats(&file,p)) authup_die("nomem");
if (!stralloc_cats(&file,"greeting")) authup_die("nomem");
if (!stralloc_0(&file)) authup_die("nomem");
retval = control_rldef(&hostname,file.s,1,(char *) 0);
if (retval != 1) retval = -1;
if (!stralloc_0(&hostname)) authup_die("nomem");
return retval;
}
int control_readtimeout(char *p) {
stralloc file = {0};
if (!stralloc_copys(&file,"control/timeout")) authup_die("nomem");
if (!stralloc_cats(&file,p)) authup_die("nomem");
if (!stralloc_cats(&file,"d")) authup_die("nomem");
if (!stralloc_0(&file)) authup_die("nomem");
return control_readint(&timeout,file.s);
}
int should_greet() {
char *x;
int r;
x = env_get("REUP");
if (!x) return 1;
if (!scan_ulong(x,&r)) return 1;
if (r > 1) return 0;
return 1;
}
void doprotocol(struct protocol p) {
protocol_error = p.error;
if (chdir(auto_qmail) == -1) authup_die("control");
if (control_init() == -1) authup_die("control");
if (control_readgreeting(p.name) == -1) authup_die("control");
if (control_readtimeout(p.name) == -1) authup_die("control");
if (should_greet()) p.greet();
commands(&ssin,p.c);
die();
}
int main(int argc,char **argv) {
char *protocol;
sig_alarmcatch(die);
sig_pipeignore();
protocol = argv[1];
if (!protocol) die_usage();
childargs = argv + 2;
if (!*childargs) die_usage();
for (int i = 0; p[i].name; ++i)
if (case_equals(p[i].name,protocol))
doprotocol(p[i]);
die_usage();
}
#include "case.h"
#include "env.h"
#include "error.h"
#include "fd.h"
#include "fmt.h"
#include "readwrite.h"
#include "select.h"
#include "str.h"
#include "stralloc.h"
#include "substdio.h"
#include "wait.h"
#define GREETING_PSEUDOVERB "greeting"
#define HOMEPAGE "https://schmonz.com/qmail/authutils"
#define PIPE_READ_BUFFER_SIZE SUBSTDIO_INSIZE
void die() { _exit(1); }
char sserrbuf[SUBSTDIO_OUTSIZE];
substdio sserr = SUBSTDIO_FDBUF(write,2,sserrbuf,sizeof sserrbuf);
void dieerrflush(char *s) {
substdio_putsflush(&sserr,"qmail-fixsmtpio: ");
substdio_putsflush(&sserr,s);
substdio_putsflush(&sserr,"\n");
die();
}
void die_usage() { dieerrflush("usage: qmail-fixsmtpio prog [ arg ... ]"); }
void die_pipe() { dieerrflush("unable to open pipe"); }
void die_fork() { dieerrflush("unable to fork"); }
void die_read() { dieerrflush("unable to read"); }
void die_write() { dieerrflush("unable to write"); }
void die_nomem() { dieerrflush("out of memory"); }
struct request_response {
stralloc *client_request;
stralloc *client_verb;
stralloc *client_arg;
stralloc *proxy_request;
stralloc *server_response;
stralloc *proxy_response;
};
int exitcode = 0;
void cat(stralloc *to,stralloc *from) {
if (!stralloc_cat(to,from)) die_nomem();
}
void catb(stralloc *to,char *buf,int len) {
if (!stralloc_catb(to,buf,len)) die_nomem();
}
void cats(stralloc *to,char *from) {
if (!stralloc_cats(to,from)) die_nomem();
}
void copy(stralloc *to,stralloc *from) {
if (!stralloc_copy(to,from)) die_nomem();
}
void copyb(stralloc *to,char *buf,int len) {
if (!stralloc_copyb(to,buf,len)) die_nomem();
}
void copys(stralloc *to,char *from) {
if (!stralloc_copys(to,from)) die_nomem();
}
void blank(stralloc *sa) {
copys(sa,"");
}
int starts(stralloc *haystack,char *needle) {
return stralloc_starts(haystack,needle);
}
void strip_last_eol(stralloc *sa) {
if (sa->len > 0 && sa->s[sa->len-1] == '\n') sa->len--;
if (sa->len > 0 && sa->s[sa->len-1] == '\r') sa->len--;
}
int accepted_data(stralloc *response) {
return starts(response,"354 ");
}
void munge_timeout(stralloc *response) {
exitcode = 16;
}
void munge_greeting(stralloc *response) {
char *x;
char uid[FMT_ULONG];
if (starts(response,"4")) exitcode = 14;
else if (starts(response,"5")) exitcode = 15;
else {
copys(response,"235 ok");
x = env_get("AUTHUSER");
if (x) {
cats(response,", ");
cats(response,x);
cats(response,",");
}
cats(response," go ahead ");
str_copy(uid + fmt_ulong(uid,getuid()),"");
cats(response,uid);
cats(response," (#2.0.0)\r\n");
}
}
void munge_help(stralloc *response) {
stralloc munged = {0};
copys(&munged,"214 qmail-fixsmtpio home page: ");
cats(&munged, HOMEPAGE);
cats(&munged, "\r\n");
cat(&munged,response);
copy(response,&munged);
}
void munge_test(stralloc *response) {
strip_last_eol(response);
cats(response," and also it's mungeable\r\n");
}
void munge_ehlo(stralloc *response) {
stralloc munged = {0};
stralloc line = {0};
stralloc subline = {0};
char *avoids[] = {
"AUTH ",
0,
};
for (int i = 0; i < response->len; i++) {
if (!stralloc_append(&line,i + response->s)) die_nomem();
if (response->s[i] == '\n' || i == response->len - 1) {
copyb(&subline,line.s + 4,line.len - 4);
int keep = 1;
char *s;
for (int j = 0; (s = avoids[j]); j++)
if (starts(&line,"250"))
if (starts(&subline,s))
keep = 0;
if (keep) cat(&munged,&line);
blank(&line);
blank(&subline);
}
}
copy(response,&munged);
}
int verb_matches(char *s,stralloc *sa) {
if (!sa->len) return 0;
return !case_diffb(s,sa->len,sa->s);
}
void change_every_line_fourth_char_to_dash(stralloc *multiline) {
int pos = 0;
for (int i = 0; i < multiline->len; i++) {
if (multiline->s[i] == '\n') pos = -1;
if (pos == 3) multiline->s[i] = '-';
pos++;
}
}
void change_last_line_fourth_char_to_space(stralloc *multiline) {
int pos = 0;
for (int i = multiline->len - 2; i >= 0; i--) {
if (multiline->s[i] == '\n') {
pos = i + 1;
break;
}
}
multiline->s[pos+3] = ' ';
}
void reformat_multiline_response(stralloc *response) {
change_every_line_fourth_char_to_dash(response);
change_last_line_fourth_char_to_space(response);
}
void munge_response(stralloc *response,stralloc *verb) {
if (verb_matches(GREETING_PSEUDOVERB,verb)) munge_greeting(response);
if (verb_matches("help",verb)) munge_help(response);
if (verb_matches("test",verb)) munge_test(response);
if (verb_matches("ehlo",verb)) munge_ehlo(response);
reformat_multiline_response(response);
}
int is_entire_line(stralloc *sa) {
return sa->len > 0 && sa->s[sa->len - 1] == '\n';
}
int could_be_final_response_line(stralloc *line) {
return line->len >= 4 && line->s[3] == ' ';
}
int is_entire_response(stralloc *server_response) {
stralloc lastline = {0};
int pos = 0;
if (!is_entire_line(server_response)) return 0;
for (int i = server_response->len - 2; i >= 0; i--) {
if (server_response->s[i] == '\n') {
pos = i + 1;
break;
}
}
copyb(&lastline,server_response->s+pos,server_response->len-pos);
return could_be_final_response_line(&lastline);
}
void use_as_stdin(int fd) {
if (fd_move(0,fd) == -1) die_pipe();
}
void use_as_stdout(int fd) {
if (fd_move(1,fd) == -1) die_pipe();
}
void mypipe(int *from,int *to) {
int pi[2];
if (pipe(pi) == -1) die_pipe();
*from = pi[0];
*to = pi[1];
}
void setup_server(int from_proxy,int to_server,
int from_server,int to_proxy) {
close(from_server);
close(to_server);
use_as_stdin(from_proxy);
use_as_stdout(to_proxy);
}
void exec_server_and_never_return(char **argv) {
execvp(*argv,argv);
die();
}
void be_child(int from_proxy,int to_proxy,
int from_server,int to_server,
char **argv) {
setup_server(from_proxy,to_server,from_server,to_proxy);
exec_server_and_never_return(argv);
}
void setup_proxy(int from_proxy,int to_proxy) {
close(from_proxy);
close(to_proxy);
}
fd_set fds;
void want_to_read(int fd1,int fd2) {
FD_ZERO(&fds);
FD_SET(fd1,&fds);
FD_SET(fd2,&fds);
}
int can_read(int fd) {
return FD_ISSET(fd,&fds);
}
int max(int a,int b) {
if (a > b) return a;
return b;
}
int can_read_something(int fd1,int fd2) {
int ready;
ready = select(1+max(fd1,fd2),&fds,(fd_set *)0,(fd_set *)0,(struct timeval *) 0);
if (ready == -1 && errno != error_intr) die_read();
return ready;
}
int saferead(int fd,char *buf,int len) {
int r;
r = read(fd,buf,len);
if (r == -1) if (errno != error_intr) die_read();
return r;
}
int safeappend(stralloc *sa,int fd,char *buf,int len) {
int r;
r = saferead(fd,buf,len);
catb(sa,buf,r);
return r;
}
int is_last_line_of_data(stralloc *r) {
return (r->len == 3 && r->s[0] == '.' && r->s[1] == '\r' && r->s[2] == '\n');
}
void parse_client_request(stralloc *verb,stralloc *arg,stralloc *request) {
int i;
for (i = 0; i < request->len; i++)
if (request->s[i] == ' ') break;
i++;
if (i > request->len) {
copy(verb,request);
blank(arg);
} else {
copyb(verb,request->s,i-1);
copyb(arg,request->s+i,request->len-i);
}
strip_last_eol(verb);
strip_last_eol(arg);
}
void logit(char logprefix,stralloc *sa) {
substdio_putflush(&sserr,&logprefix,1);
substdio_putsflush(&sserr,": ");
substdio_putflush(&sserr,sa->s,sa->len);
if (!is_entire_line(sa)) substdio_putsflush(&sserr,"\r\n");
}
void safewrite(int fd,stralloc *sa) {
if (write(fd,sa->s,sa->len) == -1) die_write();
}
void smtp_test(stralloc *response,stralloc *verb,stralloc *arg) {
copys(response,"250 qmail-fixsmtpio test ok: ");
catb(response,arg->s,arg->len);
cats(response,"\r\n");
}
void smtp_unimplemented(stralloc *response,stralloc *verb,stralloc *arg) {
copys(response,"502 unimplemented (#5.5.1)\r\n");
}
struct internal_verb {
char *name;
void (*func)();
};
struct internal_verb verbs[] = {
{ "test", smtp_test }
, { "auth", smtp_unimplemented }
, { "starttls", smtp_unimplemented }
, { 0, 0 }
};
void *handle_internally(stralloc *verb,stralloc *arg) {
for (int i = 0; verbs[i].name; ++i)
if (verb_matches(verbs[i].name,verb))
return verbs[i].func;
return 0;
}
void construct_proxy_request(stralloc *proxy_request,
stralloc *verb,stralloc *arg,
stralloc *client_request,
int *want_data,int *in_data) {
if (*in_data) {
copy(proxy_request,client_request);
if (is_last_line_of_data(proxy_request)) *in_data = 0;
} else {
if (handle_internally(verb,arg)) {
copys(proxy_request,"NOOP qmail-fixsmtpio ");
cat(proxy_request,client_request);
} else {
if (verb_matches("data",verb)) *want_data = 1;
copy(proxy_request,client_request);
}
}
}
void construct_proxy_response(stralloc *proxy_response,
stralloc *verb,stralloc *arg,
stralloc *server_response,
int request_received,
int *want_data,int *in_data) {
void (*func)();
if (*want_data) {
*want_data = 0;
if (accepted_data(server_response)) *in_data = 1;
}
if ((func = handle_internally(verb,arg))) {
func(proxy_response,verb,arg);
} else {
copy(proxy_response,server_response);
}
munge_response(proxy_response,verb);
if (!verb->len && !request_received) munge_timeout(proxy_response);
}
void request_response_init(struct request_response *rr) {
static stralloc client_request = {0},
client_verb = {0},
client_arg = {0},
proxy_request = {0},
server_response = {0},
proxy_response = {0};
blank(&client_request); rr->client_request = &client_request;
blank(&client_verb); rr->client_verb = &client_verb;
blank(&client_arg); rr->client_arg = &client_arg;
blank(&proxy_request); rr->proxy_request = &proxy_request;
blank(&server_response); rr->server_response = &server_response;
blank(&proxy_response); rr->proxy_response = &proxy_response;
}
void handle_client_request(int to_server,struct request_response *rr,
int *want_data,int *in_data) {
logit('1',rr->client_request);
if (!*in_data)
parse_client_request(rr->client_verb,rr->client_arg,rr->client_request);
logit('2',rr->client_verb);
logit('3',rr->client_arg);
construct_proxy_request(rr->proxy_request,
rr->client_verb,rr->client_arg,
rr->client_request,
want_data,in_data);
logit('4',rr->proxy_request);
safewrite(to_server,rr->proxy_request);
if (*in_data) {
blank(rr->client_request);
blank(rr->proxy_request);
}
}
void handle_server_response(int to_client,struct request_response *rr,
int *want_data,int *in_data) {
logit('5',rr->server_response);
construct_proxy_response(rr->proxy_response,
rr->client_verb,rr->client_arg,
rr->server_response,
rr->client_request->len,
want_data,in_data);
logit('6',rr->proxy_response);
safewrite(to_client,rr->proxy_response);
request_response_init(rr);
}
int request_needs_handling(struct request_response *rr) {
return rr->client_request->len && !rr->proxy_request->len;
}
int response_needs_handling(struct request_response *rr) {
return rr->server_response->len && !rr->proxy_response->len;
}
void prepare_for_handling(stralloc *to,stralloc *from) {
copy(to,from);
blank(from);
}
void do_proxy_stuff(int from_client,int to_server,
int from_server,int to_client) {
char buf[PIPE_READ_BUFFER_SIZE];
int want_data = 0, in_data = 0;
stralloc partial_request = {0}, partial_response = {0};
struct request_response rr;
request_response_init(&rr);
copys(rr.client_verb,GREETING_PSEUDOVERB);
for (;;) {
if (request_needs_handling(&rr))
handle_client_request(to_server,&rr,&want_data,&in_data);
if (response_needs_handling(&rr))
handle_server_response(to_client,&rr,&want_data,&in_data);
want_to_read(from_client,from_server);
if (!can_read_something(from_client,from_server)) continue;
if (can_read(from_client)) {
if (!safeappend(&partial_request,from_client,buf,sizeof buf)) break;
if (is_entire_line(&partial_request))
prepare_for_handling(rr.client_request,&partial_request);
}
if (can_read(from_server)) {
if (!safeappend(&partial_response,from_server,buf,sizeof buf)) break;
if (is_entire_response(&partial_response))
prepare_for_handling(rr.server_response,&partial_response);
}
}
}
void teardown_proxy_and_exit(int child,int from_server,int to_server) {
int wstat;
close(from_server);
close(to_server);
if (wait_pid(&wstat,child) == -1) die();
if (wait_crashed(wstat)) die();
_exit(exitcode);
}
void be_parent(int from_client,int to_client,
int from_proxy,int to_proxy,
int from_server,int to_server,
int child) {
setup_proxy(from_proxy,to_proxy);
do_proxy_stuff(from_client,to_server,from_server,to_client);
teardown_proxy_and_exit(child,from_server,to_server);
}
int main(int argc,char **argv) {
int from_client;
int from_proxy, to_server;
int from_server, to_proxy;
int to_client;
int child;
argv += 1; if (!*argv) die_usage();
from_client = 0;
mypipe(&from_proxy,&to_server);
mypipe(&from_server,&to_proxy);
to_client = 1;
if ((child = fork()))
be_parent(from_client,to_client,
from_proxy,to_proxy,
from_server,to_server,
child);
else if (child == 0)
be_child(from_proxy,to_proxy,
from_server,to_server,
argv);
else
die_fork();
}
#include "env.h"
#include "fmt.h"
#include "readwrite.h"
#include "scan.h"
#include "sgetopt.h"
#include "str.h"
#include "substdio.h"
#include "wait.h"
void die() { _exit(1); }
char sserrbuf[128];
substdio sserr = SUBSTDIO_FDBUF(write,2,sserrbuf,sizeof sserrbuf);
void errflush(char *s) {
substdio_puts(&sserr,s);
substdio_puts(&sserr,"\n");
substdio_flush(&sserr);
}
void die_usage() { errflush("usage: qmail-reup [ -s seconds ] [ -t tries ] subprogram"); die(); }
void die_fork() { errflush("qmail-reup unable to fork"); die(); }
void die_nomem() { errflush("qmail-reup out of memory"); die(); }
int try(int attempt,char **childargs) {
int child;
int wstat;
char reup[FMT_ULONG];
switch (child = fork()) {
case -1:
die_fork();
break;
case 0:
str_copy(reup + fmt_ulong(reup,attempt),"");
if (!env_put2("REUP",reup)) die_nomem();
execvp(*childargs,childargs);
die();
}
if (wait_pid(&wstat,child) == -1) die();
if (wait_crashed(wstat)) die();
return wait_exitcode(wstat);
}
int keep_trying(int attempt,int max) {
if (max == 0) return 1;
if (attempt <= max) return 1;
return 0;
}
int stop_trying(int exitcode) {
switch (exitcode) {
case 0:
case 12:
return 1;
default:
return 0;
}
}
int main(int argc,char **argv) {
int exitcode;
int opt;
int seconds;
int tries;
seconds = 0;
tries = 0;
while ((opt = getopt(argc,argv,"s:t:")) != opteof) {
switch (opt) {
case 's':
if (!scan_ulong(optarg,&seconds)) die_usage();
break;
case 't':
if (!scan_ulong(optarg,&tries)) die_usage();
break;
case '?':
default:
die_usage();
}
}
argc -= optind;
argv += optind;
if (!*argv) die_usage();
for (int i = 1; keep_trying(i,tries); i++) {
exitcode = try(i,argv);
if (stop_trying(exitcode)) _exit(exitcode);
sleep(seconds);
}
_exit(exitcode);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment