git ssb

0+

cel / sslh



Tree: 0658982705270b6f79c5387db43e95e7c1f67465

Files: 0658982705270b6f79c5387db43e95e7c1f67465 / sslh.c

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

Built with git-ssb-web