git ssb

0+

cel / sslh



Tree: b965d735b8cede5dfc93947da5c59e562aceb559

Files: b965d735b8cede5dfc93947da5c59e562aceb559 / sslh.c

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

Built with git-ssb-web