git ssb

0+

cel / sslh



Tree: b49617923fadbe4429cbab8f4276a70ed0f73ab7

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
21Comments? questions? sslh@rutschle.net
22
23Compilation instructions:
24
25Solaris:
26 cc -o sslh sslh.c -lresolv -lsocket -lnsl
27
28LynxOS:
29 gcc -o tcproxy tcproxy.c -lnetinet
30
31HISTORY
32
33v1.2: 12MAY2008
34 Fixed compilation warning for AMD64 (Thx Daniel Lange)
35
36v1.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
43v1.0:
44 * Basic functionality: privilege dropping, target hostnames and ports
45 configurable.
46
47*/
48
49#define VERSION "1.2"
50
51#include <sys/types.h>
52#include <fcntl.h>
53#include <string.h>
54#include <unistd.h>
55#include <stdlib.h>
56#include <stdio.h>
57#include <signal.h>
58#include <sys/socket.h>
59#include <sys/wait.h>
60#include <netinet/in.h>
61#include <arpa/inet.h>
62#include <netdb.h>
63#include <pwd.h>
64
65#define CHECK_RES_DIE(res, str) \
66if (res == -1) { \
67 perror(str); \
68 exit(1); \
69}
70
71#define USAGE_STRING \
72"sslh v" VERSION "\n" \
73"usage:\n" \
74"\texport PIDFILE=/var/run/sslhc.pid\n" \
75"\tsslh [-t <timeout>] -u <username> -p <listenport> -s [sshhost:]port -l [sslhost:]port [-v]\n"
76
77int verbose = 0; /* That's really quite global */
78
79/* Starts a listening socket on specified port.
80 Returns file descriptor
81 */
82int 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 */
111int 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 */
131int 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 */
169char* 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;
179sock: socket address to which to copy the addr
180fullname: input string -- it gets clobbered
181serv: default service/port
182(defaults don't work yet)
183*/
184void 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 */
223int timeout = 2;
224int listen_port = 443;
225struct sockaddr addr_ssl, addr_ssh;
226
227
228/* Child process that finds out what to connect to and proxies
229 */
230void 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 */
280void child_handler(int signo)
281{
282 signal(SIGCHLD, &child_handler);
283 wait(NULL);
284}
285void 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 */
298void 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 */
316void 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
334void 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
348int 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