mirror of https://github.com/moparisthebest/sslh
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
184 lines
4.6 KiB
184 lines
4.6 KiB
/* |
|
sslh-fork: forking server |
|
|
|
# Copyright (C) 2007-2012 Yves Rutschle |
|
# |
|
# This program is free software; you can redistribute it |
|
# and/or modify it under the terms of the GNU General Public |
|
# License as published by the Free Software Foundation; either |
|
# version 2 of the License, or (at your option) any later |
|
# version. |
|
# |
|
# This program is distributed in the hope that it will be |
|
# useful, but WITHOUT ANY WARRANTY; without even the implied |
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
|
# PURPOSE. See the GNU General Public License for more |
|
# details. |
|
# |
|
# The full text for the General Public License is here: |
|
# http://www.gnu.org/licenses/gpl.html |
|
|
|
*/ |
|
|
|
#include "common.h" |
|
#include "probe.h" |
|
|
|
const char* server_type = "sslh-fork"; |
|
|
|
#define MAX(a, b) (((a) > (b)) ? (a) : (b)) |
|
|
|
/* shovels data from one fd to the other and vice-versa |
|
returns after one socket closed |
|
*/ |
|
int shovel(struct connection *cnx) |
|
{ |
|
fd_set fds; |
|
int res, i; |
|
int max_fd = MAX(cnx->q[0].fd, cnx->q[1].fd) + 1; |
|
|
|
FD_ZERO(&fds); |
|
while (1) { |
|
FD_SET(cnx->q[0].fd, &fds); |
|
FD_SET(cnx->q[1].fd, &fds); |
|
|
|
res = select( |
|
max_fd, |
|
&fds, |
|
NULL, |
|
NULL, |
|
NULL |
|
); |
|
CHECK_RES_DIE(res, "select"); |
|
|
|
for (i = 0; i < 2; i++) { |
|
if (FD_ISSET(cnx->q[i].fd, &fds)) { |
|
res = fd2fd(&cnx->q[1-i], &cnx->q[i]); |
|
if (!res) { |
|
if (verbose) |
|
fprintf(stderr, "%s %s", i ? "client" : "server", "socket closed\n"); |
|
return res; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* Child process that finds out what to connect to and proxies |
|
*/ |
|
void start_shoveler(int in_socket) |
|
{ |
|
fd_set fds; |
|
struct timeval tv; |
|
int res = PROBE_AGAIN; |
|
int out_socket; |
|
struct connection cnx; |
|
|
|
init_cnx(&cnx); |
|
cnx.q[0].fd = in_socket; |
|
|
|
FD_ZERO(&fds); |
|
FD_SET(in_socket, &fds); |
|
memset(&tv, 0, sizeof(tv)); |
|
tv.tv_sec = probing_timeout; |
|
|
|
while (res == PROBE_AGAIN) { |
|
/* POSIX does not guarantee that tv will be updated, but the client can |
|
* only postpone the inevitable for so long */ |
|
res = select(in_socket + 1, &fds, NULL, NULL, &tv); |
|
if (res == -1) |
|
perror("select"); |
|
|
|
if (FD_ISSET(in_socket, &fds)) { |
|
/* Received data: figure out what protocol it is */ |
|
res = probe_client_protocol(&cnx); |
|
} else { |
|
/* Timed out: it's necessarily SSH */ |
|
cnx.proto = timeout_protocol(); |
|
break; |
|
} |
|
} |
|
|
|
if (cnx.proto->service && |
|
check_access_rights(in_socket, cnx.proto->service)) { |
|
exit(0); |
|
} |
|
|
|
/* Connect the target socket */ |
|
out_socket = connect_addr(&cnx, in_socket); |
|
CHECK_RES_DIE(out_socket, "connect"); |
|
|
|
cnx.q[1].fd = out_socket; |
|
|
|
log_connection(&cnx); |
|
|
|
flush_deferred(&cnx.q[1]); |
|
|
|
shovel(&cnx); |
|
|
|
close(in_socket); |
|
close(out_socket); |
|
|
|
if (verbose) |
|
fprintf(stderr, "connection closed down\n"); |
|
|
|
exit(0); |
|
} |
|
|
|
static int *listener_pid; |
|
static int listener_pid_number = 0; |
|
|
|
void stop_listeners(int sig) |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < listener_pid_number; i++) { |
|
kill(listener_pid[i], sig); |
|
} |
|
} |
|
|
|
void main_loop(int listen_sockets[], int num_addr_listen) |
|
{ |
|
int in_socket, i, res; |
|
struct sigaction action; |
|
|
|
listener_pid_number = num_addr_listen; |
|
listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0])); |
|
|
|
/* Start one process for each listening address */ |
|
for (i = 0; i < num_addr_listen; i++) { |
|
if (!(listener_pid[i] = fork())) { |
|
|
|
/* Listening process just accepts a connection, forks, and goes |
|
* back to listening */ |
|
while (1) |
|
{ |
|
in_socket = accept(listen_sockets[i], 0, 0); |
|
if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket); |
|
|
|
if (!fork()) |
|
{ |
|
for (i = 0; i < num_addr_listen; ++i) |
|
close(listen_sockets[i]); |
|
start_shoveler(in_socket); |
|
exit(0); |
|
} |
|
close(in_socket); |
|
} |
|
} |
|
} |
|
|
|
/* Set SIGTERM to "stop_listeners" which further kills all listener |
|
* processes. Note this won't kill processes that listeners forked, which |
|
* means active connections remain active. */ |
|
memset(&action, 0, sizeof(action)); |
|
action.sa_handler = stop_listeners; |
|
res = sigaction(SIGTERM, &action, NULL); |
|
CHECK_RES_DIE(res, "sigaction"); |
|
|
|
wait(NULL); |
|
} |
|
|
|
/* The actual main is in common.c: it's the same for both version of |
|
* the server |
|
*/ |
|
|
|
|