/*****************************************************************************/
/*  ftp.c - contains most of the core network routines                       */
/*  Copyright (C) 1998 Brian Masney <masneyb@newwave.net>                    */
/*                                                                           */
/*  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.                                      */
/*                                                                           */
/*  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 "ftp.h"

int ftp_connect(struct ftp_window_data *wdata, struct ftp_host_data *hdata, struct ftp_transfer_data *tdata) {
   struct sockaddr_in servaddr;
   struct servent *service;
   struct hostent *host;
   char tempstr[MAXSTR], resp;
   int conn, port, start, ok;

   if((hdata->sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
      snprintf(tempstr, sizeof(tempstr), "Error: Failed to create a socket: %s\n", sys_errlist[errno]);
      tempstr[sizeof(tempstr)-1] = '\0';
      if(tdata == NULL) ftp_log(LOG_MISC, tempstr);
      else queue_log(LOG_MISC, tempstr);
      return(0);
   }

   snprintf(tempstr, sizeof(tempstr), "Lookup up %s\n", hdata->host);
   tempstr[sizeof(tempstr)-1] = '\0';
   if(tdata == NULL) ftp_log(LOG_MISC, tempstr);
   else strncpy(tdata->progressstr, tempstr, sizeof(tdata->progressstr));
   
   memset(&servaddr, 0, sizeof(servaddr));
   servaddr.sin_family = AF_INET;
   if(hdata->port != 0) port = htons(hdata->port);
   else {
      if((service = getservbyname("ftp", "tcp")) == NULL) {
         snprintf(tempstr, sizeof(tempstr), "Warning: Could not lookup default ftp port...using 21\n");
         tempstr[sizeof(tempstr)-1] = '\0';
         if(tdata == NULL) ftp_log(LOG_MISC, tempstr);
         else queue_log(LOG_MISC, tempstr);
         port = htons(21);
      }
      else port = service->s_port;
   }
   servaddr.sin_port = port;
   
   if(!(host = gethostbyname(hdata->host))) {
      snprintf(tempstr, sizeof(tempstr), "Error: Cannot find host %s\n", hdata->host, sys_errlist[errno]);
      tempstr[sizeof(tempstr)-1] = '\0';
      if(tdata == NULL) ftp_log(LOG_MISC, tempstr);
      else queue_log(LOG_MISC, tempstr);
      return(0);
   }    
   memcpy(&servaddr.sin_addr, host->h_addr, host->h_length);
   strncpy(hdata->host, host->h_name, sizeof(hdata->host));
   
   snprintf(tempstr, sizeof(tempstr), "Trying %s:%d\n", hdata->host, ntohs(port));
   tempstr[sizeof(tempstr)-1] = '\0';
   if(tdata == NULL) ftp_log(LOG_MISC, tempstr);
   else strncpy(tdata->progressstr, tempstr, sizeof(tdata->progressstr));
   
   if((conn = connect(hdata->sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))) < 0) {
      snprintf(tempstr, sizeof(tempstr), "Error: Cannot connect to %s: %s\n", hdata->host, sys_errlist[errno]);
      tempstr[sizeof(tempstr)-1] = '\0';
      if(tdata == NULL) ftp_log(LOG_MISC, tempstr);
      else queue_log(LOG_MISC, tempstr);
      return(0);
   }

   snprintf(tempstr, sizeof(tempstr), "Connected to %s:%d\n", hdata->host, ntohs(port));
   tempstr[sizeof(tempstr)-1] = '\0';
   if(tdata == NULL) ftp_log(LOG_MISC, tempstr);
   else strncpy(tdata->progressstr, tempstr, sizeof(tdata->progressstr));

   start = 1;
   strncpy(tempstr, "   -", sizeof(tempstr));
   tempstr[sizeof(tempstr)-1] = '\0';
   while(strlen(tempstr) > 3 && (tempstr[0] == ' ' || tempstr[3] == '-') &&
      ftp_read_line(hdata->sockfd, (char *) &tempstr, sizeof(tempstr)) > 0) {

      if(tdata == NULL) {
         ftp_log(LOG_RECV, "%s\n", tempstr);
      };
      if(start) {
         if(strncmp(tempstr, "220", 3) != 0) {
            snprintf(tempstr, sizeof(tempstr), "Error: %s:%d is not a valid FTP server\n", hdata->host, ntohs(port));
            tempstr[sizeof(tempstr)-1] = '\0';
            if(tdata == NULL) ftp_log(LOG_MISC, tempstr);
            else queue_log(LOG_MISC, tempstr);
            ftp_disconnect(wdata, hdata, tdata == NULL);
            return(0);
         }
         start = 0;
      }
   }

   /* Logging into FTP server */
   ok = 0;
   resp = ftp_sendcommand(tdata == NULL, wdata, hdata, NULL, 0, "USER %s\r\n", hdata->user);
   if(tdata != NULL) strncpy(tdata->progressstr, "Logging in", sizeof(tdata->progressstr));
   if(resp == '2') ok = 1;
   else if(resp == '3') {
      if(ftp_sendcommand(tdata == NULL, wdata, hdata, NULL, 0, "PASS %s\r\n", hdata->passwd) == '2') ok = 1;
   }
   if(!ok) {
      snprintf(tempstr, sizeof(tempstr), "Error: Bad username/password\n");
      tempstr[sizeof(tempstr)-1] = '\0';
      if(tdata == NULL) ftp_log(LOG_MISC, tempstr);
      else queue_log(LOG_MISC, tempstr);
      ftp_disconnect(wdata, hdata, tdata == NULL);
      return(0);
   }
   if(hdata->dir[0] != '\0') ftp_chdir(wdata, hdata, hdata->dir);
   return(1);
}   
/*****************************************************************************/
int ftp_init_data_conn(struct ftp_window_data *wdata, struct ftp_host_data *hdata) {
   struct sockaddr_in data_addr;
   char tempstr[MAXSTR], *pos;
   unsigned long temp[6];
   unsigned char ad[6];
   int datafd, i;

   if(ftp_sendcommand(0, wdata, hdata, tempstr, sizeof(tempstr), "PASV\r\n") != '2') {
      if(wdata->host == hdata) {
         ftp_log(LOG_MISC, "Error: Bad response from FTP server %s\n", hdata->host);
         ftp_disconnect(wdata, hdata, TRUE);
      }
      return(0);
   }
   pos = tempstr;
   pos = pos+4;
   while(!isdigit(*pos) && *pos != '\0') pos++;
   if(*pos == '\0') {
      if(wdata->host == hdata) {
         ftp_log(LOG_MISC, "Error: Bad response from FTP server %s\n", hdata->host);
         ftp_disconnect(wdata, hdata, TRUE);
      }
      return(0);
   }
   if(sscanf(pos, "%ld,%ld,%ld,%ld,%ld,%ld", &temp[0], &temp[1], &temp[2], &temp[3], &temp[4], &temp[5]) != 6) {
      if(wdata->host == hdata) {
         ftp_log(LOG_MISC, "Error: Bad response from FTP server %s\n", hdata->host);
         ftp_disconnect(wdata, hdata, TRUE);
      }
      return(0);
   }
   for(i=0; i<6; i++) ad[i] = (unsigned char) (temp[i] & 0xff);

   if((datafd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
      return(0);
   }
   
   memset(&data_addr, 0, sizeof(data_addr));
   data_addr.sin_family = AF_INET;
   memcpy(&data_addr.sin_addr, &ad[0], 4);
   memcpy(&data_addr.sin_port, &ad[4], 2);
   if(connect(datafd, (struct sockaddr *) &data_addr, sizeof(data_addr)) < 0) {
      if(wdata->host == hdata) {
         ftp_log(LOG_MISC, "Error: Cannot create data connection to %s: %s\n", hdata->host, sys_errlist[errno]);
         ftp_disconnect(wdata, hdata, TRUE);
      }
      return(0);
   }
   return(datafd);
}
/*****************************************************************************/
int ftp_disconnect(struct ftp_window_data *wdata, struct ftp_host_data *hdata, int log) {
   struct ftp_file_data *tempfle;
   
   if(wdata->host == hdata) {
      delete_ftp_file_info(wdata);
      wdata->local = -1;
      update_ftp_info(wdata);
   }
   else {
      while(hdata->files != NULL) {
         tempfle = hdata->files;
         hdata->files = hdata->files->next;
         free(tempfle);
      }
   }
   if(ftp_sendcommand(log, wdata, hdata, NULL, 0, "QUIT\r\n") != '2') {
      if(log) {
         ftp_log(LOG_MISC, "Error: Bad response from FTP server %s\n", hdata->host);
      }
      return(0);
   }
   close(hdata->sockfd);
   return(1);
}
/*****************************************************************************/
int ftp_list_files(struct ftp_window_data *wdata, struct ftp_host_data *hdata) {
   char tempstr[MAXSTR], logstr[MAXSTR], commastr[20];
   struct ftp_file_data *fle; 
   int datafd, got, isdotdot;
   size_t dltotal;
   time_t lasttime;
   int i;
   
   wdata->totalitems = wdata->numselected = 0;
   wdata->local = 0;
   isdotdot = 0;
   gtk_clist_freeze(GTK_CLIST(wdata->listbox));
   if((datafd = ftp_init_data_conn(wdata, hdata)) < 1) return(datafd);
   if(ftp_sendcommand(1, wdata, hdata, NULL, 0, "LIST\r\n") != '1') {
      ftp_log(LOG_MISC, "Error: Cannot get directory listing\n");
      ftp_disconnect(wdata, hdata, 1);
      return(0);
   }
   lasttime = time(NULL);
   dltotal = 0;
   while((got = ftp_read_line(datafd, (char *) &tempstr, sizeof(tempstr))) > 0) {
      dltotal += got;
      if(time(NULL)-lasttime >= 1) {
         insert_commas(dltotal, commastr, sizeof(commastr));
         snprintf(logstr, sizeof(logstr), "Retrieving file names...%s bytes", commastr);
         logstr[sizeof(logstr)-1] = '\0';
         gtk_label_set(GTK_LABEL(wdata->hoststxt), logstr);
         fix_display();
         lasttime = time(NULL);
      }
/* FIXME: Screen updating */
      fle = mymalloc(sizeof(struct ftp_file_data));
      if(ftp_parse_file_listing(fle, tempstr) && strcmp(fle->file, ".") != 0) {
         if(strcmp(fle->file, "..") == 0) isdotdot = 1;
         fle->isdir = strchr(fle->attribs, 'd') != NULL;
         fle->isexe = (strchr(fle->attribs, 'x') != NULL) && !fle->isdir;
         fle->islink = strchr(fle->attribs, 'l') != NULL;
         add_file_listbox(wdata, fle);
         fle->next = NULL;
         if(hdata->last == NULL) hdata->files = fle;
         else hdata->last->next = fle;
         hdata->last = fle;
         wdata->totalitems++;
      }
      else free(fle);                                    
   }
   close(datafd);
   if(!isdotdot) {
      fle = mymalloc(sizeof(struct ftp_file_data));
      strcpy(fle->file, "..");
      fle->size = 0;
      fle->user[0] = fle->group[0] = fle->attribs[0] = fle->dt[0] = '\0';
      fle->next = NULL;
      if(hdata->last == NULL) hdata->files = fle;
      else hdata->last->next = fle;
      add_file_listbox(wdata, fle);
      wdata->totalitems++;
   }
   if(wdata->items) wdata->items = myrealloc(wdata->items, sizeof(int)*wdata->totalitems);
   else wdata->items = mymalloc(sizeof(int)*wdata->totalitems);
   for(i=0; i<wdata->totalitems; i++) wdata->items[i] = 0;

   if(ftp_read_response(1, wdata, hdata, NULL, 0) != '2') {
      ftp_log(LOG_MISC, "Error: Bad response from FTP server\n");
      ftp_disconnect(wdata, hdata, TRUE);
      return(0);
   }
   ftp_pwd(wdata, hdata, wdata->host->dir, sizeof(wdata->host->dir), 1);
   update_ftp_info(wdata);
   sortrows(GTK_CLIST(wdata->listbox), wdata->sortcol, (gpointer) wdata);
   gtk_clist_thaw(GTK_CLIST(wdata->listbox));
   gtk_label_set(GTK_LABEL(wdata->hoststxt), wdata->host->host);
   return(1); 
}
/*****************************************************************************/
char *ftp_pwd(struct ftp_window_data *wdata, struct ftp_host_data *hdata, char *dir, int len, int log) {
   char *pos;
      
   if(ftp_sendcommand(log, wdata, hdata, dir, len, "PWD\r\n") != '2') {
      ftp_log(LOG_MISC, "Error: Cannot get current directory\n;");
      ftp_disconnect(wdata, hdata, TRUE);
      return(0);
   }
   pos = strchr(dir, '"');
   if(pos != NULL) dir = pos+1;
   else *dir = '\0';

   pos = strchr(dir, '"');
   if(pos != NULL) *pos = '\0';
   else *dir = '\0';

   strncpy(hdata->dir, dir, sizeof(hdata->dir));
   return(dir);
}
/*****************************************************************************/
int ftp_read_line(int sockfd, char *buf, int size) {
   int ret, i;

   for(i=0; i<size; i++) {
      if((ret = read(sockfd, &buf[i], 1)) == 1) {
         if(buf[i] == '\n') break;
      }
      else {
         if(i == 0) return(0);
         else break;
      }
   }
   buf[i] = '\0';
   if(i > 0 && buf[i-1] == '\r') buf[i-1] = '\0';
   return(i);
}
/*****************************************************************************/
char ftp_sendcommand(int log, struct ftp_window_data *wdata, struct ftp_host_data *hdata, char *destbuf, size_t destlen, char *command, ...) {
   char tempstr[MAXSTR];
   va_list argp;
   
   va_start(argp, command);
   vsnprintf(tempstr, sizeof(tempstr), command, argp);
   tempstr[sizeof(tempstr)-1] = '\0';
#ifdef DEBUG
   printf("(%d) -> %s", log, tempstr);
#endif
   if(log) {
      if(strncmp(tempstr, "PASS", 4) == 0) {
         ftp_log(LOG_SEND, "PASS xxxx\n");
      }
      else {
         if(strlen(tempstr) > 1 && tempstr[strlen(tempstr)-2] == '\r') {
            tempstr[strlen(tempstr)-2] = '\n';
            tempstr[strlen(tempstr)-1] = '\0';
         }
         ftp_log(LOG_SEND, "%s", tempstr);
      }
   }
   if(write(hdata->sockfd, tempstr, strlen(tempstr)) == -1) {
      ftp_log(LOG_MISC, "Error: Cannot write to socket: %s\n", sys_errlist[errno]);
      ftp_disconnect(wdata, hdata, 1);
      return(0);
   }
   return(ftp_read_response(log, wdata, hdata, destbuf, destlen));
}
/*****************************************************************************/
char ftp_read_response(int log, struct ftp_window_data *wdata, struct ftp_host_data *hdata, char *buf, size_t len) {
   char tempstr[MAXSTR];
   
   strncpy(tempstr, "   -", sizeof(tempstr));
   while(strlen(tempstr) > 3 && (tempstr[0] == ' '|| tempstr[3] == '-') &&
      ftp_read_line(hdata->sockfd, (char *) &tempstr, sizeof(tempstr)) > 0) {

#ifdef DEBUG
      printf("(%d) <- %s\n", log, tempstr);
#endif
      if(log) ftp_log(LOG_RECV, "%s\n", tempstr);
   }
   if(buf != NULL) strncpy(buf, tempstr, len);
   return(tempstr[0]);
}   
/*****************************************************************************/
int ftp_chdir(struct ftp_window_data *wdata, struct ftp_host_data *hdata, char *newdir) {
   if(strcmp(newdir, "..") == 0) {
      if(ftp_sendcommand(wdata->host == hdata, wdata, hdata, NULL, 0, "CDUP\r\n") != '2') return(0);
   }
   else {
      if(ftp_sendcommand(wdata->host == hdata, wdata, hdata, NULL, 0, "CWD %s\r\n", newdir) != '2') return(0);
   }
   return(1);
}
/*****************************************************************************/
