]> pere.pagekite.me Git - exim4-smtorp.git/blob - tor-smtp/tor-smtp.c
Syslog problems.
[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 #include <syslog.h>
46 /* for BSD: htons is in there: */
47 #include <arpa/inet.h>
48 #include "atomicio.h"
49
50 //#define DEBUG(str,args...) fprintf(stderr, str, ##args);
51 #define DEBUG(str,args...) do {} while(0)
52
53 int read_bytes_timeout(int fd, char *buf, int bytes, int timeout)
54 {
55 # define DEBUG_RETURN(str,args...) do { DEBUG(str, ##args); return 0; } while (0)
56 struct pollfd pollfd = { .fd = fd, .events = POLLIN };
57 struct timeval tv_start, tv_now;
58 int got = 0, tmp;
59
60 gettimeofday(&tv_start, NULL);
61
62 while (got < bytes && timeout >= 0) {
63 if (poll(&pollfd, 1, timeout*1000) <= 0)
64 DEBUG_RETURN("poll failed: %d (got %d)\n", errno, got);
65 gettimeofday(&tv_now, NULL);
66 timeout -= tv_now.tv_sec - tv_start.tv_sec;
67 tv_start = tv_now;
68
69 tmp = read(fd, buf+got, bytes-got);
70 if (tmp <= 0)
71 DEBUG_RETURN("read failed: %d\n", errno);
72 got += tmp;
73 }
74 return bytes;
75 #undef DEBUG_RETURN
76 }
77
78 int connect_via_tor(const char *tor_server, const char *tor_port,
79 const char *remote_server, short remote_port)
80 {
81 int fd;
82 struct addrinfo *res = NULL;
83 char socks_init[] = { 5, 1, 0 };
84 char buf[255+7]; /* must be less than 255+7+1 bytes long */
85 int remote_server_len = strlen(remote_server);
86
87 if (remote_server_len > sizeof(buf)-7/*5 leading bytes and port */)
88 return -1;
89
90 fd = socket(PF_INET, SOCK_STREAM, 0);
91 if (fd == -1) {
92 DEBUG("failed to create socket\n");
93 return -1;
94 }
95
96 #define DEBUG_OUT(str, args...) do { DEBUG(str,##args); goto out_close; } while (0)
97
98 if (getaddrinfo(tor_server, tor_port, NULL, &res))
99 DEBUG_OUT("getaddrinfo\n");
100 if (!res)
101 DEBUG_OUT("getaddrinfo returned empty set\n");
102
103 if (connect(fd, res->ai_addr, res->ai_addrlen))
104 DEBUG_OUT("failed to connect\n");
105
106 /* now that we are connected, set up SOCKS */
107 if (write(fd, &socks_init, sizeof(socks_init)) != sizeof(socks_init))
108 goto out_close;
109 if (!read_bytes_timeout(fd, &buf[0], 2, 120))
110 goto out_close;
111 if (buf[0] != 5 || buf[1] != 0)
112 goto out_close;
113
114 buf[0] = 5;
115 buf[1] = 1;
116 buf[2] = 0;
117 buf[3] = 3;
118 buf[4] = remote_server_len;
119 strcpy(buf+5, remote_server);
120 *(short*) (buf+5+remote_server_len) = htons(remote_port);
121 if (write(fd, buf, 5+remote_server_len+2) != 5+remote_server_len+2)
122 goto out_close;
123 if (!read_bytes_timeout(fd, buf, 4, 120))
124 goto out_close;
125 if (buf[0] != 5 || buf[1] != 0)
126 goto out_close;
127 /* read rest of the response */
128 switch(buf[3]) {
129 case 1:
130 if (!read_bytes_timeout(fd, buf, 6, 120))
131 goto out_close;
132 break;
133 /* TODO handle other responses */
134 }
135 freeaddrinfo(res);
136
137 return fd;
138 out_close:
139 close(fd);
140 return -1;
141 }
142
143 /* read at most buflen characters into buffer,
144 * until linefeed is encountered.
145 * NB: if more characters are received they are discarded!
146 */
147 int readline(int fd, char *buf, int buflen, int timeout)
148 {
149 # define DEBUG_RETURN(str,args...) do { DEBUG(str, ##args); return 0; } while (0)
150 struct pollfd pollfd = { .fd = fd, .events = POLLIN };
151 struct timeval tv_start, tv_now;
152 int got = 0, tmp;
153 char *match;
154
155 gettimeofday(&tv_start, NULL);
156
157 while (got < buflen && timeout >= 0) {
158 if (poll(&pollfd, 1, timeout*1000) <= 0)
159 DEBUG_RETURN("poll failed: %d (got %d)\n", errno, got);
160 gettimeofday(&tv_now, NULL);
161 timeout -= tv_now.tv_sec - tv_start.tv_sec;
162 tv_start = tv_now;
163
164 tmp = read(fd, buf+got, buflen-got);
165 if (tmp <= 0)
166 DEBUG_RETURN("read failed: %d\n", errno);
167 match = memchr(buf+got, '\n', tmp);
168 if (match) {
169 if (*(match-1) == '\r')
170 match--;
171 *match = '\0';
172 return match-buf;
173 }
174 got += tmp;
175 }
176 return -1;
177 #undef DEBUG_RETURN
178 }
179
180 /*
181 * readwrite()
182 * Loop that polls on the network file descriptor and stdin.
183 */
184 void
185 bidicopy(int nfd)
186 {
187 struct pollfd pfd[2];
188 unsigned char buf[8192];
189 int n, wfd = fileno(stdin);
190 int lfd = fileno(stdout);
191 int plen;
192
193 plen = 1024;
194
195 /* Setup Network FD */
196 pfd[0].fd = nfd;
197 pfd[0].events = POLLIN;
198
199 /* Set up STDIN FD. */
200 pfd[1].fd = wfd;
201 pfd[1].events = POLLIN;
202
203 while (pfd[0].fd != -1) {
204 if ((n = poll(pfd, 2, -1)) < 0) {
205 close(nfd);
206 return;
207 }
208
209 if (n == 0)
210 return;
211
212 if (pfd[0].revents & POLLIN) {
213 if ((n = read(nfd, buf, plen)) < 0)
214 return;
215 else if (n == 0) {
216 goto shutdown_rd;
217 } else {
218 if (atomicio(vwrite, lfd, buf, n) != n)
219 return;
220 }
221 }
222 else if (pfd[0].revents & POLLHUP) {
223 shutdown_rd:
224 shutdown(nfd, SHUT_RD);
225 pfd[0].fd = -1;
226 pfd[0].events = 0;
227 }
228
229 if (1) {
230 if(pfd[1].revents & POLLIN) {
231 if ((n = read(wfd, buf, plen)) < 0)
232 return;
233 else if (n == 0) {
234 goto shutdown_wr;
235 } else {
236 if (atomicio(vwrite, nfd, buf, n) != n)
237 return;
238 }
239 }
240 else if (pfd[1].revents & POLLHUP) {
241 shutdown_wr:
242 shutdown(nfd, SHUT_WR);
243 pfd[1].fd = -1;
244 pfd[1].events = 0;
245 }
246 }
247 }
248 }
249
250 int main(int argc, char **argv)
251 {
252 char *tor_server = "localhost";
253 char *tor_port = "9050";
254 char *remote_server = NULL;
255 char *tmp;
256 char local_onion[255];
257 char buf[255], buf2[255];
258 int linelen;
259 int fd;
260
261 if (argc >= 2)
262 tor_server = argv[1];
263 if (argc >= 3)
264 tor_port = argv[2];
265 if (argc > 3 || (tor_server && (!strcasecmp(tor_server, "--help") || !strcasecmp(tor_server, "-h")))) {
266 fprintf(stderr, "tor-smtp [tor-server] [tor-port]\n");
267 fprintf(stderr, " (default localhost 9050)\n");
268 return 2;
269 }
270
271 openlog("tor-smtp", LOG_PID, LOG_MAIL);
272
273 printf("220 Welcome\r\n");
274 fflush(stdout);
275
276 command:
277 if ((linelen = readline(0, buf, sizeof(buf), 30)) < 0) {
278 printf("421 connection timed out\r\n");
279 fflush(stdout);
280 return 0;
281 }
282 if (linelen < 4) {
283 goto invalid;
284 }
285 if (!strncasecmp("QUIT", buf, 4)) {
286 printf("221 closing connection\r\n");
287 fflush(stdout);
288 return 0;
289 }
290 if (!strncasecmp("EHLO ", buf, 5) || !strncasecmp("HELO ", buf, 5))
291 goto helo_continue;
292 invalid:
293 printf("500 invalid command\r\n");
294 fflush(stdout);
295 goto command;
296
297 helo_continue:
298 tmp = strtok(buf+5, " ");
299 if (!tmp) {
300 printf("500 invalid EHLO/HELO: missing local name\n");
301 fflush(stdout);
302 goto command;
303 }
304 remote_server = strtok(NULL, " ");
305 if (!remote_server) {
306 printf("500 invalid EHLO/HELO: missing remote name\n");
307 goto command;
308 }
309 strncpy(local_onion, tmp, sizeof(local_onion));
310 fd = connect_via_tor(tor_server, tor_port, remote_server, 25);
311 if (fd < 0) {
312 printf("421 could not connect\r\n");
313 fflush(stdout);
314 syslog(LOG_WARNING, "tor connection to %s failed",
315 remote_server);
316 return 0;
317 }
318
319 linelen = readline(fd, buf2, sizeof(buf2), 120);
320 if (linelen < 0) {
321 printf("421 could not connect\r\n");
322 fflush(stdout);
323 syslog(LOG_WARNING, "no welcome message from %s", remote_server);
324 return 0;
325 }
326 if (strncmp("220 ", buf2, 4)) {
327 printf("421 server seems to not like us\r\n");
328 DEBUG("answer was: %s\n", buf2);
329 fflush(stdout);
330 return 0;
331 }
332
333 strncpy(buf+5, local_onion, sizeof(buf)-5);
334 linelen = strlen(buf);
335 *(buf+linelen) = '\r';
336 *(buf+linelen+1) = '\n';
337 write(fd, buf, linelen+2);
338
339 close(2); /* get rid of stderr */
340 /* now "all" we have to do is do a bidirectional copy of fd and stdio */
341 bidicopy(fd);
342 return 0;
343 }