/*   ftpd.c
 *
 *   This file is part of FTP4ALL
 *
 *   Copyright (C) 1996,1997,1998,1999 by Christoph Schwarz
 *
 *   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
 *   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.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

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

#include <ix.h>

#include "tweak.h"
#ifdef HAVE_SYS_SELECT_H
  #include <sys/select.h>
#endif
#ifdef HAVE_SYS_TIME_H
  #include <sys/time.h>
#endif
#ifdef DES_ENCRYPTION
  #include "des.h"
#endif
#include "globalsd.h"

int send_reply(int s, int rno, char* buffer)
{ char msg[8];

  sprintf(msg,"%d ",rno);
  if (send(s,msg,strlen(msg),0) == -1) return -1;
  if (send(s,buffer,strlen(buffer),0) == -1) return -1;
  if (*(buffer+strlen(buffer)-1) != '\n') if (send(s,"\n",1,0) == -1) return -1;
  return 0;
}

int write_stat_file(const char* filename)
{ FILE*          file;
  int            first;
  int            i;
  int            j;
  struct user_s* u = user;

  if ((file = fopen(filename,"wt")) == NULL) return -1;
  for (i=0; i<user_count; i++, u++)
  { fprintf(file,"%s:%li:%li:%s:%li:%li:%li:*.*.*.*:%li:%s:0x%02lX:%li",
      u->name,u->uid,u->gid,u->password,u->kb_downloaded,u->kb_uploaded,
      u->last_login,u->limit,u->info,u->class,u->credit);
    first = 1;
    for (j=0; j<8; j++) if (u->gids[j] != -1)
    { fprintf(file,"%c%li",first ? ':' : ',' ,u->gids[j]);
      first = 0;
    }
    fprintf(file,"\n");
  }
  fclose(file);
  return 0;
}

void signal_handler(int sig)
{ if (((sig_write==sig_size-1) && !sig_read) || (sig_write+1==sig_read))
  { fprintf(stderr,"ftpd(%i): array full, signal %i lost\n",getpid(),sig);
    return;
  }
  sig_array[sig_write++] = sig;
  if (sig_write >= sig_size) sig_write = 0;
}

void handle_signals(int s)
{ struct child_s* c;
  int             e;
  int             i;
  pid_t           pid;
  time_t          t;
  char            tstr[256];

  while (sig_read != sig_write)
  {
#ifdef DEBUG
  fprintf(debugf,"processing singnal %i\n",sig_array[sig_read]);
#endif
    switch (sig_array[sig_read])
    { case SIGHUP :
      case SIGINT :
      case SIGQUIT :
      case SIGTERM :
        e = sig_array[sig_read];
        if (e == SIGQUIT) e = SIGTERM;
        c = child;
        for (i=0; i<children; i++)
        { kill(c->pid,e);
          c++;
        }
        strcpy(tstr,user_file ? user_file : ".users");
        strcat(tstr,".bak");
        rename(user_file ? user_file : ".users",tstr);
        strcpy(tstr,group_file ? group_file : ".groups");
        strcat(tstr,".bak");
        rename(group_file ? group_file : ".groups",tstr);
        write_user_file(user_file ? user_file : ".users");
        write_group_file(group_file ? group_file : ".groups");
        time(&t);
        strftime(tstr,sizeof(tstr),"%a %b %d, %Y %H:%M:%S",localtime(&t));
        fprintf(stderr,"ftpd(%i): %s - terminated on signal %i\n",getpid(),
          tstr,e);
        while (children)
        { kill(child->pid,SIGKILL);
          child_logout(child,CL_FORCE);
        }
        close(s);
        unlink(pid_file ? pid_file : "/var/run/ftpd.pid");
        if (log_pid) fclose(log_program_f);
        exit(e);
      case SIGUSR1 :
        write_user_file(user_file ? user_file : ".users");
        write_group_file(group_file ? group_file : ".groups");
        if (logf) fflush(logf);
        fflush(errlogf);
        break;
      case SIGUSR2 :
        write_stat_file(stat_file ? stat_file : ".stats");
        break;
      case SIGPIPE :
        fprintf(stderr,"ftpd: SIGPIPE received. %i children left.\n",children);
        break;
      case SIGCLD :
        s = 0;
        do
        { pid = waitpid(-1,&e,WNOHANG);
          if (pid > 0)
          { if (s++) fprintf(stderr,"ftpd(%i): waitpid() waited for %i children\n",
              getpid(),s);
            if (pid == log_pid)
            { log_pid = 0;
              free(log_program);
              log_program = NULL;
              fclose(log_program_f);
            } else for (i=0, c=child; i<children; i++, c++) if (c->pid == pid)
            { child_logout(c,WEXITSTATUS(e));
              i = children;
            }
          }
        } while (pid > 0);
        break;
    }
    if (sig_read+1 >= sig_size) sig_read = 0; else sig_read++;
  }
}

int in_ipranges(long ip)
{ int            i;
  int            j;
  struct user_s* u = user;

  if (!private_mode) return 1;
  for (i=0; i<user_count; i++, u++) for (j=0; j<10; j++)
    if (in_iprange(ip,u->idents[j])) return 1;
  return 0;
}

int in_idents(long ip, char* ident)
{ int            i;
  int            j;
  struct user_s* u = user;

  if (!private_mode) return 1;
  if (!ident_mode) return in_ipranges(ip);
  for (i=0; i<user_count; i++, u++) for (j=0; j<10; j++)
    if (in_ident(ip,ident,u->idents[j])) return 1;
  return 0;
}


void swapreuid(void)
{ uid_t euid = geteuid();
  uid_t ruid = getuid();

  setreuid(euid,ruid);
}

void run_alternative(int cs)
{ pid_t pid;

  if (logf) fflush(logf);
  fflush(errlogf);
  pid = ix_vfork();
  switch (pid)
  { case -1 :
      perror("ftpd: fork");
      send_reply(cs,421,"Server error, service not available.");
      break;
    case 0 :
      seteuid(getuid());
      close(0);
      close(1);
      close(2);
      dup2(cs,0);
      dup2(cs,1);
      dup2(cs,2);
      close(cs);
      execl(alternative_srv,alternative_srv,NULL);
      perror("ftpd: execl");
      send_reply(1,421,"Server error, service not available (exec).");
      exit(-1);
    default :
      break;
  }
  close(cs);
}

void write_pidfile(void)
{ FILE* file;

  file = fopen(pid_file ? pid_file : "/var/run/ftpd.pid","wt");
  if (file)
  { fprintf(file,"%i\n",getpid());
    fclose(file);
  }
}

int main(int argc, char** argv, char** env)
{ struct sockaddr_in srv_addr;
  struct sockaddr_in rem_addr;
  struct sockaddr_in bouncer_ip;
  struct child_s*    child_new;
  int                cs;
  struct hostent*    hp;
  int                i;
  struct linger      li = { 0, 0 };
  char               name[32];
  pid_t              pid;
  int                pipefd1[2];
  int                pipefd2[2];
#ifndef HAVE_SIGSET
  struct sigaction   sig_act;
#endif
  int                ss;
  time_t             t;
  char               *idnt;
  unsigned long int oi;
  short              cn;
  int                reuse_addr = 1;
  char               bounced_idnt[256];
  char               bounced_user[10];
  struct sockaddr_in bounced_addr;
  int                use_bouncer = 0;

#ifndef HAVE_SIGSET
  sigfillset(&sig_act.sa_mask);
  sig_act.sa_flags = SA_RESTART;
  #define sigset(x,y) sig_act.sa_handler = y; sigaction(x,&sig_act,NULL)
#endif
  printf("FTP4ALL "VERSION", Copyright (C) 1996,1997 by Christoph Schwarz\n");
#ifdef DEBUG
  debugf = fopen("ftp4all-debug","wt");
  if (!debugf)
  { printf("cannot open debug file\n");
    return -1;
  }
  setvbuf(debugf,NULL,_IOLBF,0);
#endif
  if (argc == 2) cs = load_config(*(argv+1),0); else
  { if (argc == 1)
    { cs = load_config("ftpd.conf",0);
    } else
    { fprintf(stderr,"Syntax: ftpd [<configuration_file>]\n");
      return -1;
    }
  }
  if (cs == -1)
  { fprintf(stderr,"Error loading configuration file.\n");
    return -1;
  }
  ss = socket(PF_INET,SOCK_STREAM,0);
  if (ss == -1)
  { perror("ftpd: socket");
    return errno;
  }
  if (fcntl(ss,F_SETFD,1) < 0) perror("ftpd: fcntl");
  if (setsockopt(ss,SOL_SOCKET,SO_LINGER,(void *)&li,sizeof(li)) < 0)
    perror("ftpd: setsockopt SO_LINGER");
  if (setsockopt(ss,SOL_SOCKET,SO_REUSEADDR,(void *)&reuse_addr,sizeof(reuse_addr)) < 0)
    perror("ftpd: setsockopt SO_REUSEADDR");

  if (gethostname(name,sizeof(name)-1) == -1)
  { perror("ftpd: gethostname");
    return errno;
  }  

  if (other_ip == NULL) 
  { hp = gethostbyname(name); 
    if (!hp) 
    { 
      perror("ftpd: gethostbyname"); 
      return -1;
    }
  } else { oi = inet_addr(other_ip);
           hp = gethostbyaddr((char *)&oi,sizeof(oi),AF_INET);
           if (!hp) { perror("ftpd: gethostbyaddr");
                      return -1;
                    }
         }
  
  cn = 0;
  srv_addr.sin_family      = AF_INET;
  if (other_ip == NULL) { srv_addr.sin_addr.s_addr = htonl(INADDR_ANY); }
  else { srv_addr.sin_addr.s_addr = inet_addr(other_ip); }
  srv_addr.sin_port        = htons((u_short)port);
  bzero(srv_addr.sin_zero, sizeof srv_addr.sin_zero);
  while (port && bind(ss,(struct sockaddr*)&srv_addr,sizeof(srv_addr)) == -1)
  { if (errno == EADDRINUSE)
    { if (wait_port)
      { cn++;
        if (cn == 10) { printf("ftpd: unable to bind ftpd to a port.\n");return errno; }
        printf("Port is occupied, maybe another service/ftpd. I try to reuse it.\n");
        sleep(5);
      } else 
      { port++;
        srv_addr.sin_port = htons((u_short)port);
      }
    } else
    { perror("ftpd: bind");
      return errno;
    }
  }

  if (NULL != bouncer)
  { bouncer_ip.sin_addr.s_addr = inet_addr(bouncer);
    if (bouncer_ip.sin_addr.s_addr == -1 ) {
      printf("ftpd: error in bouncer IP-address\n");
      return errno;
    }
  }

  if (!srv_addr.sin_port)
  { printf("ftpd: unable to bind socket to a port\n");
    return errno;
  }
  pid = ix_vfork();
  if (pid == -1)
  { perror("ftpd: fork");
    return errno;
  }
  if (pid)
  { printf("Server on host %s (%s) is ready and listening on port %d\n",
      name,inet_ntoa(*(struct in_addr*)*hp->h_addr_list),port);
    print_configuration();
    return 0;
  }
  write_pidfile();
  sigset(SIGHUP,signal_handler);
  sigset(SIGINT,signal_handler);
  sigset(SIGQUIT,signal_handler);
  sigset(SIGCLD,signal_handler);
  sigset(SIGPIPE,signal_handler);
  sigset(SIGTERM,signal_handler);
  sigset(SIGUSR1,signal_handler);
  sigset(SIGUSR2,signal_handler);
  listen(ss,5);
  time(&t);
  strftime(name,sizeof(name),"%a %b %d, %Y %H:%M:%S",localtime(&t));
  fprintf(stderr,"ftpd(%i): %s - ftp server started\n",getpid(),name);
  while (1)
  { handle_signals(ss);
    FD_ZERO(&fdset);
    FD_SET(ss,&fdset);
    if (ss2 != -1) FD_SET(ss2,&fdset);
    child_new = child;
    for (i=0; i<children; i++)
    { FD_SET(fileno(child_new->rdp),&fdset);
      child_new++;
    }
#ifdef DEBUG
  fprintf(debugf,"calling select ... ");
#endif
    cs = select(FD_SETSIZE,&fdset,NULL,NULL,NULL);
#ifdef DEBUG
  fprintf(debugf,"select returned %i\n",cs);
#endif
    if (cs == -1)
    { if (errno == EINTR) handle_signals(ss); else
      { perror("ftpd: select");
        raise(SIGTERM);
      }
    } else if (!FD_ISSET(ss,&fdset) && ((ss2 == -1) || !FD_ISSET(ss2,&fdset)))
    { cs = -1;
      child_new = child;
      i = 0;
      while (i++ < children)
      { if (FD_ISSET(fileno(child_new->rdp),&fdset))
        { cs = fileno(child_new->rdp);
          i = children;
        } else child_new++;
      }
      if (cs != -1) child_communication(child_new);
    } else
    {
#ifdef DEBUG
  fprintf(debugf,"creating new child\n");
#endif
      i = sizeof(rem_addr);
      cs = accept(FD_ISSET(ss,&fdset) ? ss : ss2,(struct sockaddr*)&rem_addr,&i);
      if (-1 != cs) {
        if (setsockopt(cs,SOL_SOCKET,SO_LINGER,(void *)&li,sizeof(li)) < 0)
          perror("ftpd: setsockopt SO_LINGER");
        if (setsockopt(cs,SOL_SOCKET,SO_REUSEADDR,(void *)&reuse_addr,sizeof(reuse_addr)) < 0)  
          perror("ftpd: setsockopt SO_REUSEADDR");

        if (ident_mode) {
          idnt=(char*)rfc931(&rem_addr, &srv_addr);
          if (strlen(idnt)>11) idnt[12]='\0';
        } else {
          idnt="UNKNOWN";
        }
        if (NULL != bouncer)
        {
          if (rem_addr.sin_addr.s_addr == bouncer_ip.sin_addr.s_addr)
          {
            read(cs, bounced_idnt, sizeof bounced_idnt);
            bounced_addr.sin_addr.s_addr = inet_addr(strstr(bounced_idnt, "@")+1);
            strtok(bounced_idnt, "@");
            strcpy(bounced_user, strchr(bounced_idnt, ' ')+1);
            use_bouncer = 1;
          } else
          { hp = gethostbyaddr((char*)&rem_addr.sin_addr,sizeof(rem_addr.sin_addr), AF_INET);
            use_bouncer = 0;
          }
        }
        if (((1 == use_bouncer) && (NULL != bouncer) && (in_idents(ntohl(bounced_addr.sin_addr.s_addr), bounced_user))) || ((NULL == bouncer) && (in_idents(ntohl(rem_addr.sin_addr.s_addr),idnt))))
        { if (children >= child_size)
          { child_size += 16;
            child_new = (struct child_s*)alloc(child,
              child_size*sizeof(struct child_s));
          } else child_new = child;
          if (child_new)
          { child = child_new;
            child_new = child+children;
            i = pipe(pipefd1);
            if (i || pipe(pipefd2))
            { perror("pipe");
              send_reply(cs,421,"Server error, service not available.");
              close(cs);
              cs = -1;
              if (!i)
              { close(pipefd1[0]);
                close(pipefd1[1]);
              }
              fprintf(stderr,"pipe error, i have %i children, list follows:\n",children);
              child_new = child;
              for (i=0; i<children; i++, child_new++)
              { fprintf(stderr,"child %2i, user=%-9s, socket=%3i, readp=%3i, writep=%3i\n",
                  i,child_new->user ? child_new->user->name : "UNKNOWN",
                  child_new->sock,fileno(child_new->rdp),fileno(child_new->wrp));
              }
            }
          } else
          { child_size -= 16;
            perror("ftpd: no memory for child");
            send_reply(cs,421,"Server error, service not available.");
            close(cs);
            cs = -1;
          }
          if (cs != -1)
          { if (logf) fflush(logf);
            fflush(errlogf);
            pid = ix_vfork();
            switch (pid)
            { case -1 :
                perror("ftpd: fork");
                send_reply(cs,421,"Server error, service not available.");
                close(pipefd1[0]);
                close(pipefd1[1]);
                close(pipefd2[0]);
                close(pipefd2[1]);
                break;
              case 0 :
                setreuid(geteuid(),-1);
                close(pipefd1[0]);
                close(pipefd2[1]);
                dup2(pipefd1[1],1);
                dup2(pipefd2[0],0);
                close(pipefd1[1]);
                close(pipefd2[0]);
                if (program_name) execl(server_program,program_name,NULL);
                  else execl(server_program,server_program,NULL);
                perror("ftpd: execl");
                send_reply(cs,421,"Server error, service not available (exec).");
                return -1;
              default :
                close(cs);
                close(pipefd1[1]);
                close(pipefd2[0]);
                if (fcntl(pipefd1[0],F_SETFD,1) < 0) perror("ftpd: fcntl");
                if (fcntl(pipefd2[1],F_SETFD,1) < 0) perror("ftpd: fcntl");
                child_new->rdp  = fdopen(pipefd1[0],"rt");
                child_new->wrp  = fdopen(pipefd2[1],"wt");
                child_new->sock = cs;
                child_new->pid  = pid;
                child_new->user = NULL;
                if (1 == use_bouncer)
                { child_new->ip = ntohl(bounced_addr.sin_addr.s_addr);
                  if (0 == strcmp(bounced_user, "unknown"))
                    strncpy(child_new->ident, "UNKNOWN", 7);
                  else memcpy(child_new->ident,bounced_user,strlen(bounced_user)+1);
                } else
                { child_new->ip = ntohl(rem_addr.sin_addr.s_addr);
                  memcpy(child_new->ident,idnt,strlen(idnt)+1);
                }
                strcpy(child_new->activity,"LOGIN");
                time(&child_new->act_time);
                child_new->idle = 1;
                child_new->kb_down = child_new->kb_up = 0;
                children++;
                break;
            }
            close(cs);
          }
        } else
        { hp = gethostbyaddr((char*)&rem_addr.sin_addr,sizeof(rem_addr.sin_addr),
            AF_INET);
          server_log_local(NULL,LG_LOGIN,"UNAUTHORIZED CONNECTION from %s@%s (%s)",
            idnt, inet_ntoa(rem_addr.sin_addr),hp ? hp->h_name : "unknown host");
          if (alternative_srv) run_alternative(cs);  else close(cs);
        }
      }
    }
  }
}
/* EOF */
