]> pere.pagekite.me Git - exim4-smtorp.git/blob - tor-smtp/tor-smtp.c
Change deb section from python to mail.
[exim4-smtorp.git] / tor-smtp / tor-smtp.c
1 /*
2 * tor-smtp
3 *
4 * Copyright (c) 2006 Johannes Berg <johannes@sipsolutions.net>
5 *
6 * The bidicopy() routine is from OpenBSD's netcat rewrite, plus patches
7 * from the fedora project.
8 *
9 * Copyright (c) 2001 Eric Jackson <ericj@monkey.org>
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 *
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. The name of the author may not be used to endorse or promote products
21 * derived from this software without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 */
34 #include <sys/types.h>
35 #include <sys/socket.h>
36 #include <netdb.h>
37 #include <stdlib.h>
38 #include <sys/poll.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <errno.h>
42 #include <unistd.h>
43 #include <sys/time.h>
44 #include <time.h>
45 /* for BSD: htons is in there: */
46 #include <arpa/inet.h>
47 #include "atomicio.h"
48
49 //#define DEBUG(str,args...) fprintf(stderr, str, ##args);
50 #define DEBUG(str,args...) do {} while(0)
51
52 int read_bytes_timeout(int fd, char *buf, int bytes, int timeout)
53 {
54 # define DEBUG_RETURN(str,args...) do { DEBUG(str, ##args); return 0; } while (0)
55 struct pollfd pollfd = { .fd = fd, .events = POLLIN };
56 struct timeval tv_start, tv_now;
57 int got = 0, tmp;
58
59 gettimeofday(&tv_start, NULL);
60
61 while (got < bytes && timeout >= 0) {
62 if (poll(&pollfd, 1, timeout*1000) <= 0)
63 DEBUG_RETURN("poll failed: %d (got %d)\n", errno, got);
64 gettimeofday(&tv_now, NULL);
65 timeout -= tv_now.tv_sec - tv_start.tv_sec;
66 tv_start = tv_now;
67
68 tmp = read(fd, buf+got, bytes-got);
69 if (tmp <= 0)
70 DEBUG_RETURN("read failed: %d\n", errno);
71 got += tmp;
72 }
73 return bytes;
74 #undef DEBUG_RETURN
75 }
76
77 int connect_via_tor(const char *tor_server, const char *tor_port,
78 const char *remote_server, short remote_port)
79 {
80 int fd;
81 struct addrinfo *res = NULL;
82 char socks_init[] = { 5, 1, 0 };
83 char buf[255+7]; /* must be less than 255+7+1 bytes long */
84 size_t remote_server_len = strlen(remote_server);
85
86 if (remote_server_len > sizeof(buf)-7/*5 leading bytes and port */)
87 return -1;
88
89 fd = socket(PF_INET, SOCK_STREAM, 0);
90 if (fd == -1) {
91 DEBUG("failed to create socket\n");
92 return -1;
93 }
94
95 #define DEBUG_OUT(str, args...) do { DEBUG(str,##args); goto out_close; } while (0)
96
97 if (getaddrinfo(tor_server, tor_port, NULL, &res))
98 DEBUG_OUT("getaddrinfo\n");
99 if (!res)
100 DEBUG_OUT("getaddrinfo returned empty set\n");
101
102 if (connect(fd, res->ai_addr, res->ai_addrlen))
103 DEBUG_OUT("failed to connect\n");
104
105 /* now that we are connected, set up SOCKS */
106 if (write(fd, &socks_init, sizeof(socks_init)) != sizeof(socks_init))
107 goto out_close;
108 if (!read_bytes_timeout(fd, &buf[0], 2, 120))
109 goto out_close;
110 if (buf[0] != 5 || buf[1] != 0)
111 goto out_close;
112
113 buf[0] = 5;
114 buf[1] = 1;
115 buf[2] = 0;
116 buf[3] = 3;
117 buf[4] = remote_server_len;
118 strcpy(buf+5, remote_server);
119 *(short*) (buf+5+remote_server_len) = htons(remote_port);
120 if ((size_t)write(fd, buf, 5+remote_server_len+2) != 5+remote_server_len+2)
121 goto out_close;
122 if (!read_bytes_timeout(fd, buf, 4, 120))
123 goto out_close;
124 if (buf[0] != 5 || buf[1] != 0)
125 goto out_close;
126 /* read rest of the response */
127 switch(buf[3]) {
128 case 1:
129 if (!read_bytes_timeout(fd, buf, 6, 120))
130 goto out_close;
131 break;
132 /* TODO handle other responses */
133 }
134 freeaddrinfo(res);
135
136 return fd;
137 out_close:
138 close(fd);
139 return -1;
140 }
141
142 /* read at most buflen characters into buffer,
143 * until linefeed is encountered.
144 * NB: if more characters are received they are discarded!
145 */
146 int readline(int fd, char *buf, int buflen, int timeout)
147 {
148 # define DEBUG_RETURN(str,args...) do { DEBUG(str, ##args); return 0; } while (0)
149 struct pollfd pollfd = { .fd = fd, .events = POLLIN };
150 struct timeval tv_start, tv_now;
151 int got = 0, tmp;
152 char *match;
153
154 gettimeofday(&tv_start, NULL);
155
156 while (got < buflen && timeout >= 0) {
157 if (poll(&pollfd, 1, timeout*1000) <= 0)
158 DEBUG_RETURN("poll failed: %d (got %d)\n", errno, got);
159 gettimeofday(&tv_now, NULL);
160 timeout -= tv_now.tv_sec - tv_start.tv_sec;
161 tv_start = tv_now;
162
163 tmp = read(fd, buf+got, buflen-got);
164 if (tmp <= 0)
165 DEBUG_RETURN("read failed: %d\n", errno);
166 match = memchr(buf+got, '\n', tmp);
167 if (match) {
168 if (*(match-1) == '\r')
169 match--;
170 *match = '\0';
171 return match-buf;
172 }
173 got += tmp;
174 }
175 return -1;
176 #undef DEBUG_RETURN
177 }
178
179 /*
180 * readwrite()
181 * Loop that polls on the network file descriptor and stdin.
182 */
183 void
184 bidicopy(int nfd)
185 {
186 struct pollfd pfd[2];
187 unsigned char buf[8192];
188 int n, wfd = fileno(stdin);
189 int lfd = fileno(stdout);
190 int plen;
191
192 plen = 1024;
193
194 /* Setup Network FD */
195 pfd[0].fd = nfd;
196 pfd[0].events = POLLIN;
197
198 /* Set up STDIN FD. */
199 pfd[1].fd = wfd;
200 pfd[1].events = POLLIN;
201
202 while (pfd[0].fd != -1) {
203 if ((n = poll(pfd, 2, -1)) < 0) {
204 close(nfd);
205 return;
206 }
207
208 if (n == 0)
209 return;
210
211 if (pfd[0].revents & POLLIN) {
212 if ((n = read(nfd, buf, plen)) < 0)
213 return;
214 else if (n == 0) {
215 goto shutdown_rd;
216 } else {
217 if (atomicio(vwrite, lfd, buf, n) != (size_t)n)
218 return;
219 }
220 }
221 else if (pfd[0].revents & POLLHUP) {
222 shutdown_rd:
223 shutdown(nfd, SHUT_RD);
224 pfd[0].fd = -1;
225 pfd[0].events = 0;
226 }
227
228 if (1) {
229 if(pfd[1].revents & POLLIN) {
230 if ((n = read(wfd, buf, plen)) < 0)
231 return;
232 else if (n == 0) {
233 goto shutdown_wr;
234 } else {
235 if (atomicio(vwrite, nfd, buf, n) != (size_t)n)
236 return;
237 }
238 }
239 else if (pfd[1].revents & POLLHUP) {
240 shutdown_wr:
241 shutdown(nfd, SHUT_WR);
242 pfd[1].fd = -1;
243 pfd[1].events = 0;
244 }
245 }
246 }
247 }
248
249 int main(int argc, char **argv)
250 {
251 char *tor_server = "localhost";
252 char *tor_port = "9050";
253 char *remote_server = NULL;
254 char *tmp;
255 char local_onion[255];
256 char buf[255], buf2[255];
257 int linelen;
258 int fd;
259
260 if (argc >= 2)
261 tor_server = argv[1];
262 if (argc >= 3)
263 tor_port = argv[2];
264 if (argc > 3 || (tor_server && (!strcasecmp(tor_server, "--help") || !strcasecmp(tor_server, "-h")))) {
265 fprintf(stderr, "tor-smtp [tor-server] [tor-port]\n");
266 fprintf(stderr, " (default localhost 9050)\n");
267 return 2;
268 }
269
270 printf("220 Welcome\r\n");
271 fflush(stdout);
272
273 command:
274 if ((linelen = readline(0, buf, sizeof(buf), 30)) < 0) {
275 printf("421 connection timed out\r\n");
276 fflush(stdout);
277 return 0;
278 }
279 if (linelen < 4) {
280 goto invalid;
281 }
282 if (!strncasecmp("QUIT", buf, 4)) {
283 printf("221 closing connection\r\n");
284 fflush(stdout);
285 return 0;
286 }
287 if (!strncasecmp("EHLO ", buf, 5) || !strncasecmp("HELO ", buf, 5))
288 goto helo_continue;
289 invalid:
290 printf("500 invalid command\r\n");
291 fflush(stdout);
292 goto command;
293
294 helo_continue:
295 tmp = strtok(buf+5, " ");
296 if (!tmp) {
297 printf("500 invalid EHLO/HELO: missing local name\n");
298 fflush(stdout);
299 goto command;
300 }
301 remote_server = strtok(NULL, " ");
302 if (!remote_server) {
303 printf("500 invalid EHLO/HELO: missing remote name\n");
304 goto command;
305 }
306 strncpy(local_onion, tmp, sizeof(local_onion));
307 fd = connect_via_tor(tor_server, tor_port, remote_server, 25);
308 if (fd < 0) {
309 printf("421 could not connect\r\n");
310 fflush(stdout);
311 return 0;
312 }
313
314 linelen = readline(fd, buf2, sizeof(buf2), 120);
315 if (linelen < 0) {
316 printf("421 could not connect\r\n");
317 fflush(stdout);
318 return 0;
319 }
320 if (strncmp("220 ", buf2, 4)) {
321 printf("421 server seems to not like us\r\n");
322 DEBUG("answer was: %s\n", buf2);
323 fflush(stdout);
324 return 0;
325 }
326
327 strncpy(buf+5, local_onion, sizeof(buf)-5);
328 linelen = strlen(buf);
329 *(buf+linelen) = '\r';
330 *(buf+linelen+1) = '\n';
331 write(fd, buf, linelen+2);
332
333 close(2); /* get rid of stderr */
334 /* now "all" we have to do is do a bidirectional copy of fd and stdio */
335 bidicopy(fd);
336 return 0;
337 }