#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#ifdef HAVE_MODE_Z
#include <zlib.h>
#endif
#include <arpa/inet.h>
#include <netdb.h>
#include <libpq-fe.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/x509.h>
#include <openssl/err.h>
#include "tools.h"

struct conn *t_co = NULL;

int check_logout(struct conn *co, char reason);

void timeout(){
	sock_write_bytes(t_co,"221 Connection timed out.\r\n",-1);
	sleep(1);
	check_logout(t_co,'I');
	exit(0);
	
}

#define MAX_LOG_SIZE (20 * 1024 * 1024)

#ifdef HAVE_MODE_Z
char *display_zerr(int ret){
	switch(ret){
		case Z_OK:
			return("OK");
			break;
		case Z_STREAM_END:
			return("STREAM_END");
			break;
		case Z_STREAM_ERROR:
			return("INVALID COMPRESSION LEVEL");
			break;
		case Z_DATA_ERROR:
			return("INVALID OR INCOMPLETE DEFLATE DATA");
			break;
		case Z_MEM_ERROR:
			return("OUT OF MEMORY");
			break;
		case Z_BUF_ERROR:
			return("BUFFER ERROR");
			break;
		case Z_VERSION_ERROR:
			return("ZLIB VERSION MISMATCH");
			break;
	}
	return("UNKNOWN ERROR");
}
#endif


void logger(struct conn *fx, const char *fmt, ...){
	struct stat st;
        char p[10241];
	char dummy[INET6_ADDRSTRLEN];
	char nextgen[124];
	char oldgen[124];
        static FILE *log = NULL;
        va_list ap;
        struct timeval tv;
        struct tm *tm;
        char datestamp[104];
        gettimeofday(&tv,NULL);
        tm = localtime(&tv.tv_sec);


	if ( fx && fx->ipv6 ){
        	sprintf(datestamp,"[%04d-%02d-%02d %02d:%02d:%02d.%06d]{%s}",
        	        tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
        	        tm->tm_hour, tm->tm_min, tm->tm_sec, tv.tv_usec,
        	        fx != NULL ? inet_ntop(AF_INET6,&(fx->from6.sin6_addr),dummy,INET6_ADDRSTRLEN) : "undef" );
	} else {
        	sprintf(datestamp,"[%04d-%02d-%02d %02d:%02d:%02d.%06d]{%s}",
        	        tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
        	        tm->tm_hour, tm->tm_min, tm->tm_sec, tv.tv_usec,
        	        fx != NULL ? inet_ntoa(fx->from.sin_addr) : "undef" );
	}


        va_start(ap, fmt);
        vsnprintf (p, 10240, fmt, ap);
        va_end(ap);

	if ( (fx == NULL || fx->uid == 0) && stat(LOGFILE,&st) == 0 ){
		if ( st.st_size > MAX_LOG_SIZE ){
			sprintf(nextgen,"%s.10",LOGFILE);
			sprintf(oldgen,"%s.9",LOGFILE);
			unlink(nextgen);
			rename(oldgen,nextgen);
			sprintf(nextgen,"%s.9",LOGFILE);
			sprintf(oldgen,"%s.8",LOGFILE);
			rename(oldgen,nextgen);
			sprintf(nextgen,"%s.8",LOGFILE);
			sprintf(oldgen,"%s.7",LOGFILE);
			rename(oldgen,nextgen);
			sprintf(nextgen,"%s.7",LOGFILE);
			sprintf(oldgen,"%s.6",LOGFILE);
			rename(oldgen,nextgen);
			sprintf(nextgen,"%s.6",LOGFILE);
			sprintf(oldgen,"%s.5",LOGFILE);
			rename(oldgen,nextgen);
			sprintf(nextgen,"%s.5",LOGFILE);
			sprintf(oldgen,"%s.4",LOGFILE);
			rename(oldgen,nextgen);
			sprintf(nextgen,"%s.4",LOGFILE);
			sprintf(oldgen,"%s.3",LOGFILE);
			rename(oldgen,nextgen);
			sprintf(nextgen,"%s.3",LOGFILE);
			sprintf(oldgen,"%s.2",LOGFILE);
			rename(oldgen,nextgen);
			sprintf(nextgen,"%s.2",LOGFILE);
			sprintf(oldgen,"%s.1",LOGFILE);
			rename(oldgen,nextgen);
			sprintf(nextgen,"%s.1",LOGFILE);
			sprintf(oldgen,"%s.0",LOGFILE);
			rename(oldgen,nextgen);
			sprintf(nextgen,"%s.0",LOGFILE);
			sprintf(oldgen,"%s",LOGFILE);
			rename(oldgen,nextgen);
			if (log == NULL) fclose(log);
			log = fopen(LOGFILE,"a");
		}
	}		

        if ( log == NULL ) log = fopen(LOGFILE,"a");
        if ( log != NULL ){
                fprintf(log,"%s %s",datestamp,p);
                fflush(log);
        }
        if ( fx && fx->debug ) printf("%s %s",datestamp,p);
}


PGconn *db_connect(char *dbname, char *host, char *port, char *user, char *pwd){
        PGconn *p;
        int status;
        time_t start;
        int connected;

        p = PQsetdbLogin(host,port,NULL,NULL,dbname,user,pwd);

        if ( p == NULL ){
                return(NULL);
        }
        PQsetClientEncoding(p,"UNICODE");
        start = time(NULL);
        while ( 1 ){
                status = PQstatus(p);
                switch(status){
                        case CONNECTION_OK:
                                return(p);
                        case CONNECTION_BAD:
                                PQfinish(p);
                                return(NULL);
                        case CONNECTION_MADE:
                                return(p);
                        default:
                                break;
                }
                if ( (start + 5) < time(NULL) ){
                        PQfinish(p);
                        return(NULL);
                }
                usleep(10000);
        }
        return(NULL);
}

int read_data_socket(struct conn *co, char *buf, int len){
	int sock_to_read;
	SSL *ssl_to_read;


	if ( co->passive == 1 ){
		sock_to_read = co->passive_nsock;
		ssl_to_read = co->passive_ssl;
	} else {
		sock_to_read = co->active_socket;
		ssl_to_read = co->active_ssl;
	}
        if ( co->use_data_ssl ){
		int siz;
		siz = SSL_read(ssl_to_read,buf,len);
		co->bytes_in += siz;
		return(siz);
        } else {
		int siz;
		siz = recv(sock_to_read,buf,len,0);
		co->bytes_in += siz;
                return(siz);
	}

}

int write_data_socket(struct conn *co, char *buf, int len){
	int sock_to_write;
	SSL *ssl_to_write;

	if ( len == -1 ) len = strlen(buf);

	if ( co->passive == 1 ){
		sock_to_write = co->passive_nsock;
		ssl_to_write = co->passive_ssl;
	} else {
		sock_to_write = co->active_socket;
		ssl_to_write = co->active_ssl;
	}
        if ( co->use_data_ssl ){
		int siz;
		siz = SSL_write(ssl_to_write,buf,len);
		co->bytes_out += siz;
                return(siz);
        } else {
		int siz;
		siz = send(sock_to_write,buf,len,0);
		co->bytes_out += siz;
                return(siz);
	}

}


int sock_write_bytes(struct conn *co, char *buf, int len){

        if ( len == -1 ) len = strlen(buf);

	if ( co->debug ) logger(co,">> %s\n",buf);

        if ( co->use_ssl ){
		int siz;
		siz = SSL_write(co->ssl,buf,len);
		co->cbytes_out += siz;
                return(siz);
        } else {
		int siz;
		siz = send(co->socket,buf,len,0);
		co->cbytes_out += siz;
                return(siz);
	}
}

int sock_read_line(struct conn *co, char *buf, int len){
        int pos;
        char c;
        int res;

        pos = 0;
        buf[pos] = '\0';
	if ( co->idle_timeout ){
		t_co = co;
		signal(SIGALRM, timeout);
		alarm(co->idle_timeout);
	}
        while ( 1 ){
                if ( co->use_ssl )
                        res = SSL_read(co->ssl,&c,1);
                else
                        res = recv(co->socket,&c,1,0);
		if ( pos == 0 && res < 0 ){
			*buf = '\0';
			alarm(0);
			return(-1);
		}
                if ( res <= 0 ) break;


                if ( c == '\r' ) continue;
		co->cbytes_in++;
                buf[pos++] = c;
                buf[pos] = '\0';

                if ( pos >= len ){
			if ( co->debug ) logger(co,"<< %s\n",buf);
			alarm(0);
			return(pos);
		}
                if ( c == '\n' ){
                        buf[pos-1] = '\0'; // Cut off newline
			if ( co->debug ) logger(co,"<< %s\n",buf);
			alarm(0);
                        return(pos-1);
                }
        }
	if ( co->debug ) logger(co,"<< %s\n",buf);
	alarm(0);
        return(pos);
}
