/* * Copyright (c) 2000-2001 bivio, Inc. All rights reserved. * * Visit http://www.bivio.biz for more info. * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; If not, you may get a copy from: * http://www.opensource.org/licenses/lgpl-license.html * * * $Id: b-sendmail-http.c,v 1.27 2003/05/23 20:33:38 david Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* Usage: b-sendmail-http client-addr recipient host:port/url local-agent agent-args Forwards mail to "local-agent agent-args", if can getpwnam on recipient. Strips off a "+anything" emulating sendmail's local rule. If not a local user, forwards the mail message, wrapped in a multipart/form-data, to the HTTP server at host:port/url. url may contain "%s" in which case it will get the name of the recipient embedded at that location. Don't use URLs that have other percent signs in them. Any message for a recipient matching "^ignore-" will be discarded. Notes Doesn't connect to http server until stdin is read. stdin isn't closed until the program exits. We wait for http server to give response. Doesn't free memory. This shouldn't be a problem as this is a "program" and will exit. If "called", this code is broken because it will leak. Debugging: Use "stdout" for the host:port argument to talk to the terminal. Debugging: sendmail -d21.4 will print the output of the rule sets. -d13.1 will print the delivery agents In general, this stuff is tricky to debug, so test sendmail from this program separately. To add to sendmail, you need to direct all local (non-special) deliveries to the following delivery agent Mbsa, P=/usr/local/bin/b-sendmail-http, F=9:|/@ADFhlMnsPS, S=EnvFromL/HdrFromL, R=EnvToL/HdrToL, T=DNS/RFC822/X-Unix, A=b-sendmail-http ${client_addr} $u localhost:80/mail-handler /usr/bin/procmail -Y -a $h -d $u To direct the mail, look for lines of the form: R$+ $#local $: $1 regular local names R$+ < @ $=w . > $#local $: $1 regular local names And change them to: R$+ $#bsa $: $1 regular local names R$+ < @ $=w . > $#bsa $: $1 regular local names Don't install a smart mailer or hub. */ extern char **environ; static char *cvsid = "$Id: b-sendmail-http.c,v 1.27 2003/05/23 20:33:38 david Exp $"; #define CHUNK_SIZE 0x40000 /* taken from a Netscape-generated form, should be good enough */ static char* mime_boundary = "---------------------------1456087194795574728938443769"; extern int fileno(FILE *stream); static int copy_stdin_to_http(int sock, char* form, char *client_addr, char *recipient, char *http_server, char *url); static int read_http_reply(int sock); static int write_formdata(int sock, char *form, int length, char *http_server, char *url, char *client_addr, char *recipient); static int parse_url(char *http_server, char *recipient, char **url); static int connect_socket(int *sock, char *http_server); static int add_formfield(char *form, int offset, char *name, char *value); static struct hostent* ngethostbyname(char *name); static void handle_error(char *msg); static int is_local_user(char *recipient); static void remove_plus(char** args, char* recipient); /* If recipient is found by getpwnam(3), exec local-agent. Otherwise, copy stdin to the http server and read/process the result. */ int main(int argc, char *argv[]) { int i = 0; char *client_addr, *recipient, *http_server, *url; int sock = -1; int res = EX_TEMPFAIL; char localhost[] = "127.0.0.1"; char *buf; int size; if (argc < 6) { fprintf(stderr, "Error: too few arguments\n"); fprintf(stderr, "Usage: b-sendmail-http client-addr recipient host:port/url local-agent agent-args\n"); goto error; } client_addr = argv[++i]; if (client_addr[0] == '\0') client_addr = localhost; recipient = argv[++i]; http_server = argv[++i]; /* Also truncates http_server at the slash */ if ((res = parse_url(http_server, recipient, &url)) != EX_OK) { goto error; } /* Local users have priority. If the user exists, pass on. */ if (is_local_user(recipient)) { char *program = argv[++i]; char **args = &argv[i]; remove_plus(args, recipient); execve(program, args, environ); handle_error(program); res = EX_UNAVAILABLE; goto error; } else if (!strncmp(recipient, "ignore-", 7)) { /* Ignore all recipient names beginning with "ignore-" */ return EX_OK; } /* Sanity checks */ size = strlen(recipient); if (size > 255) { fprintf(stderr, "recipient name (len=%d) too long\n", size); res = EX_DATAERR; goto error; } if ((buf = malloc(CHUNK_SIZE)) == NULL) { handle_error("malloc"); res = EX_OSERR; goto error; } /* Sockets send sigpipe, but we check errors directly */ signal(SIGPIPE, SIG_IGN); /* Open a connection to the web server */ if((res = connect_socket(&sock, http_server)) != EX_OK) { goto error; } /* Read message from sendmail and send request to HTTP server */ if ((res = copy_stdin_to_http(sock, buf, client_addr, recipient, http_server, url)) != EX_OK) { goto error; } /* Wait for the response */ res = read_http_reply(sock); /* fall through, even on success */ error: if (sock != -1) close(sock); return res; } /* Reads RFC822 message from stdin and writes to http server. */ static int copy_stdin_to_http(int sock, char* buf, char *client_addr, char *recipient, char *http_server, char *url) { int in = fileno(stdin); int offset = 0; int size; int res; /* Create fields for the multipart/form-data */ offset = add_formfield(buf, offset, "v", "2"); offset = add_formfield(buf, offset, "client_addr", client_addr); offset = add_formfield(buf, offset, "recipient", recipient); offset = add_formfield(buf, offset, "message", ""); size = CHUNK_SIZE; /* Body is contents of stdin */ while (1) { if ((res = read(in, &buf[offset], size - offset)) == -1) { handle_error("read from stdin"); return EX_IOERR; } if (res == 0) break; offset += res; if (offset == size) { size += CHUNK_SIZE; if ((buf = realloc(buf, size)) == NULL) { handle_error("realloc"); return EX_OSERR; } } } memcpy(&buf[offset], "\r\n", 2); offset += 2; memcpy(&buf[offset], mime_boundary, strlen(mime_boundary)); offset += strlen(mime_boundary); memcpy(&buf[offset], "--\r\n", 4); offset += 4; return write_formdata(sock, buf, offset, http_server, url, client_addr, recipient); } /* Write the buffer to the socket. */ static int write_formdata(int sock, char *buf, int length, char *http_server, char *url, char *client_addr, char* recipient) { char http_header[1024]; char hold_length[80]; int offset = 0; int res; /* Construct HTTP header */ sprintf(http_header, "POST %s HTTP/1.0", url); strcat(http_header, "\r\nHost: "); strcat(http_header, http_server); strcat(http_header, "\r\nUser-Agent: b-sendmail-http"); strcat(http_header, "\r\nReferer: "); strcat(http_header, client_addr); strcat(http_header, "\r\nVia: "); strcat(http_header, client_addr); strcat(http_header, "\r\nContent-type: multipart/form-data; boundary="); strcat(http_header, mime_boundary); sprintf(hold_length, "\r\nContent-Length: %d\r\n\r\n", length); strcat(http_header, hold_length); /* Send header */ if ((res = write(sock, &http_header, strlen(http_header))) == -1) { handle_error("writing header to http server"); close(sock); return EX_PROTOCOL; } /* Send body, containing the HTTP form */ while (length != offset) { if ((res = write(sock, &buf[offset], length - offset)) == -1) { handle_error("writing message to http server"); close(sock); return EX_PROTOCOL; } offset += res; } return EX_OK; } /* Open the socket. */ static int connect_socket(int *sock, char *http_server) { char* colon; int port; struct hostent *hptr; struct sockaddr_in sock_name; if ((colon = index(http_server, ':')) == NULL) { if (!strcmp(http_server, "stdout")) { *sock = fileno(stdout); return EX_OK; } else { fprintf(stderr, "%s: not in host:port format\n", http_server); return EX_CONFIG; } } *colon = '\0'; port = atoi(++colon); if ((hptr = ngethostbyname(http_server)) == NULL) { fprintf(stderr,"Cannot lookup host (%s) by name\n", http_server); return EX_SOFTWARE; } memcpy(&sock_name.sin_addr, hptr->h_addr, hptr->h_length); sock_name.sin_family = AF_INET; sock_name.sin_port = htons(port); if ((*sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { handle_error("create TCP socket"); return EX_SOFTWARE; } if (connect(*sock, (struct sockaddr *) &sock_name, sizeof(sock_name))) { handle_error("connect to TCP socket"); return EX_TEMPFAIL; } return EX_OK; } /* Read the response from the http server and map to an EX_* code. Returns a program exit code (EX_OK, EX_TEMPFAIL, etc.) */ static int read_http_reply(int sock) { int offset = 0; char buf[CHUNK_SIZE + 1]; char *c, *cp2; int res, i; /* Accept any HTTP response */ char* http_id = "HTTP"; struct { char *http_code; int mail_code; char* mail_reason; } map_reply[] = { { "200", EX_OK, "" }, { "302", EX_OK, "" }, { "400", EX_DATAERR, "Bad Request" }, { "401", EX_NOPERM, "Unauthorized Access" }, { "403", EX_NOPERM, "Access Forbidden" }, { "404", EX_NOUSER, "User Not Found" }, { "409", EX_TEMPFAIL, "Resource Conflict" }, { "500", EX_SOFTWARE, "Internal Server Error" }, { "502", EX_TEMPFAIL, "Gateway not reachable" }, { "503", EX_UNAVAILABLE, "Mailbox Is Full" }, { NULL }, }; while (1) { if ((res = read(sock, &buf[offset], CHUNK_SIZE - offset)) == -1) { handle_error("read reply from HTTP server"); return EX_IOERR; } if (res == 0) break; offset += res; if (offset == CHUNK_SIZE) { fprintf(stderr, "response too long (> %d bytes)\n", CHUNK_SIZE); return EX_PROTOCOL; } } c = &buf[0]; /* Expect a "HTTP" reply */ if (strncmp(c, http_id, strlen(http_id)) || (cp2 = index(c, ' ')) == NULL) { fprintf(stderr, "Expected '%s', got '%s'\n", http_id, c); return EX_TEMPFAIL; } /* Skip over the HTTP version number */ c = cp2 + 1; for (; *c == ' '; c++) /* loop */; /* Lookup the reply code */ for (i = 0; map_reply[i].http_code != NULL; i++) { if (!strncmp(c, map_reply[i].http_code, strlen(map_reply[i].http_code))) { if (strlen(map_reply[i].mail_reason) != 0) { fprintf(stderr, "Error: %s\n", map_reply[i].mail_reason); /* fprintf(stderr, "Reply: %s\n", buf); */ } return map_reply[i].mail_code; } } fprintf(stderr, "Unknown server reply: %s\n", buf); return EX_UNAVAILABLE; } /* Search for recipient, stripping off "+" from the name only if found with getpwnam. */ static int is_local_user(char* recipient) { char *at = index(recipient, '@'); char *plus = index(recipient, '+'); int ok; if (at) *at = '\0'; if (plus) *plus = '\0'; ok = getpwnam(recipient) != NULL; if (at) *at = '@'; if (plus) *plus = '+'; return ok; } /* Search for recipient, stripping off "+" from the name only if found with getpwnam. */ static void remove_plus(char** args, char* recipient) { char *snip = index(recipient, '+'); snip = snip ? snip : index(recipient, '@'); if (!snip) return; for (; *args; args++) { if (strcmp(*args, recipient) != 0) continue; snip = index(*args, '+'); snip = snip ? snip : index(*args, '@'); *snip = '\0'; return; } /* Shouldn't get here, but what to do??? */ return; } /* Like gethostbyname, but supports internet addresses of the form `N.N.N.N'. */ static struct hostent* ngethostbyname(char* name) { struct hostent *hp; unsigned long addr; addr = (unsigned long)inet_addr(name); if ((int)addr != -1) { hp = gethostbyaddr((char *)&addr, sizeof (addr), AF_INET); } else { hp = gethostbyname(name); } return hp; } static int add_formfield(char *buf, int offset, char *name, char *value) { int l; char* disposition = "\r\nContent-Disposition: form-data; name=\""; char* type = "Content-Type: message/rfc822\r\n"; memcpy(&buf[offset], mime_boundary, l = strlen(mime_boundary)); offset += l; memcpy(&buf[offset], disposition, l = strlen(disposition)); offset += l; memcpy(&buf[offset], name, l = strlen(name)); offset += l; memcpy(&buf[offset], "\"\r\n", 3); offset += 3; if ((l = strlen(value)) > 0) { memcpy(&buf[offset], "\r\n", 2); offset += 2; memcpy(&buf[offset], value, l); offset += l; } else { /* Empty value indicates this field will contain the message */ memcpy(&buf[offset], type, l = strlen(type)); offset += l; } memcpy(&buf[offset], "\r\n", 2); offset += 2; return offset; } /* Parses the url from the http server. Terminates the http_server at the url's '/'. Returns a copy of the url, possibly formatted with the recipient. */ static int parse_url(char *http_server, char *recipient, char **url) { char* slash; char* buf; if ((slash = index(http_server, '/')) == NULL) { /* invalid configuration */ fprintf(stderr, "%s: not in host:port/url format\n", http_server); return EX_CONFIG; } *slash++ = '\0'; if ((buf = malloc(strlen(slash) + strlen(recipient) + 10)) == NULL) { handle_error("malloc"); return EX_OSERR; } /* Save buffer start, replace missing '/' and replace %s */ *url = buf; *buf++ = '/'; sprintf(buf, slash, recipient); return EX_OK; } static void handle_error(char *msg) { if (msg && strlen(msg)) { /* A one-liner to the sender */ fprintf(stderr, "internal error: (errno=%d, op=%s)\n", errno, msg); } return; }