Files: b49617923fadbe4429cbab8f4276a70ed0f73ab7 / sslh.c
10115 bytesRaw
1 | /* |
2 | Reimplementation of sslh in C |
3 | |
4 | # Copyright (C) 2007-2008 Yves Rutschle |
5 | # |
6 | # This program is free software; you can redistribute it |
7 | # and/or modify it under the terms of the GNU General Public |
8 | # License as published by the Free Software Foundation; either |
9 | # version 2 of the License, or (at your option) any later |
10 | # version. |
11 | # |
12 | # This program is distributed in the hope that it will be |
13 | # useful, but WITHOUT ANY WARRANTY; without even the implied |
14 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
15 | # PURPOSE. See the GNU General Public License for more |
16 | # details. |
17 | # |
18 | # The full text for the General Public License is here: |
19 | # http://www.gnu.org/licenses/gpl.html |
20 | |
21 | Comments? questions? sslh@rutschle.net |
22 | |
23 | Compilation instructions: |
24 | |
25 | Solaris: |
26 | cc -o sslh sslh.c -lresolv -lsocket -lnsl |
27 | |
28 | LynxOS: |
29 | gcc -o tcproxy tcproxy.c -lnetinet |
30 | |
31 | HISTORY |
32 | |
33 | v1.2: 12MAY2008 |
34 | Fixed compilation warning for AMD64 (Thx Daniel Lange) |
35 | |
36 | v1.1: 21MAY2007 |
37 | Making sslhc more like a real daemon: |
38 | * If $PIDFILE is defined, write first PID to it upon startup |
39 | * Fork at startup (detach from terminal) |
40 | (thanks to http://www.enderunix.org/docs/eng/daemon.php -- good checklist) |
41 | * Less memory usage (?) |
42 | |
43 | v1.0: |
44 | * Basic functionality: privilege dropping, target hostnames and ports |
45 | configurable. |
46 | |
47 | */ |
48 | |
49 | |
50 | |
51 | |
52 | |
53 | |
54 | |
55 | |
56 | |
57 | |
58 | |
59 | |
60 | |
61 | |
62 | |
63 | |
64 | |
65 | |
66 | (res == -1) { \ |
67 | perror(str); \ |
68 | exit(1); \ |
69 | } |
70 | |
71 | |
72 | VERSION \ |
73 | \ |
74 | \ |
75 | |
76 | |
77 | int verbose = 0; /* That's really quite global */ |
78 | |
79 | /* Starts a listening socket on specified port. |
80 | Returns file descriptor |
81 | */ |
82 | int start_listen_socket(int port) |
83 | { |
84 | struct sockaddr_in saddr; |
85 | int sockfd, res, reuse; |
86 | |
87 | sockfd = socket(AF_INET, SOCK_STREAM, 0); |
88 | CHECK_RES_DIE(sockfd, "socket"); |
89 | |
90 | reuse = 1; |
91 | res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); |
92 | CHECK_RES_DIE(res, "setsockopt"); |
93 | |
94 | memset(&saddr, 0, sizeof(saddr)); |
95 | saddr.sin_port = htons(port); |
96 | |
97 | res = bind (sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); |
98 | CHECK_RES_DIE(res, "bind"); |
99 | |
100 | res = listen (sockfd, 5); |
101 | CHECK_RES_DIE(res, "listen"); |
102 | |
103 | return sockfd; |
104 | } |
105 | |
106 | |
107 | /* |
108 | * moves data from one fd to other |
109 | * returns 0 if incoming socket closed, size moved otherwise |
110 | */ |
111 | int fd2fd(int target, int from) |
112 | { |
113 | char buffer[BUFSIZ]; |
114 | int size; |
115 | |
116 | size = read(from, buffer, sizeof(buffer)); |
117 | CHECK_RES_DIE(size, "read"); |
118 | |
119 | if (size == 0) |
120 | return 0; |
121 | |
122 | size = write(target, buffer, size); |
123 | CHECK_RES_DIE(size, "write"); |
124 | |
125 | return size; |
126 | } |
127 | |
128 | /* shovels data from one fd to the other and vice-versa |
129 | returns after one socket closed |
130 | */ |
131 | int shovel(int fd1, int fd2) |
132 | { |
133 | fd_set fds; |
134 | int res; |
135 | |
136 | FD_ZERO(&fds); |
137 | while (1) { |
138 | FD_SET(fd1, &fds); |
139 | FD_SET(fd2, &fds); |
140 | |
141 | res = select( |
142 | (fd1 > fd2 ? fd1 : fd2 ) + 1, |
143 | &fds, |
144 | NULL, |
145 | NULL, |
146 | NULL |
147 | ); |
148 | CHECK_RES_DIE(res, "select"); |
149 | |
150 | if (FD_ISSET(fd1, &fds)) { |
151 | res = fd2fd(fd2, fd1); |
152 | if (!res) { |
153 | if (verbose) fprintf(stderr, "client socket closed\n"); |
154 | return res; |
155 | } |
156 | } |
157 | |
158 | if (FD_ISSET(fd2, &fds)) { |
159 | res = fd2fd(fd1, fd2); |
160 | if (!res) { |
161 | if (verbose) fprintf(stderr, "server socket closed\n"); |
162 | return res; |
163 | } |
164 | } |
165 | } |
166 | } |
167 | |
168 | /* returns a string that prints the IP and port of the sockaddr */ |
169 | char* sprintaddr(char* buf, size_t size, struct sockaddr* s) |
170 | { |
171 | char addr_str[1024]; |
172 | |
173 | inet_ntop(AF_INET, &((struct sockaddr_in*)s)->sin_addr, addr_str, sizeof(addr_str)); |
174 | snprintf(buf, size, "%s:%d", addr_str, ntohs(((struct sockaddr_in*)s)->sin_port)); |
175 | return buf; |
176 | } |
177 | |
178 | /* turns a "hostname:port" string into a struct sockaddr; |
179 | sock: socket address to which to copy the addr |
180 | fullname: input string -- it gets clobbered |
181 | serv: default service/port |
182 | (defaults don't work yet) |
183 | */ |
184 | void resolve_name(struct sockaddr *sock, char* fullname, int port) { |
185 | struct addrinfo *addr, hint; |
186 | char *serv, *host; |
187 | int res; |
188 | |
189 | char *sep = strchr(fullname, ':'); |
190 | |
191 | if (!sep) /* No separator: parameter is just a port */ |
192 | { |
193 | serv = fullname; |
194 | fprintf(stderr, "names must be fully specified as hostname:port for the moment\n"); |
195 | exit(1); |
196 | } |
197 | else { |
198 | host = fullname; |
199 | serv = sep+1; |
200 | *sep = 0; |
201 | } |
202 | |
203 | memset(&hint, 0, sizeof(hint)); |
204 | hint.ai_family = PF_INET; |
205 | hint.ai_socktype = SOCK_STREAM; |
206 | |
207 | res = getaddrinfo(host, serv, &hint, &addr); |
208 | if (res) { |
209 | fprintf(stderr, "%s\n", gai_strerror(res)); |
210 | if (res == EAI_SERVICE) |
211 | fprintf(stderr, "(Check you have specified all ports)\n"); |
212 | exit(1); |
213 | } |
214 | |
215 | memcpy(sock, addr->ai_addr, sizeof(*sock)); |
216 | } |
217 | |
218 | /* |
219 | * Settings that depend on the command line. That's less global than verbose * :-) |
220 | * They're set in main(), but also used in start_shoveler(), and it'd be heavy-handed |
221 | * to pass it all as parameters |
222 | */ |
223 | int timeout = 2; |
224 | int listen_port = 443; |
225 | struct sockaddr addr_ssl, addr_ssh; |
226 | |
227 | |
228 | /* Child process that finds out what to connect to and proxies |
229 | */ |
230 | void start_shoveler(int in_socket) |
231 | { |
232 | fd_set fds; |
233 | struct timeval tv; |
234 | struct sockaddr *saddr; |
235 | int res; |
236 | int out_socket; |
237 | |
238 | FD_ZERO(&fds); |
239 | FD_SET(in_socket, &fds); |
240 | memset(&tv, 0, sizeof(tv)); |
241 | tv.tv_sec = timeout; |
242 | res = select(in_socket + 1, &fds, NULL, NULL, &tv); |
243 | if (res == -1) |
244 | perror("select"); |
245 | |
246 | /* Pick the target address depending on whether we timed out or not */ |
247 | if (FD_ISSET(in_socket, &fds)) { |
248 | /* The client wrote something to the socket: it's an SSL connection */ |
249 | saddr = &addr_ssl; |
250 | if (verbose) |
251 | fprintf(stderr, "Forwarding to SSL\n"); |
252 | } else { |
253 | /* The client hasn't written anything and we timed out: connect to SSH */ |
254 | saddr = &addr_ssh; |
255 | if (verbose) |
256 | fprintf(stderr, "Forwarding to SSH\n"); |
257 | } |
258 | |
259 | /* Connect the target socket */ |
260 | out_socket = socket(AF_INET, SOCK_STREAM, 0); |
261 | res = connect(out_socket, saddr, sizeof(addr_ssl)); |
262 | CHECK_RES_DIE(res, "connect"); |
263 | if (verbose) |
264 | fprintf(stderr, "connected to something\n"); |
265 | |
266 | shovel(in_socket, out_socket); |
267 | |
268 | close(in_socket); |
269 | close(out_socket); |
270 | |
271 | if (verbose) |
272 | fprintf(stderr, "connection closed down\n"); |
273 | |
274 | exit(0); |
275 | } |
276 | |
277 | /* SIGCHLD handling: |
278 | * we need to reap our children |
279 | */ |
280 | void child_handler(int signo) |
281 | { |
282 | signal(SIGCHLD, &child_handler); |
283 | wait(NULL); |
284 | } |
285 | void setup_signals(void) |
286 | { |
287 | void* res; |
288 | |
289 | res = signal(SIGCHLD, &child_handler); |
290 | if (res == SIG_ERR) { |
291 | perror("signal"); |
292 | exit(1); |
293 | } |
294 | } |
295 | |
296 | |
297 | /* We don't want to run as root -- drop priviledges if required */ |
298 | void drop_privileges(char* user_name) |
299 | { |
300 | int res; |
301 | struct passwd *pw = getpwnam(user_name); |
302 | if (!pw) { |
303 | fprintf(stderr, "%s: not found\n", user_name); |
304 | exit(1); |
305 | } |
306 | if (verbose) |
307 | fprintf(stderr, "turning into %s\n", user_name); |
308 | |
309 | res = setgid(pw->pw_gid); |
310 | CHECK_RES_DIE(res, "setgid"); |
311 | setuid(pw->pw_uid); |
312 | CHECK_RES_DIE(res, "setuid"); |
313 | } |
314 | |
315 | /* Writes my PID if $PIDFILE is defined */ |
316 | void write_pid_file(void) |
317 | { |
318 | char *pidfile = getenv("PIDFILE"); |
319 | FILE *f; |
320 | |
321 | if (!pidfile) |
322 | return; |
323 | |
324 | f = fopen(pidfile, "w"); |
325 | if (!f) { |
326 | perror(pidfile); |
327 | exit(1); |
328 | } |
329 | |
330 | fprintf(f, "%d\n", getpid()); |
331 | fclose(f); |
332 | } |
333 | |
334 | void printsettings(void) |
335 | { |
336 | char buf[64]; |
337 | |
338 | fprintf( |
339 | stderr, |
340 | "SSL addr: %s (after timeout %ds)\n", |
341 | sprintaddr(buf, sizeof(buf), &addr_ssl), |
342 | timeout |
343 | ); |
344 | fprintf(stderr, "SSH addr: %s\n", sprintaddr(buf, sizeof(buf), &addr_ssh)); |
345 | fprintf(stderr, "listening on port %d\n", listen_port); |
346 | } |
347 | |
348 | int main(int argc, char *argv[]) |
349 | { |
350 | |
351 | extern char *optarg; |
352 | extern int optind; |
353 | int c, res; |
354 | |
355 | int in_socket, listen_socket; |
356 | |
357 | /* Init defaults */ |
358 | char *user_name = "nobody"; |
359 | char ssl_str[] = "localhost:443"; |
360 | char ssh_str[] = "localhost:22"; |
361 | |
362 | resolve_name(&addr_ssl, ssl_str, 443); |
363 | resolve_name(&addr_ssh, ssh_str, 22); |
364 | |
365 | while ((c = getopt(argc, argv, "t:l:s:p:vu:")) != EOF) { |
366 | switch (c) { |
367 | |
368 | case 't': |
369 | timeout = atoi(optarg); |
370 | break; |
371 | |
372 | case 'p': |
373 | listen_port = atoi(optarg); |
374 | break; |
375 | |
376 | case 'l': |
377 | resolve_name(&addr_ssl, optarg, 443); |
378 | break; |
379 | |
380 | case 's': |
381 | resolve_name(&addr_ssh, optarg, 22); |
382 | break; |
383 | |
384 | case 'v': |
385 | verbose += 1; |
386 | break; |
387 | |
388 | case 'u': |
389 | user_name = optarg; |
390 | break; |
391 | |
392 | default: |
393 | fprintf(stderr, USAGE_STRING); |
394 | exit(2); |
395 | } |
396 | } |
397 | |
398 | if (verbose) |
399 | printsettings(); |
400 | |
401 | setup_signals(); |
402 | |
403 | listen_socket = start_listen_socket(listen_port); |
404 | |
405 | if (fork() > 0) exit(0); /* Detach */ |
406 | |
407 | write_pid_file(); |
408 | |
409 | drop_privileges(user_name); |
410 | |
411 | /* New session -- become group leader */ |
412 | res = setsid(); |
413 | CHECK_RES_DIE(res, "setsid: already process leader"); |
414 | |
415 | /* Main server loop: accept connections, find what they are, fork shovelers */ |
416 | while (1) |
417 | { |
418 | in_socket = accept(listen_socket, 0, 0); |
419 | if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket); |
420 | |
421 | if (!fork()) |
422 | { |
423 | start_shoveler(in_socket); |
424 | exit(0); |
425 | } |
426 | close(in_socket); |
427 | } |
428 | |
429 | return 0; |
430 | } |
431 | |
432 | |
433 |
Built with git-ssb-web