/*
* Copyright (C) 2003 Mihai RUSU (dizzy@roedu.net)
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#ifdef HAVE_SYS_UN_H
# include <sys/un.h>
#endif
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#ifdef HAVE_ERRNO_H
# include <errno.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif
#include "list.h"
#include "unix2tcp.h"
#include "server.h"
typedef struct {
char *data;
int len;
} t_packet;
typedef struct {
int sock;
t_list *outqueue;
} t_mysocket;
typedef struct {
t_mysocket in, out;
int inprogress;
} t_connection;
/* Global Variable Definitions */
static int listensock;
static struct sockaddr_in raddr;
static fd_set trfds, twfds;
static int maxsocket = 0, findmax;
static t_list *connlist = NULL;
static void packet_destroy(t_packet *);
/* Connection handling routines */
static int conn_new(int);
static int conn_establish(t_connection *);
static void conn_destroy(t_connection *);
static int socket_nonblock(int);
static int init_socket(void);
static void close_socket(void);
static int server_read(t_mysocket *, t_mysocket *);
static int server_write(t_mysocket *);
static void mainloop(void);
void server_process(void)
{
mylog("STARTUP\n");
if (init_socket() < 0) {
mylog("Error in init_socket()!\n");
return;
}
mainloop();
close_socket();
mylog("SHUTDOWN\n");
}
static int init_socket(void)
{
struct sockaddr_un unixaddr;
if (unixpath == NULL || raddrs == NULL || rports == NULL) return -1;
if (inet_aton(raddrs, &raddr.sin_addr) == 0) {
mylog("Error converting remote IP address (errno: %d str: '%s')", errno, strerror(errno));
return -1;
}
if (strlen(unixpath) >= MAXPATHLEN || strlen(unixpath) >= sizeof(unixaddr.sun_path)) {
mylog("Error : unix socket len too long!\n");
return -1;
}
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(rports));
listensock = socket(PF_UNIX, SOCK_STREAM, 0);
if (listensock < 0) {
mylog("Error on socket() (errno: %d str: '%s'\n", errno, strerror(errno));
return -1;
}
if (socket_nonblock(listensock) < 0) {
mylog("Error setting O_NONBLOCK (errno: %d str: '%s')\n", errno, strerror(errno));
close(listensock);
return -1;
}
memset(&unixaddr, 0, sizeof(unixaddr));
unixaddr.sun_family = AF_UNIX;
strcpy(unixaddr.sun_path, unixpath);
if (bind(listensock, (struct sockaddr *)&unixaddr, sizeof(unixaddr))) {
mylog("Error on bind() (errno: %d str: '%s')\n", errno, strerror(errno));
close(listensock);
return -1;
}
if (chmod(unixpath, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH) < 0) {
mylog("Error on chmod() (errno: %d str: '%s')\n", errno, strerror(errno));
close(listensock);
return -1;
}
if (listen(listensock, 5)) {
mylog("Error on listen() (errno: %d str: '%s')\n", errno, strerror(errno));
close(listensock);
unlink(unixpath);
return -1;
}
FD_ZERO(&trfds); FD_ZERO(&twfds);
FD_SET(listensock, &trfds);
if (maxsocket < listensock) maxsocket = listensock;
findmax = 100;
return 0;
}
static void close_socket(void)
{
t_list_elem *elem;
/* FIXME: close all incoming/outgoing connections */
if (listensock) close(listensock);
unlink(unixpath);
if (connlist) {
for(elem = LIST_FIRST(connlist); elem ; elem = LIST_NEXT(elem)) {
t_connection *c = elem_get_data(elem);
if (c) conn_destroy(c);
else mylog("close_socket : found NULL entry in conn list\n");
}
list_free(connlist);
connlist = NULL;
}
FD_ZERO(&trfds); FD_ZERO(&twfds);
}
static void mainloop(void)
{
fd_set rfds, wfds;
int res;
t_list_elem *elem, *next;
if (connlist == NULL && (connlist = list_init()) == NULL) return;
while(!quitasap) {
memmove(&rfds, &trfds, sizeof(fd_set));
memmove(&wfds, &twfds, sizeof(fd_set));
res = select(maxsocket + 1, &rfds, &wfds, NULL, NULL);
if (res == 0)
mylog("mainloop() : select returned with 0 fds!\n");
else if (res < 0)
switch(errno) {
case EINTR: /* got signal */
break;
default:
mylog("mainloop() : got error in select()(%s)\n", strerror(errno));
}
else {
if (FD_ISSET(listensock, &rfds)) {
int newsock;
newsock = accept(listensock, NULL, NULL);
if (newsock < 0)
switch(errno) {
case EAGAIN:
mylog("mainloop() : select returned rdy but accept() would block\n");
break;
default:
mylog("mainloop() : got error in accept()(%s)\n", strerror(errno));
}
else if (conn_new(newsock) < 0)
/* could not creat the proxy connection, close the source */
close(newsock);
}
for(elem = LIST_FIRST(connlist); elem ; elem = next) {
t_connection *c = elem_get_data(elem);
next = LIST_NEXT(elem);
if (c) {
res = 0;
if (FD_ISSET(c->in.sock, &rfds)) res = server_read(&c->in, &c->out);
if (res == 0 && FD_ISSET(c->in.sock, &wfds)) res = server_write(&c->in);
if (res == 0 && FD_ISSET(c->out.sock, &rfds)) res = server_read(&c->out, &c->in);
if (res == 0 && FD_ISSET(c->out.sock, &wfds)) {
if (c->inprogress) res = conn_establish(c);
else res = server_write(&c->out);
}
if (res < 0) quitasap = 1;
else if (res > 0) {
conn_destroy(c);
list_delete_by_elem(connlist, elem);
}
} else mylog("mainloop() : got NULL entry in in_conn list\n");
}
}
}
}
static int server_read(t_mysocket *in, t_mysocket *out)
{
static char buffer[4096];
t_packet *packet;
int res, len;
if (in == NULL || out == NULL) {
mylog("server_read : Got NULL in/out sockets\n");
return -1;
}
res = read(in->sock, buffer, sizeof(buffer));
if (res < 0) {
switch(errno) {
case EAGAIN: /* no data available */
case EINTR: /* call was interupted by a signel before any data was read */
return 0;
case ECONNRESET:
return 1;
default:
mylog("server_read : got error in read() (%s)\n", strerror(errno));
return -1;
}
}
if (res == 0) return 1; /* connection closed by peer */
res = write(out->sock, buffer, len = res);
if (res == len) return 0;
if (res < 0) {
switch(errno) {
case EPIPE: /* connection closed */
case ECONNRESET:
return 1;
case EAGAIN: /* would block */
case EINTR: /* interupted in write() */
break;
default:
mylog("server_read : error in write() (%s)\n", strerror(errno));
return -1;
}
} else if (res && res < len) { /* sent a part of original data */
len -= res;
memmove(buffer, buffer + res, len);
}
/* could not send read data, so we queue it for later send */
if (out->outqueue == NULL && (out->outqueue = list_init()) == NULL) {
mylog("server_read : could not init the outqueue\n");
return -1;
}
if ((packet = malloc(sizeof(t_packet))) == NULL) {
mylog("server_read : could not allocate new packet\n");
return -1;
}
if ((packet->data = malloc(len)) == NULL) {
mylog("server_read : could not allocate for data\n");
free(packet);
return -1;
}
memmove(packet->data, buffer, len);
packet->len = len;
if (list_append_data(out->outqueue, packet) < 0) {
mylog("server_read : could not append packet to out\n");
free(packet->data);
free(packet);
return -1;
}
FD_SET(out->sock, &twfds); /* next select() will need to check for writing */
return 0;
}
static int server_write(t_mysocket *con)
{
int res;
t_list_elem *elem;
t_packet *packet;
if (con == NULL) {
mylog("server_write : got NULL con\n");
return -1;
}
if (con->outqueue == NULL) {
mylog("server_write : got NULL outqueue\n");
return -1;
}
elem = LIST_FIRST(con->outqueue);
if (elem == NULL) {
mylog("server_write : outqueue is empty\n");
return -1;
}
packet = elem_get_data(elem);
if (packet == NULL || packet->data == NULL) {
mylog("server_write : found NULL packet in outqueue\n");
return -1;
}
res = write(con->sock, packet->data, packet->len);
if (res < 0) {
switch(errno) {
case EPIPE: /* connection closed */
case ECONNRESET:
return 1;
case EAGAIN: /* would block */
case EINTR: /* interupted in write() */
return 0;
default:
mylog("server_write : error in write() (%s)\n", strerror(errno));
return -1;
}
}
if (!res) return 0; /* nothing written */
if (res < packet->len) { /* we wrote fewer bytes than requested */
memmove(packet->data, (char *)packet->data + res, packet->len - res);
packet->len -= res;
return 0;
}
packet_destroy(packet);
if (list_delete_by_elem(con->outqueue, elem) < 0) {
mylog("server_write : could not remove sent packet from outqueue\n");
return -1;
}
/* outqueue is empty so next select() wont need to check for write */
if (list_get_size(con->outqueue) == 0) FD_CLR(con->sock, &twfds);
return 0;
}
static int conn_new(int newsock)
{
t_connection *c;
int outsock;
if (connlist == NULL) return -1;
if (newsock < 1) {
mylog("conn_new() : got invalid socket\n");
return -1;
}
c = malloc(sizeof(t_connection));
if (c == NULL) {
mylog("conn_new() : could not allocate for new connections\n");
return -1;
}
outsock = socket(PF_INET, SOCK_STREAM, 0);
if (outsock < 1) {
mylog("conn_new() : could not create new socket\n");
free(c);
return -1;
}
if (socket_nonblock(outsock) < 0) {
mylog("conn_new() : could not set O_NONBLOCK on socket\n");
free(c);
close(outsock);
return -1;
}
if (connect(outsock, (struct sockaddr *) &raddr, sizeof(raddr))) {
switch(errno) {
case EINPROGRESS: /* note a in progress connection */
c->inprogress = 1;
break;
default:
mylog("conn_new() : could not connect()(%s)\n", strerror(errno));
close(outsock);
free(c);
return -1;
}
} else c->inprogress = 0;
c->in.sock = newsock;
c->in.outqueue = NULL;
c->out.sock = outsock;
c->out.outqueue = NULL;
if (list_append_data(connlist, c) < 0) {
mylog("conn_new() : could not append new connection to list\n");
close(outsock);
free(c);
return -1;
}
if (maxsocket < c->in.sock) maxsocket = c->in.sock;
if (maxsocket < c->out.sock) maxsocket = c->out.sock;
if (!c->inprogress) { /* connection fully established */
FD_SET(c->in.sock, &trfds);
FD_SET(c->out.sock, &trfds);
} else FD_SET(c->out.sock, &twfds); /* connection in progress, wait for write */
return 0;
}
static int conn_establish(t_connection *c)
{
int error;
socklen_t len;
if (c == NULL) {
mylog("conn_establish() : found NULL connection\n");
return -1;
}
if (!c->inprogress || c->out.sock <= 0) {
mylog("conn_establish() : got invalid connection\n");
return -1;
}
len = sizeof(error);
if (getsockopt(c->out.sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
mylog("conn_establish() : could not getsockopt()\n");
return -1;
}
if (error) /* could not establish remote connection */
return 1; /* close this connection */
c->inprogress = 0; /* connection established */
FD_CLR(c->out.sock, &twfds);
FD_SET(c->in.sock, &trfds); FD_SET(c->out.sock, &trfds);
return 0;
}
static void conn_destroy(t_connection *c)
{
t_list_elem *elem;
t_packet *packet;
if (c == NULL) return;
close(c->in.sock);
close(c->out.sock);
FD_CLR(c->in.sock, &trfds); FD_CLR(c->out.sock, &trfds);
FD_CLR(c->in.sock, &twfds); FD_CLR(c->out.sock, &twfds);
if (--findmax == 0) {
t_connection *con;
maxsocket = listensock;
for(elem = LIST_FIRST(connlist); elem; elem = LIST_NEXT(elem)) {
con = elem_get_data(elem);
if (con && con != c) {
if (con->in.sock > maxsocket) maxsocket = con->in.sock;
if (con->out.sock > maxsocket) maxsocket = con->out.sock;
}
}
findmax = 100;
}
if (c->in.outqueue) {
for(elem = LIST_FIRST(c->in.outqueue); elem; elem = LIST_NEXT(elem)) {
packet = elem_get_data(elem);
if (packet) packet_destroy(packet);
else mylog("conn_destroy : found NULL entry in outqueue\n");
}
list_free(c->in.outqueue);
}
if (c->out.outqueue) {
for(elem = LIST_FIRST(c->out.outqueue); elem; elem = LIST_NEXT(elem)) {
packet = elem_get_data(elem);
if (packet) packet_destroy(packet);
else mylog("conn_destroy : found NULL entry in outqueue\n");
}
list_free(c->out.outqueue);
}
free(c);
}
static void packet_destroy(t_packet *packet)
{
if (packet == NULL) return;
if (packet->data) free(packet->data);
free(packet);
}
static int socket_nonblock(int fd)
{
int flags;
flags = fcntl(fd, F_GETFL);
if (flags < 0) return -1;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) return -1;
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1