2013-07-10 17:12:42 -04:00
|
|
|
/*
|
2013-07-10 17:15:38 -04:00
|
|
|
sslh-fork: forking server
|
2013-07-10 17:12:42 -04:00
|
|
|
|
2013-07-10 17:15:38 -04:00
|
|
|
# Copyright (C) 2007-2012 Yves Rutschle
|
2013-07-10 17:12:42 -04:00
|
|
|
#
|
|
|
|
# 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"
|
2013-07-10 17:15:38 -04:00
|
|
|
#include "probe.h"
|
2013-07-10 17:12:42 -04:00
|
|
|
|
|
|
|
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;
|
2013-09-23 18:30:34 -04:00
|
|
|
int res = PROBE_AGAIN;
|
2013-07-10 17:12:42 -04:00
|
|
|
int out_socket;
|
|
|
|
struct connection cnx;
|
|
|
|
|
|
|
|
init_cnx(&cnx);
|
2013-09-23 18:30:34 -04:00
|
|
|
cnx.q[0].fd = in_socket;
|
2013-07-10 17:12:42 -04:00
|
|
|
|
|
|
|
FD_ZERO(&fds);
|
|
|
|
FD_SET(in_socket, &fds);
|
|
|
|
memset(&tv, 0, sizeof(tv));
|
|
|
|
tv.tv_sec = probing_timeout;
|
|
|
|
|
2013-09-23 18:30:34 -04:00
|
|
|
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;
|
|
|
|
}
|
2013-07-10 17:12:42 -04:00
|
|
|
}
|
|
|
|
|
2013-09-23 18:30:34 -04:00
|
|
|
if (cnx.proto->service &&
|
|
|
|
check_access_rights(in_socket, cnx.proto->service)) {
|
2013-07-10 17:12:42 -04:00
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Connect the target socket */
|
2013-09-23 18:30:36 -04:00
|
|
|
out_socket = connect_addr(&cnx, in_socket);
|
2013-07-10 17:14:15 -04:00
|
|
|
CHECK_RES_DIE(out_socket, "connect");
|
2013-07-10 17:12:42 -04:00
|
|
|
|
|
|
|
cnx.q[1].fd = out_socket;
|
|
|
|
|
|
|
|
log_connection(&cnx);
|
|
|
|
|
2013-09-23 18:30:38 -04:00
|
|
|
flush_deferred(&cnx.q[1]);
|
2013-07-10 17:12:42 -04:00
|
|
|
|
|
|
|
shovel(&cnx);
|
|
|
|
|
|
|
|
close(in_socket);
|
|
|
|
close(out_socket);
|
|
|
|
|
|
|
|
if (verbose)
|
|
|
|
fprintf(stderr, "connection closed down\n");
|
|
|
|
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
2013-07-10 17:14:15 -04:00
|
|
|
static int *listener_pid;
|
|
|
|
static int listener_pid_number = 0;
|
|
|
|
|
|
|
|
void stop_listeners(int sig)
|
2013-07-10 17:12:42 -04:00
|
|
|
{
|
2013-07-10 17:14:15 -04:00
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < listener_pid_number; i++) {
|
|
|
|
kill(listener_pid[i], sig);
|
|
|
|
}
|
|
|
|
}
|
2013-07-10 17:13:32 -04:00
|
|
|
|
2013-07-10 17:14:15 -04:00
|
|
|
void main_loop(int listen_sockets[], int num_addr_listen)
|
|
|
|
{
|
|
|
|
int in_socket, i, res;
|
|
|
|
struct sigaction action;
|
|
|
|
|
2013-07-10 17:14:48 -04:00
|
|
|
listener_pid_number = num_addr_listen;
|
2013-07-10 17:14:15 -04:00
|
|
|
listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0]));
|
|
|
|
|
|
|
|
/* Start one process for each listening address */
|
2013-07-10 17:13:32 -04:00
|
|
|
for (i = 0; i < num_addr_listen; i++) {
|
2013-07-10 17:14:15 -04:00
|
|
|
if (!(listener_pid[i] = fork())) {
|
|
|
|
|
|
|
|
/* Listening process just accepts a connection, forks, and goes
|
|
|
|
* back to listening */
|
2013-07-10 17:13:32 -04:00
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
in_socket = accept(listen_sockets[i], 0, 0);
|
|
|
|
if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
|
|
|
|
|
|
|
|
if (!fork())
|
|
|
|
{
|
|
|
|
close(listen_sockets[i]);
|
|
|
|
start_shoveler(in_socket);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
close(in_socket);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-07-10 17:14:15 -04:00
|
|
|
|
|
|
|
/* 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);
|
2013-07-10 17:12:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* The actual main is in common.c: it's the same for both version of
|
|
|
|
* the server
|
|
|
|
*/
|
|
|
|
|