/*

  sftp_client.c

  Author: Pekka Riikonen <priikone@silcnet.org>

  Copyright (C) 2001 2003 Pekka Riikonen

  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; version 2 of the License.

  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.

*/
/* $Id: sftp_client.c,v 1.8.2.1 2003/11/08 11:10:03 priikone Exp $ */

#include "silcincludes.h"
#include "silcsftp.h"
#include "sftp_util.h"

/* Request context. Every request will allocate this context and set
   the correct callback function according the `type' field. */
typedef struct SilcSFTPRequestStruct {
  SilcUInt32 id;
  SilcSFTPPacket type;
  SilcSFTPStatusCallback status;
  SilcSFTPHandleCallback handle;
  SilcSFTPDataCallback data;
  SilcSFTPNameCallback name;
  SilcSFTPAttrCallback attr;
  SilcSFTPExtendedCallback extended;
  void *context;
  struct SilcSFTPRequestStruct *next;
} *SilcSFTPRequest;

/* SFTP client context */
typedef struct {
  SilcSFTPSendPacketCallback send_packet;
  void *send_context;
  SilcSFTPVersionCallback version;
  void *version_context;
  SilcUInt32 id;
  SilcList requests;
  SilcBuffer packet;
} *SilcSFTPClient;

/* File handle */
struct SilcSFTPHandleStruct {
  unsigned char *data;
  SilcUInt32 data_len;
};

/* Creates SilcSFTPHandle and returns pointer to it. The caller must free
   the context. */

static SilcSFTPHandle silc_sftp_handle_create(unsigned char *data,
					      SilcUInt32 data_len)
{
  SilcSFTPHandle handle;

  handle = silc_calloc(1, sizeof(*handle));
  if (!handle)
    return NULL;
  handle->data = silc_calloc(data_len, sizeof(*handle->data));
  if (!handle->data)
    return NULL;
  memcpy(handle->data, data, data_len);
  handle->data_len = data_len;

  return handle;
}

/* Deletes the handle indicated by the `handle'. */

static void silc_sftp_handle_delete(SilcSFTPHandle handle)
{
  silc_free(handle->data);
  silc_free(handle);
}

/* Returns the handle data of the `handle' to the `data' pointer. */

static void silc_sftp_handle_get(SilcSFTPHandle handle,
				 const unsigned char **data,
				 SilcUInt32 *data_len)
{
  *data = (const unsigned char *)handle->data;
  *data_len = handle->data_len;
}

/* General routine to send SFTP packet to the SFTP server. */

static void silc_sftp_send_packet(SilcSFTPClient sftp,
				  SilcSFTPPacket type,
				  SilcUInt32 len, ...)
{
  SilcBuffer tmp;
  va_list vp;

  va_start(vp, len);
  tmp = silc_sftp_packet_encode_vp(type, sftp->packet, len, vp);
  va_end(vp);
  if (!tmp)
    return;
  sftp->packet = tmp;

  SILC_LOG_HEXDUMP(("SFTP packet to server"), sftp->packet->data,
		   sftp->packet->len);

  /* Send the packet */
  (*sftp->send_packet)(sftp->packet, sftp->send_context);

  /* Clear packet */
  sftp->packet->data = sftp->packet->tail = sftp->packet->head;
  sftp->packet->len = 0;
}

/* Finds request by request ID. */

static SilcSFTPRequest silc_sftp_find_request(SilcSFTPClient sftp, SilcUInt32 id)
{
  SilcSFTPRequest req;

  SILC_LOG_DEBUG(("Finding request ID: %d", id));

  silc_list_start(sftp->requests);
  while ((req = silc_list_get(sftp->requests)) != SILC_LIST_END) {
    if (req->id == id)
      return req;
  }

  SILC_LOG_DEBUG(("Unknown request ID"));

  return NULL;
}

/* Function used to call the request callback indicated by the `req'. The
   `status' will be sent to the callback function as the status of the
   operation. The variable argument list includes the status and req->type
   specific data. */

static void silc_sftp_call_request(SilcSFTPClient sftp,
				   SilcSFTPRequest req,
				   SilcSFTPPacket type,
				   SilcSFTPStatus status, ...)
{
  va_list vp;

  SILC_LOG_DEBUG(("Start"));

  va_start(vp, status);

  switch (req->type) {
  case SILC_SFTP_READ:
    {
      /* Data returned */
      unsigned char *data;
      SilcUInt32 data_len;

      if (status != SILC_SFTP_STATUS_OK) {
	if (req->data)
	  (*req->data)((SilcSFTP)sftp, status, NULL, 0, req->context);
	break;
      }

      data = (unsigned char *)va_arg(vp, unsigned char *);
      data_len = (SilcUInt32)va_arg(vp, SilcUInt32);

      if (req->data)
	(*req->data)((SilcSFTP)sftp, status, (const unsigned char *)data,
		     data_len, req->context);
    }
    break;

  case SILC_SFTP_OPEN:
  case SILC_SFTP_OPENDIR:
    {
      /* Handle returned */
      SilcSFTPHandle handle;
      unsigned char *hdata;
      SilcUInt32 hdata_len;

      if (status != SILC_SFTP_STATUS_OK) {
	if (req->handle)
	  (*req->handle)((SilcSFTP)sftp, status, NULL, req->context);
	break;
      }

      hdata = (unsigned char *)va_arg(vp, unsigned char *);
      hdata_len = (SilcUInt32)va_arg(vp, SilcUInt32);
      handle = silc_sftp_handle_create(hdata, hdata_len);
      if (!handle) {
	if (req->handle)
	  (*req->handle)((SilcSFTP)sftp, status, NULL, req->context);
	break;
      }

      if (req->handle)
	(*req->handle)((SilcSFTP)sftp, status, handle, req->context);
    }
    break;

  case SILC_SFTP_CLOSE:
  case SILC_SFTP_WRITE:
  case SILC_SFTP_REMOVE:
  case SILC_SFTP_RENAME:
  case SILC_SFTP_MKDIR:
  case SILC_SFTP_RMDIR:
  case SILC_SFTP_SETSTAT:
  case SILC_SFTP_FSETSTAT:
  case SILC_SFTP_SYMLINK:
    {
      /* Status returned */
      char *message, *language_tag;

      message = (char *)va_arg(vp, char *);
      language_tag = (char *)va_arg(vp, char *);

      if (req->status)
	(*req->status)((SilcSFTP)sftp, status, (const char *)message,
		       (const char *)language_tag, req->context);
    }
    break;

  case SILC_SFTP_STAT:
  case SILC_SFTP_LSTAT:
  case SILC_SFTP_FSTAT:
    {
      /* Attributes returned */
      SilcSFTPAttributes attr;

      if (status != SILC_SFTP_STATUS_OK) {
	if (req->attr)
	  (*req->attr)((SilcSFTP)sftp, status, NULL, req->context);
	break;
      }

      attr = (SilcSFTPAttributes)va_arg(vp, SilcSFTPAttributes);

      if (req->attr)
	(*req->attr)((SilcSFTP)sftp, status, (const SilcSFTPAttributes)attr,
		     req->context);
    }
    break;

  case SILC_SFTP_READDIR:
  case SILC_SFTP_REALPATH:
  case SILC_SFTP_READLINK:
    {
      /* Name(s) returned */
      SilcSFTPName name;

      if (status != SILC_SFTP_STATUS_OK) {
	if (req->name)
	  (*req->name)((SilcSFTP)sftp, status, NULL, req->context);
	break;
      }

      name = (SilcSFTPName)va_arg(vp, SilcSFTPName);

      if (req->name)
	(*req->name)((SilcSFTP)sftp, status, name, req->context);
    }
    break;

  case SILC_SFTP_EXTENDED:
    {
      /* Extended reply returned */
      unsigned char *data;
      SilcUInt32 data_len;

      if (status != SILC_SFTP_STATUS_OK) {
	if (req->extended)
	  (*req->extended)((SilcSFTP)sftp, status, NULL, 0, req->context);
	break;
      }

      data = (unsigned char *)va_arg(vp, unsigned char *);
      data_len = (SilcUInt32)va_arg(vp, SilcUInt32);

      if (req->extended)
	(*req->extended)((SilcSFTP)sftp, status, (const unsigned char *)data,
			 data_len, req->context);
    }
    break;

  default:
    break;
  }

  /* Remove this request */
  silc_list_del(sftp->requests, req);
  silc_free(req);

  va_end(vp);
}

/* Starts SFTP client and returns context for it.  The version callback
   indicated by the `callback' will be called after the SFTP session has
   been started and server has returned the version of the protocol.  The
   SFTP client context is returned in the callback too.  This returns
   allocated SFTP client context or NULL on error. */

SilcSFTP silc_sftp_client_start(SilcSFTPSendPacketCallback send_packet,
				void *send_context,
				SilcSFTPVersionCallback callback,
				void *context)
{
  SilcSFTPClient sftp;

  if (!send_packet)
    return NULL;

  sftp = silc_calloc(1, sizeof(*sftp));
  if (!sftp)
    return NULL;
  sftp->send_packet = send_packet;
  sftp->send_context = send_context;
  sftp->version = callback;
  sftp->version_context = context;
  silc_list_init(sftp->requests, struct SilcSFTPRequestStruct, next);

  /* Send the SFTP session initialization to the server */
  silc_sftp_send_packet(sftp, SILC_SFTP_INIT, 4,
			SILC_STR_UI_INT(SILC_SFTP_PROTOCOL_VERSION),
			SILC_STR_END);

  return (SilcSFTP)sftp;
}

/* Shutdown's the SFTP client.  The caller is responsible of closing
   the associated socket connection.  The SFTP context is freed and is
   invalid after this function returns. */

void silc_sftp_client_shutdown(SilcSFTP context)
{
  SilcSFTPClient sftp = (SilcSFTPClient)context;

  if (sftp->packet)
    silc_buffer_free(sftp->packet);
  silc_free(sftp);
}

/* Function that is called to process the incmoing SFTP packet. */
/* XXX Some day this will go away and we have automatic receive callbacks
   for SilcSocketConnection API or SilcPacketContext API. */

void silc_sftp_client_receive_process(SilcSFTP context,
				      SilcSocketConnection sock,
				      SilcPacketContext *packet)
{
  SilcSFTPClient sftp = (SilcSFTPClient)context;
  SilcSFTPRequest req;
  SilcSFTPPacket type;
  const unsigned char *payload = NULL;
  SilcUInt32 payload_len;
  int ret;
  SilcBufferStruct buf;
  SilcUInt32 id;

  SILC_LOG_DEBUG(("Start"));

  /* Parse the packet */
  type = silc_sftp_packet_decode(packet->buffer, (unsigned char **)&payload,
				 &payload_len);
  if (!type)
    return;

  silc_buffer_set(&buf, (unsigned char *)payload, payload_len);

  switch (type) {
  case SILC_SFTP_VERSION:
    {
      SilcSFTPVersion version;

      SILC_LOG_DEBUG(("Version packet"));

      ret = silc_buffer_unformat(&buf,
				 SILC_STR_UI_INT(&version),
				 SILC_STR_END);
      if (ret < 0) {
	(*sftp->version)((SilcSFTP)sftp, SILC_SFTP_STATUS_FAILURE, 0,
			 sftp->version_context);
	break;
      }

      /* Call the callback */
      (*sftp->version)((SilcSFTP)sftp, SILC_SFTP_STATUS_OK, version,
		       sftp->version_context);
    }
    break;

  case SILC_SFTP_STATUS:
    {
      SilcUInt32 status;
      char *message = NULL, *language_tag = NULL;

      SILC_LOG_DEBUG(("Status packet"));

      ret = silc_buffer_unformat(&buf,
				 SILC_STR_UI_INT(&id),
				 SILC_STR_UI_INT(&status),
				 SILC_STR_END);
      if (ret < 0)
	break;

      if (status != SILC_SFTP_STATUS_OK) {
	silc_buffer_pull(&buf, 8);
	ret = silc_buffer_unformat(&buf,
				   SILC_STR_UI32_STRING_ALLOC(&message),
				   SILC_STR_UI32_STRING_ALLOC(&language_tag),
				   SILC_STR_END);
	if (ret < 0)
	  break;

	silc_buffer_push(&buf, 8);
      }

      /* Get request */
      req = silc_sftp_find_request(sftp, id);
      if (!req) {
	silc_free(message);
	silc_free(language_tag);
	break;
      }

      /* Call the callback */
      silc_sftp_call_request(sftp, req, type, status, message, language_tag);

      silc_free(message);
      silc_free(language_tag);
    }
    break;

  case SILC_SFTP_HANDLE:
    {
      unsigned char *handle = NULL;
      SilcUInt32 handle_len;

      SILC_LOG_DEBUG(("Handle packet"));

      ret = silc_buffer_unformat(&buf,
				 SILC_STR_UI_INT(&id),
				 SILC_STR_UI32_NSTRING(&handle,
						       &handle_len),
				 SILC_STR_END);
      if (ret < 0)
	break;

      /* Get request */
      req = silc_sftp_find_request(sftp, id);
      if (!req)
	break;

      /* Call the callback */
      silc_sftp_call_request(sftp, req, type, SILC_SFTP_STATUS_OK,
			     handle, handle_len);
    }
    break;

  case SILC_SFTP_DATA:
    {
      unsigned char *data = NULL;
      SilcUInt32 data_len = 0;

      SILC_LOG_DEBUG(("Data packet"));

      ret = silc_buffer_unformat(&buf,
				 SILC_STR_UI_INT(&id),
				 SILC_STR_UI32_NSTRING(&data, &data_len),
				 SILC_STR_END);
      if (ret < 0)
	break;

      /* Get request */
      req = silc_sftp_find_request(sftp, id);
      if (!req)
	break;

      /* Call the callback */
      silc_sftp_call_request(sftp, req, type, SILC_SFTP_STATUS_OK,
			     data, data_len);
    }
    break;

  case SILC_SFTP_NAME:
    {
      SilcUInt32 count;
      SilcSFTPName name = NULL;

      SILC_LOG_DEBUG(("Name packet"));

      ret = silc_buffer_unformat(&buf,
				 SILC_STR_UI_INT(&id),
				 SILC_STR_UI_INT(&count),
				 SILC_STR_END);
      if (ret < 0)
	break;

      /* Get request */
      req = silc_sftp_find_request(sftp, id);
      if (!req)
	break;

      silc_buffer_pull(&buf, 8);
      name = silc_sftp_name_decode(count, &buf);
      if (!name)
	break;
      silc_buffer_push(&buf, 8);

      /* Call the callback */
      silc_sftp_call_request(sftp, req, type, SILC_SFTP_STATUS_OK, name);
      silc_sftp_name_free(name);
    }
    break;

  case SILC_SFTP_ATTRS:
    {
      SilcSFTPAttributes attr = NULL;
      unsigned char *data;
      SilcBufferStruct tmpbuf;

      SILC_LOG_DEBUG(("Attributes packet"));

      ret = silc_buffer_unformat(&buf,
				 SILC_STR_UI_INT(&id),
				 SILC_STR_UI_XNSTRING(&data, buf.len - 4),
				 SILC_STR_END);
      if (ret < 0)
	break;

      /* Get request */
      req = silc_sftp_find_request(sftp, id);
      if (!req)
	break;

      silc_buffer_set(&tmpbuf, data, buf.len - 4);
      attr = silc_sftp_attr_decode(&tmpbuf);
      if (!attr)
	break;

      /* Call the callback */
      silc_sftp_call_request(sftp, req, type, SILC_SFTP_STATUS_OK, attr);
    }
    break;

  case SILC_SFTP_EXTENDED_REPLY:
    {
      unsigned char *data = NULL;

      SILC_LOG_DEBUG(("Extended reply packet"));

      ret = silc_buffer_unformat(&buf,
				 SILC_STR_UI_INT(&id),
				 SILC_STR_UI_XNSTRING(&data, buf.len - 4),
				 SILC_STR_END);
      if (ret < 0)
	break;

      /* Get request */
      req = silc_sftp_find_request(sftp, id);
      if (!req)
	break;

      /* Call the callback */
      silc_sftp_call_request(sftp, req, type, SILC_SFTP_STATUS_OK,
			     data, buf.len - 4);
    }
    break;

  default:
    break;
  }
}

void silc_sftp_open(SilcSFTP sftp,
		    const char *filename,
		    SilcSFTPFileOperation pflags,
		    SilcSFTPAttributes attrs,
		    SilcSFTPHandleCallback callback,
		    void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcBuffer attrs_buf;
  SilcUInt32 len = 0;

  SILC_LOG_DEBUG(("Open request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_OPEN;
  req->handle = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  attrs_buf = silc_sftp_attr_encode(attrs);
  if (!attrs_buf)
    return;
  len = 4 + 4 + strlen(filename) + 4 + attrs_buf->len;

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(filename)),
			SILC_STR_UI32_STRING(filename),
			SILC_STR_UI_INT(pflags),
			SILC_STR_UI_XNSTRING(attrs_buf->data,
					     attrs_buf->len),
			SILC_STR_END);

  silc_buffer_free(attrs_buf);
}

void silc_sftp_close(SilcSFTP sftp,
		     SilcSFTPHandle handle,
		     SilcSFTPStatusCallback callback,
		     void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;
  const unsigned char *hdata;
  SilcUInt32 hdata_len;

  SILC_LOG_DEBUG(("Close request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_CLOSE;
  req->status = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  silc_sftp_handle_get(handle, &hdata, &hdata_len);
  len = 4 + 4 + hdata_len;

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(hdata_len),
			SILC_STR_UI_XNSTRING(hdata, hdata_len),
			SILC_STR_END);
  silc_sftp_handle_delete(handle);
}

void silc_sftp_read(SilcSFTP sftp,
		    SilcSFTPHandle handle,
		    SilcUInt64 offset,
		    SilcUInt32 len,
		    SilcSFTPDataCallback callback,
		    void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len2 = 0;
  const unsigned char *hdata;
  SilcUInt32 hdata_len;

  SILC_LOG_DEBUG(("Read request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_READ;
  req->data = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  silc_sftp_handle_get(handle, &hdata, &hdata_len);
  len2 = 4 + 4 + hdata_len + 8 + 4;

  silc_sftp_send_packet(client, req->type, len2,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(hdata_len),
			SILC_STR_UI_XNSTRING(hdata, hdata_len),
			SILC_STR_UI_INT64(offset),
			SILC_STR_UI_INT(len),
			SILC_STR_END);
}

void silc_sftp_write(SilcSFTP sftp,
		     SilcSFTPHandle handle,
		     SilcUInt64 offset,
		     const unsigned char *data,
		     SilcUInt32 data_len,
		     SilcSFTPStatusCallback callback,
		     void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;
  const unsigned char *hdata;
  SilcUInt32 hdata_len;

  SILC_LOG_DEBUG(("Write request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_WRITE;
  req->status = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  silc_sftp_handle_get(handle, &hdata, &hdata_len);
  len = 4 + 4 + hdata_len + 8 + 4 + data_len;

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(hdata_len),
			SILC_STR_UI_XNSTRING(hdata, hdata_len),
			SILC_STR_UI_INT64(offset),
			SILC_STR_UI_INT(data_len),
			SILC_STR_UI_XNSTRING(data, data_len),
			SILC_STR_END);
}

void silc_sftp_remove(SilcSFTP sftp,
		      const char *filename,
		      SilcSFTPStatusCallback callback,
		      void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;

  SILC_LOG_DEBUG(("Remove request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_REMOVE;
  req->status = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  len = 4 + 4 + strlen(filename);

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(filename)),
			SILC_STR_UI32_STRING(filename),
			SILC_STR_END);
}

void silc_sftp_rename(SilcSFTP sftp,
		      const char *oldname,
		      const char *newname,
		      SilcSFTPStatusCallback callback,
		      void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;

  SILC_LOG_DEBUG(("Rename request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_RENAME;
  req->status = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  len = 4 + 4 + strlen(oldname) + 4 + strlen(newname);

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(oldname)),
			SILC_STR_UI32_STRING(oldname),
			SILC_STR_UI_INT(strlen(newname)),
			SILC_STR_UI32_STRING(newname),
			SILC_STR_END);
}

void silc_sftp_mkdir(SilcSFTP sftp,
		     const char *path,
		     SilcSFTPAttributes attrs,
		     SilcSFTPStatusCallback callback,
		     void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;
  SilcBuffer attrs_buf;

  SILC_LOG_DEBUG(("Mkdir request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_MKDIR;
  req->status = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  attrs_buf = silc_sftp_attr_encode(attrs);
  if (!attrs_buf)
    return;
  len = 4 + 4 + strlen(path) + attrs_buf->len;

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(path)),
			SILC_STR_UI32_STRING(path),
			SILC_STR_UI_XNSTRING(attrs_buf->data,
					     attrs_buf->len),
			SILC_STR_END);

  silc_buffer_free(attrs_buf);
}

void silc_sftp_rmdir(SilcSFTP sftp,
		     const char *path,
		     SilcSFTPStatusCallback callback,
		     void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;

  SILC_LOG_DEBUG(("Rmdir request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_RMDIR;
  req->status = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  len = 4 + 4 + strlen(path);

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(path)),
			SILC_STR_UI32_STRING(path),
			SILC_STR_END);
}

void silc_sftp_opendir(SilcSFTP sftp,
		       const char *path,
		       SilcSFTPHandleCallback callback,
		       void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;

  SILC_LOG_DEBUG(("Opendir request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_OPENDIR;
  req->handle = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  len = 4 + 4 + strlen(path);

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(path)),
			SILC_STR_UI32_STRING(path),
			SILC_STR_END);
}

void silc_sftp_readdir(SilcSFTP sftp,
		       SilcSFTPHandle handle,
		       SilcSFTPNameCallback callback,
		       void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;
  const unsigned char *hdata;
  SilcUInt32 hdata_len;

  SILC_LOG_DEBUG(("Readdir request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_READDIR;
  req->name = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  silc_sftp_handle_get(handle, &hdata, &hdata_len);
  len = 4 + 4 + hdata_len;

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(hdata_len),
			SILC_STR_UI_XNSTRING(hdata, hdata_len),
			SILC_STR_END);
}

void silc_sftp_stat(SilcSFTP sftp,
		    const char *path,
		    SilcSFTPAttrCallback callback,
		    void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;

  SILC_LOG_DEBUG(("Stat request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_STAT;
  req->attr = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  len = 4 + 4 + strlen(path);

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(path)),
			SILC_STR_UI32_STRING(path),
			SILC_STR_END);
}

void silc_sftp_lstat(SilcSFTP sftp,
		     const char *path,
		     SilcSFTPAttrCallback callback,
		     void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;

  SILC_LOG_DEBUG(("Lstat request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_LSTAT;
  req->attr = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  len = 4 + 4 + strlen(path);

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(path)),
			SILC_STR_UI32_STRING(path),
			SILC_STR_END);
}

void silc_sftp_fstat(SilcSFTP sftp,
		     SilcSFTPHandle handle,
		     SilcSFTPAttrCallback callback,
		     void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;
  const unsigned char *hdata;
  SilcUInt32 hdata_len;

  SILC_LOG_DEBUG(("Fstat request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_FSTAT;
  req->attr = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  silc_sftp_handle_get(handle, &hdata, &hdata_len);
  len = 4 + 4 + hdata_len;

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(hdata_len),
			SILC_STR_UI_XNSTRING(hdata, hdata_len),
			SILC_STR_END);
}

void silc_sftp_setstat(SilcSFTP sftp,
		       const char *path,
		       SilcSFTPAttributes attrs,
		       SilcSFTPStatusCallback callback,
		       void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;
  SilcBuffer attrs_buf;

  SILC_LOG_DEBUG(("Setstat request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_SETSTAT;
  req->status = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  attrs_buf = silc_sftp_attr_encode(attrs);
  if (!attrs_buf)
    return;
  len = 4 + 4 + strlen(path) + attrs_buf->len;

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(path)),
			SILC_STR_UI32_STRING(path),
			SILC_STR_UI_XNSTRING(attrs_buf->data,
					     attrs_buf->len),
			SILC_STR_END);

  silc_buffer_free(attrs_buf);
}

void silc_sftp_fsetstat(SilcSFTP sftp,
			SilcSFTPHandle handle,
			SilcSFTPAttributes attrs,
			SilcSFTPStatusCallback callback,
			void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;
  SilcBuffer attrs_buf;
  const unsigned char *hdata;
  SilcUInt32 hdata_len;

  SILC_LOG_DEBUG(("Fsetstat request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_FSETSTAT;
  req->status = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  silc_sftp_handle_get(handle, &hdata, &hdata_len);
  attrs_buf = silc_sftp_attr_encode(attrs);
  if (!attrs_buf)
    return;
  len = 4 + 4 + hdata_len + attrs_buf->len;

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(hdata_len),
			SILC_STR_UI_XNSTRING(hdata, hdata_len),
			SILC_STR_UI_XNSTRING(attrs_buf->data,
					     attrs_buf->len),
			SILC_STR_END);

  silc_buffer_free(attrs_buf);
}

void silc_sftp_readlink(SilcSFTP sftp,
			const char *path,
			SilcSFTPNameCallback callback,
			void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;

  SILC_LOG_DEBUG(("Readlink request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_READLINK;
  req->name = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  len = 4 + 4 + strlen(path);

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(path)),
			SILC_STR_UI32_STRING(path),
			SILC_STR_END);
}

void silc_sftp_symlink(SilcSFTP sftp,
		       const char *linkpath,
		       const char *targetpath,
		       SilcSFTPStatusCallback callback,
		       void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;

  SILC_LOG_DEBUG(("Symlink request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_SYMLINK;
  req->status = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  len = 4 + 4 + strlen(linkpath) + 4 + strlen(targetpath);

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(linkpath)),
			SILC_STR_UI32_STRING(linkpath),
			SILC_STR_UI_INT(strlen(targetpath)),
			SILC_STR_UI32_STRING(targetpath),
			SILC_STR_END);
}

void silc_sftp_realpath(SilcSFTP sftp,
			const char *path,
			SilcSFTPNameCallback callback,
			void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;

  SILC_LOG_DEBUG(("Realpath request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_REALPATH;
  req->name = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  len = 4 + 4 + strlen(path);

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(path)),
			SILC_STR_UI32_STRING(path),
			SILC_STR_END);
}

void silc_sftp_extended(SilcSFTP sftp,
			const char *request,
			const unsigned char *data,
			SilcUInt32 data_len,
			SilcSFTPExtendedCallback callback,
			void *context)
{
  SilcSFTPClient client = (SilcSFTPClient)sftp;
  SilcSFTPRequest req;
  SilcUInt32 len = 0;

  SILC_LOG_DEBUG(("Extended request"));

  req = silc_calloc(1, sizeof(*req));
  if (!req)
    return;
  req->id = client->id++;
  req->type = SILC_SFTP_WRITE;
  req->extended = callback;
  req->context = context;
  silc_list_add(client->requests, req);

  len = 4 + 4 + strlen(request) + data_len;

  silc_sftp_send_packet(client, req->type, len,
			SILC_STR_UI_INT(req->id),
			SILC_STR_UI_INT(strlen(request)),
			SILC_STR_UI32_STRING(request),
			SILC_STR_UI_XNSTRING(data, data_len),
			SILC_STR_END);
}
