/*
 * Tentun - covert tunneling project
 *
 * Copyright (C) 2003 David Gordon
 *
 *      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.
 */


#include "config.h"

#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <errno.h>

#ifdef HAVE_SYS_SOCKIO_H
#include <sys/sockio.h>
#endif

#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#ifdef HAVE_NETINET_IN_SYSTM_H
#include <netinet/in_systm.h>
#endif

#ifdef HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif

#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif

#ifdef HAVE_RESOLV_H
#include <resolv.h>
#endif

#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif

#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif

#include "tentun.h"
#include "lib.h"


/* Read N bytes with timeout */
int readn_t(int fd, void *buf, size_t count, time_t timeout) 
{
	fd_set fdset;
	struct timeval tv;

	tv.tv_usec=0; tv.tv_sec=timeout;

	FD_ZERO(&fdset);
	FD_SET(fd,&fdset);
	if( select(fd+1,&fdset,NULL,NULL,&tv) <= 0)
	   return -1;

	return read_n(fd, buf, count);
}


/* Get interface address */
unsigned long getifaddr(char * ifname) 
{
     struct sockaddr_in addr;
     struct ifreq ifr;
     int s;

     if( (s = socket(AF_INET, SOCK_DGRAM, 0)) == -1 )
        return -1;

     strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)-1);
     ifr.ifr_name[sizeof(ifr.ifr_name)-1]='\0';

     if( ioctl(s, SIOCGIFADDR, &ifr) < 0 ){
        close(s);
        return -1;
     }
     close(s);

     addr = *((struct sockaddr_in *) &ifr.ifr_addr);

     return addr.sin_addr.s_addr;
}

void free_sopt( struct tentun_sopt *opt )
{
     if( opt->dev ){
	free(opt->dev);
        opt->dev = NULL;
     }

     if( opt->laddr ){
	free(opt->laddr);
        opt->laddr = NULL;
     }

     if( opt->raddr ){
	free(opt->raddr);
        opt->raddr = NULL;
     }
}

/* Set local address */
int local_addr(struct sockaddr_in *addr, struct tentun_host *host, int con)
{
     struct hostent * hent;
     int opt;

     if( con ){
        /* Use address of the already connected socket. */
        opt = sizeof(struct sockaddr_in);
        if( getsockname(host->rmt_fd, (struct sockaddr *)addr, &opt) < 0 ){
           TENTUN_PRINT("Can't get local socket address");
           return -1; 
        }
     } else {
        memset(addr, 0, sizeof(struct sockaddr_in));
        addr->sin_family = AF_INET;
        switch( host->src_addr.type ){
           case TENTUN_ADDR_IFACE:
              if( !( addr->sin_addr.s_addr = getifaddr(host->src_addr.name)) ){
                 TENTUN_PRINT("Can't get address of interface %s", 
					host->src_addr.name);
                 return -1;
              }
	      break;
           case TENTUN_ADDR_NAME:
              if( !(hent = gethostbyname(host->src_addr.name)) ){
                 TENTUN_PRINT("Can't resolv local address %s", 
					host->src_addr.name);
                 return -1;
              }
              addr->sin_addr.s_addr = *(unsigned long *)hent->h_addr; 
	      break;
           default:
              addr->sin_addr.s_addr = INADDR_ANY; 
              break;
        }
     }
     if( host->src_addr.port )	
        addr->sin_port = htons(host->src_addr.port);

     host->sopt.laddr = strdup(inet_ntoa(addr->sin_addr));

     return 0;
}


/* 
 * Establish UDP session with host connected to fd(socket).
 * Returns connected UDP socket or -1 on error.
 */
int udp_session(struct tentun_host *host) 
{
     struct sockaddr_in saddr; 
     short port;
     int s,opt;

     if( (s=socket(AF_INET,SOCK_DGRAM,0))== -1 ){
        TENTUN_PRINT("Can't create socket");
        return -1;
     }

     opt=1;
     setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 
    
     /* Set local address and port */
     local_addr(&saddr, host, 1);
     if( bind(s,(struct sockaddr *)&saddr,sizeof(saddr)) ){
        TENTUN_PRINT("Can't bind to the socket");
        return -1;
     }

     opt = sizeof(saddr);
     if( getsockname(s,(struct sockaddr *)&saddr,&opt) ){
        TENTUN_PRINT("Can't get socket name");
        return -1;
     }

     /* Write port of the new UDP socket */
     port = saddr.sin_port;
     if( write_n(host->rmt_fd,(char *)&port,sizeof(short)) < 0 ){
        TENTUN_PRINT("Can't write port number");
        return -1;
     }
     host->sopt.lport = htons(port);

     /* Read port of the other's end UDP socket */
     if( readn_t(host->rmt_fd,&port,sizeof(short),host->timeout) < 0 ){
        TENTUN_PRINT("Can't read port number %s", strerror(errno));
        return -1;
     }

     opt = sizeof(saddr);
     if( getpeername(host->rmt_fd,(struct sockaddr *)&saddr,&opt) ){
        TENTUN_PRINT("Can't get peer name");
        return -1;
     }

     saddr.sin_port = port;
     if( connect(s,(struct sockaddr *)&saddr,sizeof(saddr)) ){
        TENTUN_PRINT("Can't connect socket");
        return -1;
     }
     host->sopt.rport = htons(port);

     /* Close TCP socket and replace with UDP socket */	
     close(host->rmt_fd); 
     host->rmt_fd = s;	

     TENTUN_PRINT("UDP connection initialized");
     return s;
}


/* Connect with timeout */
int connect_t(int s, struct sockaddr *svr, time_t timeout) 
{
     /* Some SOCKS implementations don't support
      * non blocking connect */
     /* return connect(s,svr,sizeof(struct sockaddr)); */

     int sock_flags;
     fd_set fdset;
     struct timeval tv;

     tv.tv_usec=0; tv.tv_sec=timeout;

     sock_flags=fcntl(s,F_GETFL);
     if( fcntl(s,F_SETFL,O_NONBLOCK) < 0 )
        return -1;

     if( connect(s,svr,sizeof(struct sockaddr)) < 0 && errno != EINPROGRESS)
        return -1;

     FD_ZERO(&fdset);
     FD_SET(s,&fdset);
     if( select(s+1,NULL,&fdset,NULL,timeout?&tv:NULL) > 0 ){
        int l=sizeof(errno);	 
        errno=0;
        getsockopt(s,SOL_SOCKET,SO_ERROR,&errno,&l);
     } else
        errno=ETIMEDOUT;  	

     fcntl(s,F_SETFL,sock_flags); 

     if( errno )
        return -1;

     return 0;
}


int server_addr(struct sockaddr_in *addr, struct tentun_host *host)
{
     struct hostent * hent;

     memset(addr,0,sizeof(struct sockaddr_in));
     addr->sin_family = AF_INET;
     addr->sin_port = htons(tentun.svr_port);

     /* Lookup server's IP address.
      * We do it on every reconnect because server's IP 
      * address can be dynamic.
      */
     if( !(hent = gethostbyname(tentun.svr_name)) ){
        TENTUN_PRINT("Can't resolv server address: %s", tentun.svr_name);
        return -1;
     }
     addr->sin_addr.s_addr = *(unsigned long *)hent->h_addr; 

     host->sopt.raddr = strdup(inet_ntoa(addr->sin_addr));
     host->sopt.rport = tentun.svr_port;

     return 0; 
}
