/*

    File: ftpp/ftppmain.cc

    Copyright (C) 2002  Dmitry Udalov
    Email: uddm@penza.net
  
    This software 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.
    
 */


#include <stdio.h>
#include "ftpproxy.h"
#include <signal.h>
#include <syslog.h>
#include <sys/param.h>
#include <sys/resource.h>
#include "adbmtool.h"
#include "scantool.h"


void sighandler(int iSigNo);

//Flags for neceassary to Update configuration and to termination
int fIsNeedUpdate=FALSE, fNeedTerm=FALSE;
//Flags for mode work programm, server and client disconnected
int fTransparentFTPProxy=TRUE,fServerDisconnected=FALSE,fClientDisconnected=FALSE;
//Working port number, Maximal counts of clients, maximal size file for downloading
int iWorkingPort=WORKINGPORT,iMaxClients=MAXCLIENTCOUNT,iMaxFileSize=MAXFILESIZE;
//Number of clients with begin and count clients now time
int iClientNum=0,iClientCnt=0;
//Workin IP adress, path of debug log, path of 'access.log'
char szWorkingAddr[STR_SIZE],szLogPath[STR_SIZE],szAccessLogPath[STR_SIZE];
//File name for debug log and access log
char szLogFileName[STR_SIZE],szAccessLogFileName[STR_SIZE];

//timeout for save record at ADBM and read timeout for ftpp
int iADBMDelTimeOut=0,iADBMReadTimeOut=0;
//Netware interface string and filter for scanning
char szNetInterface[STR_SIZE],szScanFilter[STR_SIZE];

int ftpproxy();//Process ftpp
int startadbm();//Process ADBM(Adress database manager)
int startscan();//Scanner process

int main(){
    //create daemon
    int fd,scanpid,adbmpid;
    struct rlimit flim;
    if(getppid()!=1){
        signal(SIGTTOU,SIG_IGN);
        signal(SIGTTIN,SIG_IGN);
        signal(SIGTSTP,SIG_IGN);
        int pid=fork();
        if(pid>0)
            exit(0);
        if(pid==-1)
            exit(1);
        setsid();
    }
    getrlimit(RLIMIT_NOFILE,&flim);
    for(fd=0;fd<flim.rlim_max;fd++)
        close(fd);
//    chdir("/");
    //read log path from config file
    if(getparam("LOGPATH",szLogPath)==FALSE)
        strcpy(szLogPath,"");
    normaldir(szLogPath);//add slash to end if necessary
    if(getparam("LOGACCESSPATH",szAccessLogPath)==FALSE)
        strcpy(szAccessLogPath,"");
    normaldir(szAccessLogPath);
    //Make debug log filename and access log filename
    strcpy(szLogFileName,szLogPath);
    strcat(szLogFileName,LOGNAME);
    strcpy(szAccessLogFileName,szAccessLogPath);
    strcat(szAccessLogFileName,ACCESSLOG);

    //open system log
    openlog("ftppd",LOG_PID|LOG_CONS,LOG_DAEMON);
    syslog(LOG_INFO,"ftppd started");

    //read mode of work from config file
    char szStr[STR_SIZE];
    if(getparam("TRANSPARENTFTPP",szStr)==FALSE)
        fTransparentFTPProxy=0;
    else
        fTransparentFTPProxy=atoi(szStr);
//    logex(0,"Transparent %s",szStr);

    
    if(fTransparentFTPProxy==TRUE){//if transparent mode then
        //Read adbm parametrs from config file
        if(getparam("ADBMDELTIMEOUT",szStr)==FALSE)
            iADBMDelTimeOut=0;
        else
            iADBMDelTimeOut=atoi(szStr);
        if(iADBMDelTimeOut==0)
            iADBMDelTimeOut=ADBMDELTIMEOUT;
        if(getparam("ADBMREADTIMEOUT",szStr)==FALSE)
            iADBMReadTimeOut=0;
        else
            iADBMReadTimeOut=atoi(szStr);
        if(iADBMReadTimeOut==0)
            iADBMReadTimeOut=ADBMREADTIMEOUT;

        //start adbm
        adbmpid=startadbm();
        //start ftpscan
        scanpid=startscan();
    }

    //register signal handlers
    struct sigaction act,oact;
    sigemptyset(&act.sa_mask);
    act.sa_flags=0;
    act.sa_handler=SIG_IGN;
    sigaction(SIGALRM,&act,&oact);
    act.sa_handler=sighandler;    
    sigaction(SIGSEGV,&act,&oact);
    sigaction(SIGKILL,&act,&oact);
    sigaction(SIGTERM,&act,&oact);
    sigaction(SIGHUP,&act,&oact);
    sigaction(SIGPIPE,&act,&oact);
    sigaction(SIGINT,&act,&oact);
    sigaction(SIGCHLD,&act,&oact);

    //ftpp proxy
    logex(0,"FTP Proxy Session open.");
    while(ftpproxy()==TRUE){
        if(fNeedTerm==TRUE){//if flag for terminate then
            break;
        }
        syslog(LOG_INFO,"ftppd restarted");
        logex(0,"Session reopen.");
        fIsNeedUpdate=FALSE;
    }
    logex(0,"Session closed.");
    syslog(LOG_INFO,"ftppd stoped");
    closelog();//close system log
    kill(scanpid,SIGTERM);
    kill(adbmpid,SIGTERM);
}

//main function of ftp proxy
int ftpproxy(){
    //read working portnumber
    char szStr[STR_SIZE];
    if(getparam("WORKINGPORT",szStr)==FALSE)
        iWorkingPort=0;
    else
        iWorkingPort=atoi(szStr);
    if(iWorkingPort==0)
        iWorkingPort=WORKINGPORT;
    //read working host adress
    if(getparam("WORKINGADDR",szWorkingAddr)==FALSE)
        strcpy(szWorkingAddr,WORKINGADDR);
    //get IP adress by host name
    if(getipbyhost(szWorkingAddr)==FALSE){
        logex(0,"Can't convert host name to IP %s",szWorkingAddr);
        return FALSE;
    }

    int hMainSocket=socket(PF_INET,SOCK_STREAM,0);
    if(hMainSocket==-1){
        syslog(LOG_INFO,"can't create main socket");
        return -1;
    }
    int iOn=1;
    if(setsockopt(hMainSocket,SOL_SOCKET,SO_REUSEADDR,&iOn,sizeof(iOn))==-1){
        syslog(LOG_INFO,"setsockopt error");
    }
    char cBuf[BUF_SIZE];
    struct sockaddr_in ServerAddr;
    ServerAddr.sin_family=AF_INET;
    ServerAddr.sin_addr.s_addr=inet_addr(szWorkingAddr);//INADDR_ANY;//iAddr;
    ServerAddr.sin_port=htons(iWorkingPort);
    //bind
    if(bind(hMainSocket,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr))==-1){
        syslog(LOG_INFO,"can't bind to %d port",iWorkingPort);
        logex(0,"Can't bind to address %s and port %d",szWorkingAddr,iWorkingPort);
        close(hMainSocket);
        return TRUE;
    }
    socklen_t AddrLen=sizeof(ServerAddr);
    getsockname(hMainSocket,(struct sockaddr*)&ServerAddr,&AddrLen);
    //read maximum clients number from config file
    char szMaxClients[STR_SIZE];
    if(getparam("MAXCLIENTS",szMaxClients)==FALSE)
        iMaxClients=0;
    else
        iMaxClients=atoi(szMaxClients);
    if(iMaxClients==0)
        iMaxClients=MAXCLIENTCOUNT;
    //listen
    if(listen(hMainSocket,2)==-1){
        shutdown(hMainSocket,SHUT_RDWR);
        close(hMainSocket);
        return TRUE;
    }
    fd_set rfds;
    struct timeval tv;
    int iRetval=1;

    while(TRUE){
        if((fNeedTerm==TRUE)||(fIsNeedUpdate==TRUE))
            break;
        FD_ZERO(&rfds);
        FD_SET(hMainSocket, &rfds);
        tv.tv_sec = SECTIMEOUT;
        tv.tv_usec = MSECTIMEOUT;
        iRetval = select(hMainSocket+1, &rfds, NULL, NULL, &tv);//wait connection
        if (!iRetval)
            continue;
        if((fNeedTerm==TRUE)||(fIsNeedUpdate==TRUE))
            break;
        struct sockaddr_in ClientAddr;
        AddrLen=sizeof(ClientAddr);
        //accept client connection
        int hClientSocket = accept(hMainSocket,(struct sockaddr*)&ClientAddr,&AddrLen);
        if((hClientSocket == -1)){
            shutdown(hMainSocket,SHUT_RDWR);
            close(hMainSocket);
            return -1;
        }
        else{
            iClientNum++;//increment client number with begin work
            iClientCnt++;//increment client count now time
            if(iClientCnt>iMaxClients){
               strcpy(cBuf,FTPNPROMT);
               strcat(cBuf,"\r\n");
               send(hClientSocket,cBuf,strlen(cBuf),0);
               close(hClientSocket);
               iClientCnt--;
            }
            else{
	        logex(0,"Accepted client: %s",inet_ntoa(ClientAddr.sin_addr));
                handleclient(hClientSocket,ClientAddr);//processclient
	    }
        }
        hClientSocket=-1;
     }
     shutdown(hMainSocket,SHUT_RDWR);
     close(hMainSocket);
     hMainSocket=-1;
     if(fNeedTerm==TRUE)
         return FALSE;
     else
         return TRUE;
}

int startadbm(){
    //start adbm
    int pid=fork();//create new process for adbm
    if(pid==-1){
//        logex(0,"Can't fork adbm");
        return FALSE;
    }
    if(pid>0)
        return pid;
    if(pid==0){
//        logex(0,"Start adbm");
        proccesdb();//work adbm
        exit(0);
    }
}

int startscan(){
    //start netscan
    int pid=fork();//create new proccess for scannig net
    if(pid==-1){
//        logex(0,"Can't fork netscan");
        return FALSE;
    }
    if(pid>0)
        return pid;
    if(pid==0){
//        logex(0,"Start netscan");
        scan();//start scan
        exit(0);
    }
}

//signal handler
void sighandler(int iSigNo){
    switch(iSigNo){
        case SIGPIPE://if socket error then continue
            break;
        case SIGHUP://if need update config then set flag
            fIsNeedUpdate=TRUE;
            break;
        case SIGALRM://continue
            break;
        case SIGSEGV://if error then terminate process
            fNeedTerm=TRUE;
            break;
        case SIGCHLD://if closed child then delete his
            int status;
            logex(0,"child proccess closed.");
            wait(&status);
            iClientCnt--;
            break;
        case SIGKILL:
        case SIGTERM:
        case SIGINT:
            fNeedTerm=TRUE;//set flag to terminate
            break;
        default:
            break;
    }
    return;
}


