| #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(×tamp,"<")) authup_die("nomem"); | |
| if (!stralloc_cats(×tamp,unique)) authup_die("nomem"); | |
| if (!stralloc_cats(×tamp,hostname.s)) authup_die("nomem"); | |
| if (!stralloc_cats(×tamp,">")) authup_die("nomem"); | |
| checkpassword(&username,&password,×tamp); | |
| } | |
| 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,×tamp); | |
| } | |
| 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,×tamp); | |
| } | |
| 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