Files
udpxd/net.c

441 lines
10 KiB
C
Raw Normal View History

2015-04-21 20:09:12 +02:00
/*
This file is part of udpxd.
2016-09-22 21:37:30 +02:00
Copyright (C) 2015-2016 T.v.Dein.
2015-04-21 20:09:12 +02: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 3 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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact me by mail: <tom AT vondein DOT org>.
*/
#include "net.h"
#include "client.h"
2015-04-24 22:03:50 +02:00
#include "host.h"
#include "log.h"
2015-04-24 22:03:50 +02:00
2015-04-21 20:09:12 +02:00
/* called each time when the loop restarts to feed select() correctly */
int fill_set(fd_set *fds) {
int max = 0;
client_t *current = NULL;
client_iter(clients, current) {
if (current->socket < (int)FD_SETSIZE) {
if (current->socket > max)
2016-09-22 21:37:30 +02:00
max = current->socket;
2015-04-21 20:09:12 +02:00
FD_SET(current->socket, fds);
}
else {
fprintf(stderr, "skipped client, socket too large!\n");
}
}
return max;
}
/* return file handle ready to read */
int get_sender(fd_set *fds) {
int i = 0;
while(!FD_ISSET(i, fds))
i++;
return i;
}
2015-04-24 22:03:50 +02:00
2015-04-21 20:09:12 +02:00
/* bind to a socket, either for listen() or for outgoing src ip binding */
2015-04-24 22:03:50 +02:00
int bindsocket( host_t *sock_h) {
2015-04-21 20:09:12 +02:00
int fd;
2015-04-24 22:03:50 +02:00
int err = 0;
if(sock_h->is_v6) {
fd = socket( PF_INET6, SOCK_DGRAM, IPPROTO_UDP );
}
else {
fd = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP );
}
2015-04-21 20:09:12 +02:00
2015-04-24 22:03:50 +02:00
if( -1 == bind( fd, (struct sockaddr*)sock_h->sock, sock_h->size ) ) {
err = 1;
}
2015-04-21 20:09:12 +02:00
2015-04-24 22:03:50 +02:00
if(err) {
fprintf( stderr, "Cannot bind address ([%s]:%d)\n", sock_h->ip, sock_h->port );
perror(NULL);
return -1;
2015-04-21 20:09:12 +02:00
}
return fd;
}
/*
returns:
-1: error in any case
0: parent not forked (fork disabled)
1: parent after successful fork
2: child after successful fork
*/
int daemonize(char *pidfile) {
if(FORKED) {
// fork
pid_t pid, sid;
FILE *fd;
pid = fork();
if (pid < 0) {
perror("fork error");
return 1;
}
if (pid > 0) {
/* leave parent */
if((fd = fopen(pidfile, "w")) == NULL) {
2016-09-22 21:37:30 +02:00
perror("failed to write pidfile");
return -1;
}
else {
2016-09-22 21:37:30 +02:00
fprintf(fd, "%d\n", pid);
fclose(fd);
}
return 1;
}
/* child */
sid = setsid();
if (sid < 0) {
perror("set sid error");
return 1;
}
umask(0);
openlog("udpxd", LOG_NOWAIT|LOG_PID, LOG_USER);
return 2;
}
return 0;
}
int drop_privileges(char *user, char *chrootdir) {
struct passwd *pw = getpwnam(user);
uid_t me = getuid();
2015-04-27 20:13:38 +02:00
if(!FORKED)
return 0;
if ((chdir("/")) < 0) {
perror("failed to chdir to /");
return 1;
}
if(me == 0) {
/* drop privileges */
if(chroot(chrootdir) != 0) {
perror("failed to chroot");
return 1;
}
if(pw == NULL) {
perror("user not found");
return 1;
}
if(setegid(pw->pw_gid) != 0) {
perror("could not set egid");
return 1;
}
if(setgid(pw->pw_gid) != 0) {
perror("could not set gid");
return 1;
}
if(setuid(pw->pw_uid) != 0) {
perror("could not set uid");
return 1;
}
if(seteuid(pw->pw_uid) != 0) {
perror("could not set euid");
return 1;
}
if (setuid(0) != -1) {
fprintf(stderr, "error, managed to regain root privileges after dropping them!\n");
return 1;
}
}
return 0;
}
int start_listener (char *inip, char *inpt, char *srcip, char *dstip,
2016-09-22 21:37:30 +02:00
char *dstpt, char *pidfile, char *chrootdir, char *user) {
host_t *listen_h, *dst_h, *bind_h;
int dm = daemonize(pidfile);
switch(dm) {
case -1:
return 1; /* parent, fork error */
break;
case 0:
break; /* parent, not forking */
case 1:
return 0; /* parent, fork ok, leave */
break;
case 2:
break; /* child, fork ok, continue */
}
listen_h = get_host(inip, atoi(inpt), NULL, NULL);
dst_h = get_host(dstip, atoi(dstpt), NULL, NULL);
bind_h = NULL;
2015-04-24 22:03:50 +02:00
if(srcip != NULL) {
bind_h = get_host(srcip, 0, NULL, NULL);
}
else {
if(dst_h->is_v6)
bind_h = get_host("::0", 0, NULL, NULL);
else
bind_h = get_host("0.0.0.0", 0, NULL, NULL);
}
int listen = bindsocket(listen_h);
if(listen == -1)
return 1;
if(VERBOSE) {
verbose("Listening on %s:%s, forwarding to %s:%s",
2016-09-22 21:37:30 +02:00
listen_h->ip, inpt, dst_h->ip, dstpt);
2015-04-24 22:03:50 +02:00
if(srcip != NULL)
verbose(", binding to %s\n", bind_h->ip);
2015-04-24 22:03:50 +02:00
else
verbose("\n");
2015-04-24 22:03:50 +02:00
}
if(drop_privileges(user, chrootdir) != 0) {
host_clean(bind_h);
host_clean(listen_h);
host_clean(dst_h);
return 1;
}
2015-04-24 22:03:50 +02:00
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
2015-04-24 22:03:50 +02:00
main_loop(listen, listen_h, bind_h, dst_h);
2015-04-25 20:14:24 +02:00
host_clean(bind_h);
host_clean(listen_h);
host_clean(dst_h);
closelog();
2015-04-24 22:03:50 +02:00
return 0;
}
2015-04-25 20:14:24 +02:00
/* handle new or known incoming requests */
2015-04-24 22:03:50 +02:00
void handle_inside(int inside, host_t *listen_h, host_t *bind_h, host_t *dst_h) {
2015-04-21 20:09:12 +02:00
int len;
unsigned char buffer[MAX_BUFFER_SIZE];
2015-04-24 22:03:50 +02:00
void *src;
2015-04-21 20:09:12 +02:00
client_t *client;
2015-04-24 22:03:50 +02:00
host_t *src_h;
2015-04-21 20:09:12 +02:00
int output;
2015-04-24 22:03:50 +02:00
size_t size = listen_h->size;
src = malloc(size);
2015-04-21 20:09:12 +02:00
2015-04-24 22:03:50 +02:00
len = recvfrom( inside, buffer, sizeof( buffer ), 0,
2016-09-22 21:37:30 +02:00
(struct sockaddr*)src, (socklen_t *)&size );
2015-04-24 22:03:50 +02:00
if(listen_h->is_v6)
src_h = get_host(NULL, 0, NULL, (struct sockaddr_in6 *)src);
else
src_h = get_host(NULL, 0, (struct sockaddr_in *)src, NULL);
2015-04-21 20:09:12 +02:00
2015-04-25 20:14:24 +02:00
free(src);
2015-04-21 20:09:12 +02:00
if(len > 0) {
/* do we know it ? */
2015-04-24 22:03:50 +02:00
client = client_find_src(src_h);
2015-04-21 20:09:12 +02:00
if(client != NULL) {
/* yes, we know it, send req out via existing bind socket */
verbose("Client %s:%d is known, forwarding %d bytes to %s:%d ",
2016-09-22 21:37:30 +02:00
src_h->ip, src_h->port, len, dst_h->ip, dst_h->port);
verb_prbind(bind_h);
2015-04-24 22:03:50 +02:00
if(sendto(client->socket, buffer, len, 0, (struct sockaddr*)dst_h->sock, dst_h->size) < 0) {
2016-09-22 21:37:30 +02:00
fprintf(stderr, "unable to forward to %s:%d\n", dst_h->ip, dst_h->port);
perror(NULL);
2015-04-21 20:09:12 +02:00
}
else {
2016-09-22 21:37:30 +02:00
client_seen(client);
2015-04-21 20:09:12 +02:00
}
}
else {
/* unknown client, open new out socket */
2016-09-22 21:37:30 +02:00
verbose("Client %s:%d is unknown, forwarding %d bytes to %s:%d ",
src_h->ip, src_h->port, len, dst_h->ip, dst_h->port);
verb_prbind(bind_h);
2015-04-21 20:09:12 +02:00
2015-04-24 22:03:50 +02:00
output = bindsocket(bind_h);
2015-04-21 20:09:12 +02:00
/* send req out */
2015-04-24 22:03:50 +02:00
if(sendto(output, buffer, len, 0, (struct sockaddr*)dst_h->sock, dst_h->size) < 0) {
2016-09-22 21:37:30 +02:00
fprintf(stderr, "unable to forward to %s:%d\n", dst_h->ip, dst_h->port);
perror(NULL);
2015-04-21 20:09:12 +02:00
}
else {
2016-09-22 21:37:30 +02:00
size = listen_h->size;
host_t *ret_h;
if(listen_h->is_v6) {
struct sockaddr_in6 *ret = malloc(size);
getsockname(output, (struct sockaddr*)ret, (socklen_t *)&size);
ret_h = get_host(NULL, 0, NULL, ret);
free(ret);
client = client_new(output, src_h, ret_h);
}
else {
struct sockaddr_in *ret = malloc(size);
getsockname(output, (struct sockaddr*)ret, (socklen_t *)&size);
ret_h = get_host(NULL, 0, ret, NULL);
free(ret);
client = client_new(output, src_h, ret_h);
}
client_add(client);
2015-04-21 20:09:12 +02:00
}
}
}
}
/* handle answer from the outside */
2015-04-24 22:03:50 +02:00
void handle_outside(int inside, int outside, host_t *outside_h) {
2015-04-21 20:09:12 +02:00
int len;
unsigned char buffer[MAX_BUFFER_SIZE];
2015-04-24 22:03:50 +02:00
void *src;
2015-04-21 20:09:12 +02:00
client_t *client;
2015-04-24 22:03:50 +02:00
size_t size = outside_h->size;
src = malloc(size);
2015-04-21 20:09:12 +02:00
len = recvfrom( outside, buffer, sizeof( buffer ), 0, (struct sockaddr*)src, (socklen_t *)&size );
2015-04-25 20:14:24 +02:00
free(src);
2015-04-21 20:09:12 +02:00
if(len > 0) {
/* do we know it? */
client = client_find_fd(outside);
if(client != NULL) {
/* yes, we know it */
2015-04-25 20:14:24 +02:00
/* FIXME: check src vs. client->src ? */
if(sendto(inside, buffer, len, 0,
2016-09-22 21:37:30 +02:00
(struct sockaddr*)client->src->sock, client->src->size) < 0) {
perror("unable to send back to client"); /* FIXME: add src+port */
client_close(client);
2015-04-21 20:09:12 +02:00
}
}
2015-04-25 20:14:24 +02:00
else {
fprintf(stderr, "weird, no matching client found!\n");
}
}
else {
fprintf(stderr, "weird, recvfrom returned 0 bytes!\n");
2015-04-21 20:09:12 +02:00
}
}
/* stores system specific information, used by longjmp(), see below */
2015-04-25 20:14:24 +02:00
jmp_buf JumpBuffer;
2015-04-24 22:03:50 +02:00
/* runs forever, handles incoming requests on the inside and answers on the outside */
int main_loop(int listensocket, host_t *listen_h, host_t *bind_h, host_t *dst_h) {
2015-04-25 20:14:24 +02:00
int max, sender;
fd_set fds;
2015-04-24 22:03:50 +02:00
/* we want to properly tear down running sessions when interrupted,
int_handler() will be called on INT or TERM signals */
2015-04-25 20:14:24 +02:00
signal(SIGINT, int_handler);
signal(SIGTERM, int_handler);
2015-04-24 22:03:50 +02:00
2015-04-25 20:14:24 +02:00
for(;;) {
/*
Normally returns 0, that is, if it's the first instruction after
entering the loop. However, it will return 1, when called from
longjmp(), which will be called by int_handler() if a SIGINT- or
TERM arrives. In that case we leave the loop, tear down
everything and exit.
*/
2015-04-25 20:14:24 +02:00
if (setjmp(JumpBuffer) == 1) {
break;
}
2015-04-24 22:03:50 +02:00
2015-04-25 20:14:24 +02:00
FD_ZERO(&fds);
max = fill_set(&fds);
2015-04-24 22:03:50 +02:00
2015-04-25 20:14:24 +02:00
FD_SET(listensocket, &fds);
if (listensocket > max)
max = listensocket;
2015-04-24 22:03:50 +02:00
2015-04-25 20:14:24 +02:00
select(max + 1, &fds, NULL, NULL, NULL);
if (FD_ISSET(listensocket, &fds)) {
2016-09-22 21:37:30 +02:00
/* incoming client on the inside, get src, bind output fd, add
to list if known, otherwise just handle it */
2015-04-25 20:14:24 +02:00
handle_inside(listensocket, listen_h, bind_h, dst_h);
}
else {
/* remote answer came in on an output fd, proxy back to the inside */
sender = get_sender(&fds);
handle_outside(listensocket, sender, dst_h);
2015-04-24 22:03:50 +02:00
}
2015-04-25 20:14:24 +02:00
/* close old outputs, if any */
client_clean(0);
}
/* we came here via signal handler, clean up */
2015-04-25 20:14:24 +02:00
close(listensocket);
client_clean(1);
return 0;
}
/*
Handle SIGINT- and TERM, call longjmp(), which jumps right into the
main loop, where it causes the loop to be left.
*/
2015-04-25 20:14:24 +02:00
void int_handler(int sig) {
signal(sig, SIG_IGN);
longjmp(JumpBuffer, 1);
2015-04-24 22:03:50 +02:00
}
void verb_prbind (host_t *bind_h) {
if(VERBOSE) {
if(strcmp(bind_h->ip, "0.0.0.0") != 0 || strcmp(bind_h->ip, "[::0]") != 0) {
verbose("from %s:%d\n", bind_h->ip, bind_h->port);
}
else {
verbose("\n");
}
}
}