/*    SIMPLE HTTP PROXY SERVER
      AUTHOR: Matus Fedak
*/

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <netdb.h>
#include <netinet/in.h>

#define DEFAULT_PORT 8010
#define BUFSIZE 100240
#define MAX_WAITING_CONNECTIONS 20
int port=DEFAULT_PORT;

struct SocketPair{
    int  user;      //fd : local machine to proxy server
    int  server;    //fd : proxy sever to remote server
    bool IsUser_ProxyClosed; // status of local machine to proxy server
    bool IsProxy_ServerClosed; // status of proxy server to remote server
};

int GetAddressAndPort( char * str, char *address, int * port)
{ //Ziska adresu z toho co som dostal od klienta
//    printf(">>%s<<",str);
    if (strstr(str,"\n")-str>1000) {
       printf("Prilis dlha adresa\n");
       return 2;
    }
    char buf[1002], command[1002], proto[1002], *p, *q, ch;
    q=strstr(str,"\n");
    *q=0;
    sscanf(str,"%s%s%s",command,buf,proto);
//    printf("Command:%s\nBuff:%s\nProto:%s\n",command,buf,proto);
    p=strstr(buf,"http://");
    if(p) {
      p+=strlen("http://");
      int i;
      for(i=0;i<(int)strlen(p);i++)
         if(( *(p+i)=='/')or(*(p+i)==':')) break;
      ch=*(p+i);
      *(p+i)=0;
      strcpy(address,p);
      *(p+i)=ch;
      p=p+i+1;
      if (ch==':') {
         for(i=0;i<(int)strlen(p);i++)
           if(*(p+i)=='/') break;
         ch=*(p+i);
         *(p+i)=0;
         sscanf(p,"%d",port);
         *(p+i)=ch;
      } else {  
         *port=80;      //default http port 
      }
    } else { 
       printf("Neznamy protokol:\n%s\n",buf);
       return 1;
    }
    *q='\n';
    return 0;
}
void setc(int fd){ //Send Error To Client
char s[]="HTTP/1.1 47 Proxy error \nDate: Mon, 18 Jun 2007 17:52:35 GMT\nServer: Apache\nLocation: http://www.ksp.sk/\nTransfer-Encoding: chunked\nContent-Type: text/html; charset=iso-8859-1\n\ne5\n<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<HTML><HEAD>\n<TITLE>47 PROXY ERROR</TITLE>\n</HEAD><BODY>\n<H1>PROXY ERROR</H1>\nNastala chyba <P>\n</BODY></HTML>\n";
send(fd,s,strlen(s),MSG_NOSIGNAL);
close(fd);
}
int comunicate(int fd){
        printf("Spojenie bolo uspesne prijate na fd:%d\n",fd);
        SocketPair SPair;      
        SPair.IsUser_ProxyClosed=false;
        SPair.IsProxy_ServerClosed=true;
        SPair.user=fd;
        char buf[BUFSIZE];
        ssize_t s=recv(fd,buf,BUFSIZE-1,0);
        if (s<0) {
          printf("\nError Recv from client\n");
          if (SPair.IsUser_ProxyClosed==false)
            { close(SPair.user);
              SPair.IsUser_ProxyClosed=true;
            }
          return 1;
        } 
        if(s==0)
          { printf("Client Close connection\n");
            if(SPair.IsUser_ProxyClosed==false)
              {close(SPair.user);
               SPair.IsUser_ProxyClosed=true;
              }
            return 1;
          }
//      buf[s]='\0';
//      printf("%s\n",buf);        
        int serv_port;
        char serv_name[1002];
        if (GetAddressAndPort(buf,serv_name,&serv_port)>0) {
            setc(fd);
            return 1;
        }  
        printf("adresa:%s port:%d\n",serv_name,serv_port);
        struct hostent *hp = gethostbyname(serv_name);  //zisti ip adresu z www adresy
        if (hp == NULL) {
             perror(strerror(errno));
             printf("gethostbyname() failed\n");
             setc(fd);
             return 1;
        } 
       struct sockaddr_in server;  //nastavenie pripojenia na server
       bcopy ( hp->h_addr_list[0], &(server.sin_addr.s_addr), hp->h_length);
       server.sin_family=AF_INET;
       server.sin_port=htons(serv_port);
       int sd;//fd na ktorom sa pripojim na server
       sd = socket (AF_INET,SOCK_STREAM,0);
       if (connect(sd, (struct sockaddr*)&server, sizeof(server))==-1) {
          perror(strerror(errno));
          setc(fd);
          return 1;
       } else  {
          SPair.IsProxy_ServerClosed=false;
          SPair.server=sd;
       }
//       printf("NEW>>%s\n",buf);        
       ssize_t ss=send(sd,buf,s,MSG_NOSIGNAL);
       if (ss!=s) {
         printf("Nepodarilo sa poslat vsetky prijate udaje a preto bude konekcia zrusena\n");
         setc(fd);
         return 1;
       }
// MAIN CYKLUS NA VYMENU DAT MEDZY SERVEROM A USEROM
fd_set r,w,e;
int max;
if (SPair.user>SPair.server) max= SPair.user; else max=SPair.server;
max++;
while(SPair.IsProxy_ServerClosed ==false && SPair.IsUser_ProxyClosed==false)     {  
FD_ZERO(&r);FD_ZERO(&w);FD_ZERO(&e);FD_SET(SPair.user,&r);FD_SET(SPair.server,&r);
if (select(max,&r,&w,&e,NULL)==-1) {
   perror(strerror(errno));
   return 1;
}
if (FD_ISSET(SPair.server,&r)) {
    s=read(SPair.server,buf,BUFSIZE);
/*    while*/if (s>0){
      ss=send(SPair.user,buf,s,MSG_NOSIGNAL);
    printf("%d send to user from %d bytes %d\n",SPair.user,SPair.server,(int)s);
      if (ss!=s) {
         printf("Nepodarilo sa poslat vsetky udaje zo servra k pouzivatelovy preto bude konekcia zrusena\n");
         close(SPair.server);
         close(SPair.user);
         return 1;
       }
//      s=read(SPair.server,buf,BUFSIZE);
    }
    if (s==0) {
      close(SPair.server);
      SPair.IsProxy_ServerClosed =true;
      break;
    }
    
}
if (FD_ISSET(SPair.user,&r)) {
    s=read(SPair.user,buf,BUFSIZE);
   /* while*/if (s>0){
        ss=send(SPair.server,buf,s,MSG_NOSIGNAL);
      printf("%d send to server from %d bytes %d\n",SPair.server,SPair.user,(int)s);
        if (ss!=s) {
           printf("Nepodarilo sa poslat vsetky udaje od uzivatela na server a preto bude konekcia zrusena\n");
           return 1;
        }
//        s=read(SPair.user,buf,BUFSIZE);
    }
    if (s==0) {
      close(SPair.user);
      SPair.IsUser_ProxyClosed=true;
      break;
    }
}
}//koniec hlavneho posielajuceho cyklu teda jedno spojenie uz bolo ukoncene
     
      if(SPair.IsProxy_ServerClosed==false)
        {    shutdown(SPair.server,SHUT_RDWR);
             close(SPair.server);
             SPair.IsProxy_ServerClosed=true;
        }
      if(SPair.IsUser_ProxyClosed==false)
        {    shutdown(SPair.user,SHUT_RDWR);
             close(SPair.user);
             SPair.IsUser_ProxyClosed=true;
        }
        printf("USPESNY KONIEC SPOJENIA %d\n",fd);
        return 0;
}

int main(int argc, char **argv){
        //parsovanie parametrov
        int i;
        for(i=1;i<argc;i++){
                if((!strcmp(argv[i],"-p"))&&(i+1<argc))  port=atoi(argv[++i]);else
                if((!strcmp(argv[i],"-D"))&&(i+2<argc)){
                        daemon(atoi(argv[i+1]),atoi(argv[i+2]));
                        i+=3;
                }else
                {
                        printf("usage: proxy [-p port, -D 0/1 0/1]\n");
                        return 0;
                }
        }
        
        signal(SIGCLD, SIG_IGN); //ignotuje smrt dietata
        int fd=socket(PF_INET,SOCK_STREAM,0);
        struct sockaddr[21~_in proxy,client;
        socklen_t len;
        if(fd==-1){
                perror(strerror(errno));
                return 1;
        }
        proxy.sin_family=AF_INET;
        proxy.sin_addr.s_addr=INADDR_ANY;
        proxy.sin_port=htons(port);
        if(bind(fd,(struct sockaddr*)&proxy,sizeof(proxy))==-1){
                perror(strerror(errno));
                return 1;
        };
        if(listen(fd,MAX_WAITING_CONNECTIONS)==-1){
                perror(strerror(errno));
                return 1;
        }
        int client_d;
        printf("Cakam na spojenia\n");
        while(1){
                len=sizeof(client);
                client_d=accept(fd,(struct sockaddr*)&client,&len);
                if(client_d==-1){
                        perror(strerror(errno));
                        return 1;
                }
                int f=fork(); 
                //prime spojenie a rozdvoji sa pricom jeden program ostane servrovat a druhy spracuje konekciu
                if(f==-1){
                        perror(strerror(errno));
                        return 1;
                }
                if(!f) {close(fd); return comunicate(client_d);}
                close(client_d);
        }
        return 0;
}

