git ssb

0+

cel / sslh



Tree: d0c0689e3ce9f57db360d5d1206090211ddd92c5

Files: d0c0689e3ce9f57db360d5d1206090211ddd92c5 / sslh.c

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

Built with git-ssb-web