view lib/protocols.c @ 952:a490d94a5b8e

2008-03-28 Brian Masney <masneyb@gftp.org> * lib/Makefile.am lib/misc.c lib/socket-connect.c lib/socket-connect-getaddrinfo.c lib/socket-connect-gethostbyname.c lib/sockutils.c lib/gftp.h - cleaned up more of the socket functions and split them up into their own files. Cleanups and bug fixes to the DNS lookup code.
author masneyb
date Fri, 28 Mar 2008 11:44:36 +0000
parents c7d7a081cd9c
children 63555c9744c2
line wrap: on
line source

/*****************************************************************************/
/*  protocols.c - Skeleton functions for the protocols gftp supports         */
/*  Copyright (C) 1998-2007 Brian Masney <masneyb@gftp.org>                  */
/*                                                                           */
/*  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., 59 Temple Place - Suite 330, Boston, MA 02111 USA      */
/*****************************************************************************/

#include "gftp.h"
static const char cvsid[] = "$Id$";

gftp_request *
gftp_request_new (void)
{
  gftp_request *request;

  request = g_malloc0 (sizeof (*request));
  request->datafd = -1;
  request->cachefd = -1;
  request->server_type = GFTP_DIRTYPE_OTHER;
  return (request);
}


void
gftp_request_destroy (gftp_request * request, int free_request)
{
  g_return_if_fail (request != NULL);

  gftp_disconnect (request);

  if (request->destroy != NULL)
    request->destroy (request);

  if (request->hostname)
    g_free (request->hostname);
  if (request->username)
    g_free (request->username);
  if (request->password)
    g_free (request->password);
  if (request->account)
    g_free (request->account);
  if (request->directory)
    g_free (request->directory);
  if (request->last_ftp_response)
    g_free (request->last_ftp_response);
  if (request->protocol_data)
    g_free (request->protocol_data);

#if defined (HAVE_GETADDRINFO) && defined (HAVE_GAI_STRERROR)
  if (request->remote_addr != NULL)
    g_free (request->remote_addr);
#endif

  if (request->local_options_vars != NULL)
    {
      gftp_config_free_options (request->local_options_vars,
                                request->local_options_hash,
                                request->num_local_options_vars);
    }

  memset (request, 0, sizeof (*request));

  if (free_request)
    g_free (request);
  else
    {
      request->datafd = -1;
      request->cachefd = -1;
      request->server_type = GFTP_DIRTYPE_OTHER;
    }
}


/* This function is called to copy protocol specific data from one request 
   structure to another. This is typically called when a file transfer is
   completed, state information can be copied back to the main window */
void
gftp_copy_param_options (gftp_request * dest_request,
                         gftp_request * src_request)
{
  g_return_if_fail (dest_request != NULL);
  g_return_if_fail (src_request != NULL);
  g_return_if_fail (dest_request->protonum == src_request->protonum);

  if (dest_request->copy_param_options)
    dest_request->copy_param_options (dest_request, src_request);
}


void
gftp_file_destroy (gftp_file * file, int free_it)
{
  g_return_if_fail (file != NULL);

  if (file->file)
    g_free (file->file);
  if (file->user)
    g_free (file->user);
  if (file->group)
    g_free (file->group);
  if (file->destfile)
    g_free (file->destfile);

  if (free_it)
    g_free (file);
  else
    memset (file, 0, sizeof (*file));
}


int
gftp_connect (gftp_request * request)
{
  int ret;

  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  if (request->connect == NULL)
    return (GFTP_EFATAL);

  if ((ret = gftp_set_config_options (request)) < 0)
    return (ret);

  return (request->connect (request));
}


void
gftp_disconnect (gftp_request * request)
{
  g_return_if_fail (request != NULL);

#ifdef USE_SSL
  if (request->ssl != NULL)
    {
      SSL_free (request->ssl);
      request->ssl = NULL;
    }
#endif

#if GLIB_MAJOR_VERSION > 1
  if (request->iconv_from_initialized)
    {
      g_iconv_close (request->iconv_from);
      request->iconv_from_initialized = 0;
    }

  if (request->iconv_to_initialized)
    {
      g_iconv_close (request->iconv_to);
      request->iconv_to_initialized = 0;
    }

  if (request->iconv_charset)
  {
    g_free (request->iconv_charset);
    request->iconv_charset = NULL;
  }
#endif

  request->cached = 0;
  if (request->disconnect == NULL)
    return;
  request->disconnect (request);
}


off_t
gftp_get_file (gftp_request * request, const char *filename,
               off_t startsize)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  request->cached = 0;
  if (request->get_file == NULL)
    return (GFTP_EFATAL);

  return (request->get_file (request, filename, startsize));
}


int
gftp_put_file (gftp_request * request, const char *filename,
               off_t startsize, off_t totalsize)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  request->cached = 0;
  if (request->put_file == NULL)
    return (GFTP_EFATAL);

  return (request->put_file (request, filename, startsize, totalsize));
}


off_t
gftp_transfer_file (gftp_request * fromreq, const char *fromfile, 
                    off_t fromsize, gftp_request * toreq, const char *tofile,
                    off_t tosize)
{
  /* Needed for systems that size(float) < size(void *) */
  union { intptr_t i; float f; } maxkbs;
  off_t size;
  int ret;

  g_return_val_if_fail (fromreq != NULL, GFTP_EFATAL);
  g_return_val_if_fail (fromfile != NULL, GFTP_EFATAL);
  g_return_val_if_fail (toreq != NULL, GFTP_EFATAL);
  g_return_val_if_fail (tofile != NULL, GFTP_EFATAL);

  gftp_lookup_request_option (toreq, "maxkbs", &maxkbs.f);

  if (maxkbs.f > 0)
    {
      toreq->logging_function (gftp_logging_misc, toreq,
                    _("File transfer will be throttled to %.2f KB/s\n"), 
                    maxkbs.f);
    }

  if (fromreq->protonum == toreq->protonum &&
      fromreq->transfer_file != NULL)
    return (fromreq->transfer_file (fromreq, fromfile, fromsize, toreq, 
                                    tofile, tosize));

  fromreq->cached = 0;
  toreq->cached = 0;

get_file:
  size = gftp_get_file (fromreq, fromfile, tosize);
  if (size < 0)
    {
      if (size == GFTP_ETIMEDOUT)
        {
          ret = gftp_connect (fromreq);
          if (ret < 0)
            return (ret);

          goto get_file;
        }

      return (size);
    }

put_file:
  ret = gftp_put_file (toreq, tofile, tosize, size);
  if (ret != 0)
    {
      if (size == GFTP_ETIMEDOUT)
        {
          ret = gftp_connect (fromreq);
          if (ret < 0)
            return (ret);

          goto put_file;
        }

      if (gftp_abort_transfer (fromreq) != 0)
        gftp_end_transfer (fromreq);

      return (ret);
    }

  return (size);
}


ssize_t 
gftp_get_next_file_chunk (gftp_request * request, char *buf, size_t size)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);
  g_return_val_if_fail (buf != NULL, GFTP_EFATAL);

  if (request->get_next_file_chunk != NULL)
    return (request->get_next_file_chunk (request, buf, size));

  return (request->read_function (request, buf, size, request->datafd));
}


ssize_t 
gftp_put_next_file_chunk (gftp_request * request, char *buf, size_t size)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);
  g_return_val_if_fail (buf != NULL, GFTP_EFATAL);

  if (request->put_next_file_chunk != NULL)
    return (request->put_next_file_chunk (request, buf, size));

  return (request->write_function (request, buf, size, request->datafd));
}


int
gftp_end_transfer (gftp_request * request)
{
  int ret;

  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  if (!request->cached && 
      request->end_transfer != NULL)
    ret = request->end_transfer (request);
  else
    ret = 0;

  if (request->cachefd > 0)
    {
      close (request->cachefd);
      request->cachefd = -1;
    }

  if (request->last_dir_entry)
    {
      g_free (request->last_dir_entry);
      request->last_dir_entry = NULL;
      request->last_dir_entry_len = 0;
    }

  return (ret);
}


int
gftp_abort_transfer (gftp_request * request)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  if (request->abort_transfer == NULL)
    return (GFTP_EFATAL);

  /* FIXME - end the transfer if it is not successful */
  return (request->abort_transfer (request));
}


int
gftp_stat_filename (gftp_request * request, const char *filename, mode_t * mode,
                    off_t * filesize)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);
  g_return_val_if_fail (filename != NULL, GFTP_EFATAL);

  if (request->stat_filename != NULL)
    return (request->stat_filename (request, filename, mode, filesize));
  else
    return (0);
}


int
gftp_list_files (gftp_request * request)
{
  char *remote_lc_time, *locret;
  int fd;

  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

#if ENABLE_NLS
  gftp_lookup_request_option (request, "remote_lc_time", &remote_lc_time);
  if (remote_lc_time != NULL && *remote_lc_time != '\0')
    locret = setlocale (LC_TIME, remote_lc_time);
  else
    locret = setlocale (LC_TIME, NULL);

  if (locret == NULL)
    {
      locret = setlocale (LC_TIME, NULL);
      request->logging_function (gftp_logging_error, request,
                                 _("Error setting LC_TIME to '%s'. Falling back to '%s'\n"),
                                 remote_lc_time, locret);
    }
#else
  locret = _("<unknown>");
#endif

  request->cached = 0;
  if (request->use_cache && (fd = gftp_find_cache_entry (request)) > 0)
    {
      request->logging_function (gftp_logging_misc, request,
                                 _("Loading directory listing %s from cache (LC_TIME=%s)\n"),
                                 request->directory, locret);

      request->cachefd = fd;
      request->cached = 1;
      return (0);
    }
  else if (request->use_cache)
    {
      request->logging_function (gftp_logging_misc, request,
                                 _("Loading directory listing %s from server (LC_TIME=%s)\n"),
                                 request->directory, locret);

      request->cachefd = gftp_new_cache_entry (request);
      request->cached = 0; 
    }

  if (request->list_files == NULL)
    return (GFTP_EFATAL);

  return (request->list_files (request));
}


int
gftp_get_next_file (gftp_request * request, const char *filespec,
                    gftp_file * fle)
{
  char *slashpos, *tmpfile, *utf8;
  size_t destlen;
  int fd, ret;

  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  if (request->get_next_file == NULL)
    return (GFTP_EFATAL);

  if (request->cached && request->cachefd > 0)
    fd = request->cachefd;
  else
    fd = request->datafd;

  memset (fle, 0, sizeof (*fle));
  do
    {
      gftp_file_destroy (fle, 0);
      ret = request->get_next_file (request, fle, fd);
      if (fle->file != NULL && (slashpos = strrchr (fle->file, '/')) != NULL)
        {
          if (*(slashpos + 1) == '\0')
            {
              gftp_file_destroy (fle, 0);
              continue;
            }

          *slashpos = '\0';
          tmpfile = g_strdup (slashpos + 1);

          if (strcmp (fle->file, request->directory) != 0)
            request->logging_function (gftp_logging_error, request,
                                       _("Warning: Stripping path off of file '%s'. The stripped path (%s) doesn't match the current directory (%s)\n"),
                                       tmpfile, fle->file, request->directory,
                                       g_strerror (errno));
          
          g_free (fle->file);
          fle->file = tmpfile;
        }

      if (ret >= 0 && fle->file != NULL)
        {
          if (g_utf8_validate (fle->file, -1, NULL))
            fle->filename_utf8_encoded = 1;
          else
            {
              utf8 = gftp_filename_to_utf8 (request, fle->file, &destlen);
              if (utf8 != NULL)
                {
                  g_free (fle->file);
                  fle->file = utf8;
                  fle->filename_utf8_encoded = 1;
                }
            }
        }

      if (ret >= 0 && !request->cached && request->cachefd > 0 && 
          request->last_dir_entry != NULL)
        {
          if (gftp_fd_write (request, request->last_dir_entry,
                          request->last_dir_entry_len, request->cachefd) < 0)
            {
              request->logging_function (gftp_logging_error, request,
                                        _("Error: Cannot write to cache: %s\n"),
                                        g_strerror (errno));
              close (request->cachefd);
              request->cachefd = -1;
            }
        }
    } while (ret > 0 && !gftp_match_filespec (request, fle->file, filespec));

  return (ret);
}


int
gftp_parse_bookmark (gftp_request * request, gftp_request * local_request, 
                     const char * bookmark, int *refresh_local)
{
  gftp_logging_func logging_function;
  gftp_bookmarks_var * tempentry;
  char *default_protocol;
  const char *email;
  int i, init_ret;

  g_return_val_if_fail (request != NULL, GFTP_EFATAL);
  g_return_val_if_fail (bookmark != NULL, GFTP_EFATAL);
  
  logging_function = request->logging_function;
  gftp_request_destroy (request, 0);
  request->logging_function = logging_function;

  if ((tempentry = g_hash_table_lookup (gftp_bookmarks_htable, 
                                        bookmark)) == NULL)
    {
      request->logging_function (gftp_logging_error, request,
                                 _("Error: Could not find bookmark %s\n"), 
                                 bookmark);
      return (GFTP_EFATAL);
    }
  else if (tempentry->hostname == NULL || *tempentry->hostname == '\0')
    {
      request->logging_function (gftp_logging_error, request,
                                 _("Bookmarks Error: The bookmark entry %s does not have a hostname\n"), bookmark);
      return (GFTP_EFATAL);
    }

  if (tempentry->user != NULL)
    gftp_set_username (request, tempentry->user);

  if (tempentry->pass != NULL)
    {
      if (strcmp (tempentry->pass, "@EMAIL@") == 0)
        {
          gftp_lookup_request_option (request, "email", &email);
          gftp_set_password (request, email);
        }
      else
        gftp_set_password (request, tempentry->pass);
    }

  if (tempentry->acct != NULL)
    gftp_set_account (request, tempentry->acct);

  gftp_set_hostname (request, tempentry->hostname);
  gftp_set_directory (request, tempentry->remote_dir);
  gftp_set_port (request, tempentry->port);

  if (local_request != NULL && tempentry->local_dir != NULL &&
      *tempentry->local_dir != '\0')
    {
      gftp_set_directory (local_request, tempentry->local_dir);
      if (refresh_local != NULL)
        *refresh_local = 1;
    }
  else if (refresh_local != NULL)
    *refresh_local = 0;

  for (i = 0; gftp_protocols[i].name; i++)
    {
      if (strcmp (gftp_protocols[i].name, tempentry->protocol) == 0)
        {
          if ((init_ret = gftp_protocols[i].init (request)) < 0)
            {
              gftp_request_destroy (request, 0);
              return (init_ret);
            }
          break;
        }
    }

  if (gftp_protocols[i].name == NULL)
    {
      gftp_lookup_request_option (request, "default_protocol", 
                                  &default_protocol);

      if (default_protocol != NULL && *default_protocol != '\0')
        {
          for (i = 0; gftp_protocols[i].url_prefix; i++)
            {
              if (strcmp (gftp_protocols[i].name, default_protocol) == 0)
                break;
            }
        }

      if (gftp_protocols[i].url_prefix == NULL)
        i = GFTP_FTP_NUM;
    }

  gftp_copy_local_options (&request->local_options_vars,
                           &request->local_options_hash,
                           &request->num_local_options_vars,
                           tempentry->local_options_vars,
                           tempentry->num_local_options_vars);

  if ((init_ret = gftp_protocols[i].init (request)) < 0)
    {
      gftp_request_destroy (request, 0);
      return (init_ret);
    }

  return (0);
}


int
gftp_parse_url (gftp_request * request, const char *url)
{
  char *pos, *endpos, *default_protocol, *new_url;
  gftp_logging_func logging_function;
  const char *clear_pos;
  int i, ret;

  g_return_val_if_fail (request != NULL, GFTP_EFATAL);
  g_return_val_if_fail (url != NULL, GFTP_EFATAL);

  logging_function = request->logging_function;
  gftp_request_destroy (request, 0);
  request->logging_function = logging_function;

  for (clear_pos = url;
       *clear_pos == ' ' || *clear_pos == '\t';
       clear_pos++);

  new_url = g_strdup (clear_pos);

  for (pos = new_url + strlen (new_url) - 1;
       *pos == ' ' || *pos == '\t';
       pos--)
    *pos = '\0';

  /* See if the URL has a protocol... */
  if ((pos = strstr (new_url, "://")) != NULL)
    {
      *pos = '\0';

      for (i = 0; gftp_protocols[i].url_prefix; i++)
        {
          if (strcmp (gftp_protocols[i].url_prefix, new_url) == 0)
            break;
        }

      if (gftp_protocols[i].url_prefix == NULL)
        {
          request->logging_function (gftp_logging_error, NULL, 
                                     _("The protocol '%s' is currently not supported.\n"),
                                     new_url);
          g_free (new_url);
          return (GFTP_EFATAL);
        }

      *pos = ':';
      pos += 3;
    }
  else
    {
      gftp_lookup_request_option (request, "default_protocol", 
                                  &default_protocol);

      i = GFTP_FTP_NUM;
      if (default_protocol != NULL && *default_protocol != '\0')
        {
          for (i = 0; gftp_protocols[i].url_prefix; i++)
            {
              if (strcmp (gftp_protocols[i].name, default_protocol) == 0)
                break;
            }
        }

      if (gftp_protocols[i].url_prefix == NULL)
        {
          request->logging_function (gftp_logging_error, NULL, 
                                     _("The protocol '%s' is currently not supported.\n"),
                                     default_protocol);
          g_free (new_url);
          return (GFTP_EFATAL);
        }

      pos = new_url;
    }

  if ((ret = gftp_protocols[i].init (request)) < 0)
    {
      gftp_request_destroy (request, 0);
      return (ret);
    }

  if ((endpos = strchr (pos, '/')) != NULL)
    {
      gftp_set_directory (request, endpos);
      *endpos = '\0';
    }

  if (request->parse_url != NULL)
    {
      ret = request->parse_url (request, new_url);
      g_free (new_url);
      return (ret);
    }

  if (*pos != '\0')
    {
      if (endpos == NULL)
        endpos = pos + strlen (pos) - 1;
      else
        endpos--;

      for (; isdigit (*endpos); endpos--);

      if (*endpos == ':' && isdigit (*(endpos + 1)))
        {
          gftp_set_port (request, strtol (endpos + 1, NULL, 10));
          *endpos = '\0';
        }

      if ((endpos = strrchr (pos, '@')) != NULL)
        {
          gftp_set_hostname (request, endpos + 1);
          *endpos = '\0';

          if ((endpos = strchr (pos, ':')) != NULL)
            {
              *endpos = '\0';
              gftp_set_username (request, pos);
              gftp_set_password (request, endpos + 1);
            }
          else
            {
              gftp_set_username (request, pos);
              gftp_set_password (request, "");
            }
        }
      else
        gftp_set_hostname (request, pos);
    }

  g_free (new_url);
  return (0);
}


void
gftp_set_hostname (gftp_request * request, const char *hostname)
{
  g_return_if_fail (request != NULL);
  g_return_if_fail (hostname != NULL);

  if (request->hostname)
    g_free (request->hostname);
  request->hostname = g_strdup (hostname);
}


void
gftp_set_username (gftp_request * request, const char *username)
{
  g_return_if_fail (request != NULL);

  if (request->username)
    g_free (request->username);

  if (username != NULL)
    request->username = g_strdup (username);
  else
    request->username = NULL;
}


void
gftp_set_password (gftp_request * request, const char *password)
{
  g_return_if_fail (request != NULL);

  if (request->password)
    g_free (request->password);

  if (password != NULL)
    request->password = g_strdup (password);
  else
    request->password = NULL;
}


void
gftp_set_account (gftp_request * request, const char *account)
{
  g_return_if_fail (request != NULL);
  g_return_if_fail (account != NULL);

  if (request->account)
    g_free (request->account);
  request->account = g_strdup (account);
}


int
gftp_set_directory (gftp_request * request, const char *directory)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);
  g_return_val_if_fail (directory != NULL, GFTP_EFATAL);


  if (request->datafd <= 0 && !request->always_connected)
    {
      if (directory != request->directory)
        {
          if (request->directory)
            g_free (request->directory);
          request->directory = g_strdup (directory);
        }
      return (0);
    }
  else if (request->chdir == NULL)
    return (GFTP_EFATAL);
  return (request->chdir (request, directory));
}


void
gftp_set_port (gftp_request * request, unsigned int port)
{
  g_return_if_fail (request != NULL);

  request->port = port;
}


int
gftp_remove_directory (gftp_request * request, const char *directory)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  if (request->rmdir == NULL)
    return (GFTP_EFATAL);
  return (request->rmdir (request, directory));
}


int
gftp_remove_file (gftp_request * request, const char *file)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  if (request->rmfile == NULL)
    return (GFTP_EFATAL);
  return (request->rmfile (request, file));
}


int
gftp_make_directory (gftp_request * request, const char *directory)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  if (request->mkdir == NULL)
    return (GFTP_EFATAL);

  return (request->mkdir (request, directory));
}


int
gftp_rename_file (gftp_request * request, const char *oldname,
                  const char *newname)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  if (request->rename == NULL)
    return (GFTP_EFATAL);

  return (request->rename (request, oldname, newname));
}


int
gftp_chmod (gftp_request * request, const char *file, mode_t mode)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  if (request->chmod == NULL)
    return (GFTP_EFATAL);

  mode &= S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX;
  return (request->chmod (request, file, mode));
}


int
gftp_set_file_time (gftp_request * request, const char *file, time_t datetime)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  if (request->set_file_time == NULL)
    return (GFTP_EFATAL);
  return (request->set_file_time (request, file, datetime));
}


int
gftp_site_cmd (gftp_request * request, int specify_site, const char *command)
{
  g_return_val_if_fail (request != NULL, GFTP_EFATAL);

  if (request->site == NULL)
    return (GFTP_EFATAL);
  return (request->site (request, specify_site, command));
}


off_t
gftp_get_file_size (gftp_request * request, const char *filename)
{
  g_return_val_if_fail (request != NULL, 0);

  if (request->get_file_size == NULL)
    return (0);
  return (request->get_file_size (request, filename));
}


static GHashTable *
gftp_gen_dir_hash (gftp_request * request, int *ret)
{
  GHashTable * dirhash;
  gftp_file * fle;
  off_t *newsize;

  dirhash = g_hash_table_new (string_hash_function, string_hash_compare);
  *ret = gftp_list_files (request);
  if (*ret == 0)
    {
      fle = g_malloc0 (sizeof (*fle));
      while (gftp_get_next_file (request, NULL, fle) > 0)
        {
          newsize = g_malloc0 (sizeof (*newsize));
          *newsize = fle->size;
          g_hash_table_insert (dirhash, fle->file, newsize);
          fle->file = NULL;
          gftp_file_destroy (fle, 0);
        }
      gftp_end_transfer (request);
      g_free (fle);
    }
  else
    {
      g_hash_table_destroy (dirhash);
      dirhash = NULL;
    }

  return (dirhash);
}


static void
destroy_hash_ent (gpointer key, gpointer value, gpointer user_data)
{

  g_free (key);
  g_free (value);
}


static void
gftp_destroy_dir_hash (GHashTable * dirhash)
{
  if (dirhash == NULL)
    return;

  g_hash_table_foreach (dirhash, destroy_hash_ent, NULL);
  g_hash_table_destroy (dirhash);
}


static GList *
gftp_get_dir_listing (gftp_transfer * transfer, int getothdir, int *ret)
{
  GHashTable * dirhash;
  GList * templist;
  gftp_file * fle;
  off_t *newsize;
  char *newname;

  if (getothdir && transfer->toreq != NULL)
    {
      dirhash = gftp_gen_dir_hash (transfer->toreq, ret);
      if (*ret == GFTP_EFATAL)
        return (NULL);
    }
  else 
    dirhash = NULL; 

  *ret = gftp_list_files (transfer->fromreq);
  if (*ret < 0)
    {
      gftp_destroy_dir_hash (dirhash);
      return (NULL);
    }

  fle = g_malloc0 (sizeof (*fle));
  templist = NULL;
  while (gftp_get_next_file (transfer->fromreq, NULL, fle) > 0)
    {
      if (strcmp (fle->file, ".") == 0 || strcmp (fle->file, "..") == 0)
        {
          gftp_file_destroy (fle, 0);
          continue;
        }

      if (dirhash && 
          (newsize = g_hash_table_lookup (dirhash, fle->file)) != NULL)
        {
          fle->exists_other_side = 1;
          fle->startsize = *newsize;
        }
      else
        fle->exists_other_side = 0;

      if (transfer->toreq && fle->destfile == NULL)
        fle->destfile = gftp_build_path (transfer->toreq,
                                         transfer->toreq->directory, 
                                         fle->file, NULL);

      if (transfer->fromreq->directory != NULL &&
          *transfer->fromreq->directory != '\0' &&
          *fle->file != '/')
        {
          newname = gftp_build_path (transfer->fromreq,
                                     transfer->fromreq->directory,
                                     fle->file, NULL);

          g_free (fle->file);
          fle->file = newname;
        }

      templist = g_list_append (templist, fle);

      fle = g_malloc0 (sizeof (*fle));
    }
  gftp_end_transfer (transfer->fromreq);

  gftp_file_destroy (fle, 1);
  gftp_destroy_dir_hash (dirhash);

  return (templist);
}


static void
_cleanup_get_all_subdirs (gftp_transfer * transfer, char *oldfromdir,
                          char *oldtodir,
                          void (*update_func) (gftp_transfer * transfer))
{
  if (update_func != NULL)
    {
      transfer->numfiles = transfer->numdirs = -1;
      update_func (transfer);
    }

  if (oldfromdir != NULL)
    g_free (oldfromdir);

  if (oldtodir != NULL)
    g_free (oldtodir);
}


static GList *
_setup_current_directory_transfer (gftp_transfer * transfer, int *ret)
{
  GHashTable * dirhash;
  char *pos, *newname;
  gftp_file * curfle;
  GList * lastlist;
  off_t *newsize;

  *ret = 0;
  if (transfer->toreq != NULL)
    {
      dirhash = gftp_gen_dir_hash (transfer->toreq, ret);
      if (*ret == GFTP_EFATAL)
        return (NULL);
    }
  else
    dirhash = NULL;

  for (lastlist = transfer->files; ; lastlist = lastlist->next)
    {
      curfle = lastlist->data;

      if ((pos = strrchr (curfle->file, '/')) != NULL)
        pos++;
      else
        pos = curfle->file;

      if (dirhash != NULL && 
          (newsize = g_hash_table_lookup (dirhash, pos)) != NULL)
        {
          curfle->exists_other_side = 1;
          curfle->startsize = *newsize;
        }
      else
        curfle->exists_other_side = 0;

      if (curfle->size < 0 && GFTP_IS_CONNECTED (transfer->fromreq))
        {
          curfle->size = gftp_get_file_size (transfer->fromreq, curfle->file);
          if (curfle->size == GFTP_EFATAL)
            {
              gftp_destroy_dir_hash (dirhash);
              *ret = curfle->size;
              return (NULL);
            }
        }

      if (transfer->toreq && curfle->destfile == NULL)
        curfle->destfile = gftp_build_path (transfer->toreq,
                                            transfer->toreq->directory, 
                                            curfle->file, NULL);

      if (transfer->fromreq->directory != NULL &&
          *transfer->fromreq->directory != '\0' && *curfle->file != '/')
        {
          newname = gftp_build_path (transfer->fromreq,
                                     transfer->fromreq->directory,
                                     curfle->file, NULL);
          g_free (curfle->file);
          curfle->file = newname;
        }

      if (lastlist->next == NULL)
        break;
    }

  gftp_destroy_dir_hash (dirhash);

  return (lastlist);
}


static int
_lookup_curfle_in_device_hash (gftp_request * request, gftp_file * curfle,
                               GHashTable * device_hash)
{
  GHashTable * inode_hash;

  if (curfle->st_dev == 0 || curfle->st_ino == 0)
    return (0);

  if ((inode_hash = g_hash_table_lookup (device_hash,
                                         GUINT_TO_POINTER ((guint) curfle->st_dev))) != NULL)
    {
      if (g_hash_table_lookup (inode_hash,
                               GUINT_TO_POINTER ((guint) curfle->st_ino)))
        {
          request->logging_function (gftp_logging_error, request,
                                     _("Found recursive symbolic link %s\n"),
                                     curfle->file);
          return (1);
        }

      g_hash_table_insert (inode_hash, GUINT_TO_POINTER ((guint) curfle->st_ino),
                           GUINT_TO_POINTER (1));
      return (0);
    }
  else
    {
      inode_hash = g_hash_table_new (uint_hash_function, uint_hash_compare);
      g_hash_table_insert (inode_hash, GUINT_TO_POINTER ((guint) curfle->st_ino),
                           GUINT_TO_POINTER (1));
      g_hash_table_insert (device_hash, GUINT_TO_POINTER ((guint) curfle->st_dev),
                           inode_hash);
      return (0);
    }

}


static void
_free_inode_hash (gpointer key, gpointer value, gpointer user_data)
{
  g_hash_table_destroy (value);
}


static void
_free_device_hash (GHashTable * device_hash)
{
  g_hash_table_foreach (device_hash, _free_inode_hash, NULL);
  g_hash_table_destroy (device_hash);
}


int
gftp_get_all_subdirs (gftp_transfer * transfer,
                      void (*update_func) (gftp_transfer * transfer))
{
  GList * templist, * lastlist;
  char *oldfromdir, *oldtodir;
  GHashTable * device_hash;
  gftp_file * curfle;
  off_t linksize;
  mode_t st_mode;
  int ret;

  g_return_val_if_fail (transfer != NULL, GFTP_EFATAL);
  g_return_val_if_fail (transfer->fromreq != NULL, GFTP_EFATAL);
  g_return_val_if_fail (transfer->files != NULL, GFTP_EFATAL);

  if (transfer->files == NULL)
    return (0);

  ret = 0;
  lastlist = _setup_current_directory_transfer (transfer, &ret);
  if (lastlist == NULL)
    return (ret);

  oldfromdir = oldtodir = NULL;
  device_hash = g_hash_table_new (uint_hash_function, uint_hash_compare);

  for (templist = transfer->files; templist != NULL; templist = templist->next)
    {
      curfle = templist->data;

      if (_lookup_curfle_in_device_hash (transfer->fromreq, curfle,
                                         device_hash))
        continue;

      if (S_ISLNK (curfle->st_mode) && !S_ISDIR (curfle->st_mode))
        {
          st_mode = 0;
          linksize = 0;
          ret = gftp_stat_filename (transfer->fromreq, curfle->file, &st_mode,
                                    &linksize);
          if (ret == GFTP_EFATAL)
            {
              _cleanup_get_all_subdirs (transfer, oldfromdir, oldtodir,
                                        update_func);
              return (ret);
            }
          else if (ret == 0)
            {
              if (S_ISDIR (st_mode))
                curfle->st_mode = st_mode;
              else
                curfle->size = linksize;
            }
        }

      if (!S_ISDIR (curfle->st_mode))
        {
          transfer->numfiles++;
          continue;
        }

      /* Got a directory... */
      transfer->numdirs++;

      if (oldfromdir == NULL)
        oldfromdir = g_strdup (transfer->fromreq->directory);

      ret = gftp_set_directory (transfer->fromreq, curfle->file);
      if (ret < 0)
        {
          _cleanup_get_all_subdirs (transfer, oldfromdir, oldtodir,
                                    update_func);
          _free_device_hash (device_hash);
          return (ret);
        }

      if (transfer->toreq != NULL)
        {
          if (oldtodir == NULL)
            oldtodir = g_strdup (transfer->toreq->directory);

          if (curfle->exists_other_side)
            {
              ret = gftp_set_directory (transfer->toreq, curfle->destfile);
              if (ret == GFTP_EFATAL)
                {
                  _cleanup_get_all_subdirs (transfer, oldfromdir, oldtodir,
                                            update_func);
                  _free_device_hash (device_hash);
                  return (ret);
                }
            }
          else
            {
              if (transfer->toreq->directory != NULL)
                g_free (transfer->toreq->directory);

              transfer->toreq->directory = g_strdup (curfle->destfile);
            }
        } 

      ret = 0;
      lastlist->next = gftp_get_dir_listing (transfer,
                                             curfle->exists_other_side, &ret);
      if (ret < 0)
        {
          _cleanup_get_all_subdirs (transfer, oldfromdir, oldtodir,
                                    update_func);
          _free_device_hash (device_hash);
          return (ret);
        }

      if (lastlist->next != NULL)
        {
          lastlist->next->prev = lastlist;
          for (; lastlist->next != NULL; lastlist = lastlist->next);
        }

      if (update_func != NULL)
        update_func (transfer);
    }

  _free_device_hash (device_hash);

  if (oldfromdir != NULL)
    {
      ret = gftp_set_directory (transfer->fromreq, oldfromdir);
      if (ret == GFTP_EFATAL)
        {
          _cleanup_get_all_subdirs (transfer, oldfromdir, oldtodir,
                                    update_func);
          return (ret);
        }
    }

  if (oldtodir != NULL)
    {
      ret = gftp_set_directory (transfer->toreq, oldtodir);
      if (ret == GFTP_EFATAL)
        {
          _cleanup_get_all_subdirs (transfer, oldfromdir, oldtodir,
                                    update_func);
          return (ret);
        }
    }

  _cleanup_get_all_subdirs (transfer, oldfromdir, oldtodir, update_func);

  return (0);
}


int
gftp_set_config_options (gftp_request * request)
{
  if (request->set_config_options != NULL)
    return (request->set_config_options (request));
  else
    return (0);
}


void
print_file_list (GList * list)
{
  gftp_file * tempfle;
  GList * templist;
  char *attribs;

  printf ("--START OF FILE LISTING - TOP TO BOTTOM--\n");
  for (templist = list; ; templist = templist->next)
    {
      tempfle = templist->data;
      attribs = gftp_convert_attributes_from_mode_t (tempfle->st_mode);

      printf ("%s:%s:" GFTP_OFF_T_PRINTF_MOD ":" GFTP_OFF_T_PRINTF_MOD ":%s:%s:%s\n", 
              tempfle->file, tempfle->destfile, 
              tempfle->size, tempfle->startsize, 
              tempfle->user, tempfle->group, attribs);

      g_free (attribs);
      if (templist->next == NULL)
        break;
    }

  printf ("--START OF FILE LISTING - BOTTOM TO TOP--\n");
  for (; ; templist = templist->prev)
    {
      tempfle = templist->data;
      attribs = gftp_convert_attributes_from_mode_t (tempfle->st_mode);

      printf ("%s:%s:" GFTP_OFF_T_PRINTF_MOD ":" GFTP_OFF_T_PRINTF_MOD ":%s:%s:%s\n", 
              tempfle->file, tempfle->destfile, 
              tempfle->size, tempfle->startsize, 
              tempfle->user, tempfle->group, attribs);

      g_free (attribs);
      if (templist == list)
        break;
    }
  printf ("--END OF FILE LISTING--\n");
}


void
gftp_swap_socks (gftp_request * dest, gftp_request * source)
{
  g_return_if_fail (dest != NULL);
  g_return_if_fail (source != NULL);
  g_return_if_fail (dest->protonum == source->protonum);

  dest->datafd = source->datafd;
  dest->cached = 0;
#ifdef USE_SSL
  dest->ssl = source->ssl;
#endif

  if (!source->always_connected)
    {
      source->datafd = -1;
      source->cached = 1;
#ifdef USE_SSL
      source->ssl = NULL;
#endif
    }

  if (dest->swap_socks != NULL)
    dest->swap_socks (dest, source);
}


void
gftp_calc_kbs (gftp_transfer * tdata, ssize_t num_read)
{
  /* Needed for systems that size(float) < size(void *) */
  union { intptr_t i; float f; } maxkbs;
  unsigned long waitusecs;
  double start_difftime;
  struct timeval tv;
  int waited;

  gftp_lookup_request_option (tdata->fromreq, "maxkbs", &maxkbs.f);

  if (g_thread_supported ())
    g_static_mutex_lock (&tdata->statmutex);

  gettimeofday (&tv, NULL);

  tdata->trans_bytes += num_read;
  tdata->curtrans += num_read;
  tdata->stalled = 0;

  start_difftime = (tv.tv_sec - tdata->starttime.tv_sec) + ((double) (tv.tv_usec - tdata->starttime.tv_usec) / 1000000.0);

  if (start_difftime <= 0)
    tdata->kbs = tdata->trans_bytes / 1024.0;
  else
    tdata->kbs = tdata->trans_bytes / 1024.0 / start_difftime;

  waited = 0;
  if (maxkbs.f > 0 && tdata->kbs > maxkbs.f)
    {
      waitusecs = num_read / 1024.0 / maxkbs.f * 1000000.0 - start_difftime;

      if (waitusecs > 0)
        {
          if (g_thread_supported ())
            g_static_mutex_unlock (&tdata->statmutex);

          waited = 1;
          usleep (waitusecs);

          if (g_thread_supported ())
            g_static_mutex_lock (&tdata->statmutex);
        }

    }

  if (waited)
    gettimeofday (&tdata->lasttime, NULL);
  else
    memcpy (&tdata->lasttime, &tv, sizeof (tdata->lasttime));

  if (g_thread_supported ())
    g_static_mutex_unlock (&tdata->statmutex);
}


static int
_do_sleep (int sleep_time)
{
  struct timeval tv;

  tv.tv_sec = sleep_time;
  tv.tv_usec = 0;

  return (select (0, NULL, NULL, NULL, &tv));
}


int
gftp_get_transfer_status (gftp_transfer * tdata, ssize_t num_read)
{
  intptr_t retries, sleep_time;
  gftp_file * tempfle;
  int ret1, ret2;

  gftp_lookup_request_option (tdata->fromreq, "retries", &retries);
  gftp_lookup_request_option (tdata->fromreq, "sleep_time", &sleep_time);

  if (g_thread_supported ())
    g_static_mutex_lock (&tdata->structmutex);

  if (tdata->curfle == NULL)
    {
      if (g_thread_supported ())
        g_static_mutex_unlock (&tdata->structmutex);

      return (GFTP_EFATAL);
    }

  tempfle = tdata->curfle->data;

  if (g_thread_supported ())
    g_static_mutex_unlock (&tdata->structmutex);

  gftp_disconnect (tdata->fromreq);
  gftp_disconnect (tdata->toreq);

  if (tdata->cancel || num_read == GFTP_EFATAL)
    return (GFTP_EFATAL);
  else if (num_read >= 0 && !tdata->skip_file)
    return (0);

  if (num_read != GFTP_ETIMEDOUT && !tdata->conn_error_no_timeout)
    {
      if (retries != 0 && 
          tdata->current_file_retries >= retries)
        {
          tdata->fromreq->logging_function (gftp_logging_error, tdata->fromreq,
                   _("Error: Remote site %s disconnected. Max retries reached...giving up\n"),
                   tdata->fromreq->hostname != NULL ? 
                         tdata->fromreq->hostname : tdata->toreq->hostname);
          return (GFTP_EFATAL);
        }
      else
        {
          tdata->fromreq->logging_function (gftp_logging_error, tdata->fromreq,
                     _("Error: Remote site %s disconnected. Will reconnect in %d seconds\n"),
                     tdata->fromreq->hostname != NULL ? 
                         tdata->fromreq->hostname : tdata->toreq->hostname, 
                     sleep_time);
        }
    }

  while (retries == 0 || 
         tdata->current_file_retries <= retries)
    {
      /* Look up the options in case the user changes them... */
      gftp_lookup_request_option (tdata->fromreq, "retries", &retries);
      gftp_lookup_request_option (tdata->fromreq, "sleep_time", &sleep_time);

      if (num_read != GFTP_ETIMEDOUT && !tdata->conn_error_no_timeout &&
          !tdata->skip_file)
        _do_sleep (sleep_time);

      tdata->current_file_retries++;

      ret1 = ret2 = 0;
      if ((ret1 = gftp_connect (tdata->fromreq)) == 0 &&
          (ret2 = gftp_connect (tdata->toreq)) == 0)
        {
          if (g_thread_supported ())
            g_static_mutex_lock (&tdata->structmutex);

          tdata->resumed_bytes = tdata->resumed_bytes + tdata->trans_bytes - tdata->curresumed - tdata->curtrans;
          tdata->trans_bytes = 0;
          if (tdata->skip_file)
            {
              tdata->total_bytes -= tempfle->size;
              tdata->curtrans = 0;

              tdata->curfle = tdata->curfle->next;
              tdata->next_file = 1;
              tdata->skip_file = 0;
              tdata->cancel = 0;
              tdata->fromreq->cancel = 0;
              tdata->toreq->cancel = 0;
            }
          else
            {
              tempfle->transfer_action = GFTP_TRANS_ACTION_RESUME;
              tempfle->startsize = tdata->curtrans + tdata->curresumed;
              /* We decrement this here because it will be incremented in 
                 the loop again */
              tdata->curresumed = 0;
              tdata->current_file_number--; /* Decrement this because it 
                                               will be incremented when we 
                                               continue in the loop */
            }

          gettimeofday (&tdata->starttime, NULL);

          if (g_thread_supported ())
            g_static_mutex_unlock (&tdata->structmutex);

          return (GFTP_ERETRYABLE);
        }
      else if (ret1 == GFTP_EFATAL || ret2 == GFTP_EFATAL)
        {
          gftp_disconnect (tdata->fromreq);
          gftp_disconnect (tdata->toreq);
          return (GFTP_EFATAL);
        }
    }

  return (0);
}


int
gftp_fd_open (gftp_request * request, const char *pathname, int flags, mode_t mode)
{
  int fd;

  if (mode == 0)
    fd = open (pathname, flags);
  else
    fd = open (pathname, flags, mode);

  if (fd < 0)
    {
      if (request != NULL)
        request->logging_function (gftp_logging_error, request,
                                   _("Error: Cannot open local file %s: %s\n"),
                                   pathname, g_strerror (errno));
      return (GFTP_ERETRYABLE);
    }

  if (fcntl (fd, F_SETFD, 1) == -1)
    {
      if (request != NULL)
        request->logging_function (gftp_logging_error, request,
                                   _("Error: Cannot set close on exec flag: %s\n"),
                                   g_strerror (errno));

      return (-1);
    }

  return (fd);
}


void
gftp_setup_startup_directory (gftp_request * request, const char *option_name)
{
  char *startup_directory, *tempstr;

  gftp_lookup_request_option (request, option_name, &startup_directory);

  if (*startup_directory != '\0' &&
      (tempstr = gftp_expand_path (request, startup_directory)) != NULL)
    {
      gftp_set_directory (request, tempstr);
      g_free (tempstr);
    }
}


char *
gftp_convert_attributes_from_mode_t (mode_t mode)
{
  char *str;

  str = g_malloc0 (11UL);
  
  str[0] = '?';
  if (S_ISREG (mode))
    str[0] = '-';

  if (S_ISLNK (mode))
    str[0] = 'l';

  if (S_ISBLK (mode))
    str[0] = 'b';

  if (S_ISCHR (mode))
    str[0] = 'c';

  if (S_ISFIFO (mode))
    str[0] = 'p';

  if (S_ISSOCK (mode))
    str[0] = 's';

  if (S_ISDIR (mode))
    str[0] = 'd';

  str[1] = mode & S_IRUSR ? 'r' : '-';
  str[2] = mode & S_IWUSR ? 'w' : '-';

  if ((mode & S_ISUID) && (mode & S_IXUSR))
    str[3] = 's';
  else if (mode & S_ISUID)
    str[3] = 'S';
  else if (mode & S_IXUSR)
    str[3] = 'x';
  else
    str[3] = '-';
    
  str[4] = mode & S_IRGRP ? 'r' : '-';
  str[5] = mode & S_IWGRP ? 'w' : '-';

  if ((mode & S_ISGID) && (mode & S_IXGRP))
    str[6] = 's';
  else if (mode & S_ISGID)
    str[6] = 'S';
  else if (mode & S_IXGRP)
    str[6] = 'x';
  else
    str[6] = '-';

  str[7] = mode & S_IROTH ? 'r' : '-';
  str[8] = mode & S_IWOTH ? 'w' : '-';

  if ((mode & S_ISVTX) && (mode & S_IXOTH))
    str[9] = 't';
  else if (mode & S_ISVTX)
    str[9] = 'T';
  else if (mode & S_IXOTH)
    str[9] = 'x';
  else
    str[9] = '-';

  return (str);
}


mode_t
gftp_convert_attributes_to_mode_t (char *attribs)
{
  mode_t mode;

  if (attribs[0] == 'd')
    mode = S_IFDIR;
  else if (attribs[0] == 'l')
    mode = S_IFLNK;
  else if (attribs[0] == 's')
    mode = S_IFSOCK;
  else if (attribs[0] == 'b')
    mode = S_IFBLK;
  else if (attribs[0] == 'c')
    mode = S_IFCHR;
  else
    mode = S_IFREG;

  if (attribs[1] == 'r')
    mode |= S_IRUSR;
  if (attribs[2] == 'w')
    mode |= S_IWUSR;
  if (attribs[3] == 'x' || attribs[3] == 's')
    mode |= S_IXUSR;
  if (attribs[3] == 's' || attribs[3] == 'S')
    mode |= S_ISUID;

  if (attribs[4] == 'r')
    mode |= S_IRGRP;
  if (attribs[5] == 'w')
    mode |= S_IWGRP;
  if (attribs[6] == 'x' ||
      attribs[6] == 's')
    mode |= S_IXGRP;
  if (attribs[6] == 's' || attribs[6] == 'S')
    mode |= S_ISGID;

  if (attribs[7] == 'r')
    mode |= S_IROTH;
  if (attribs[8] == 'w')
    mode |= S_IWOTH;
  if (attribs[9] == 'x' ||
      attribs[9] == 's')
    mode |= S_IXOTH;
  if (attribs[9] == 't' || attribs[9] == 'T')
    mode |= S_ISVTX;

  return (mode);
}


unsigned int
gftp_protocol_default_port (gftp_request * request)
{
  struct servent serv_struct;

  if (r_getservbyname (gftp_protocols[request->protonum].url_prefix, "tcp",
                       &serv_struct, NULL) == NULL)
    return (gftp_protocols[request->protonum].default_port);
  else
    return (ntohs (serv_struct.s_port));
}