git ssb

0+

cel / sslh



Tree: 44f02ddf39a9d4c338dae207cfd1fb6bc6bfd1e0

Files: 44f02ddf39a9d4c338dae207cfd1fb6bc6bfd1e0 / sslh.c

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

Built with git-ssb-web