int sftp_close(sftp_file file){
  int err = SSH_NO_ERROR;

  SAFE_FREE(file->name);
  if (file->handle){
    err = sftp_handle_close(file->sftp,file->handle);
    ssh_string_free(file->handle);
  }
  /* FIXME: check server response and implement errno */
  SAFE_FREE(file);

  return err;
}


int sftp_rmdir(sftp_session sftp, const char *directory) {
  sftp_status_message status = NULL;
  sftp_message msg = NULL;
  ssh_string filename;
  ssh_buffer buffer;
  uint32_t id;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return -1;
  }

  filename = ssh_string_from_char(directory);
  if (filename == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return -1;
  }

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, filename) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(filename);
    return -1;
  }
  if (sftp_packet_write(sftp, SSH_FXP_RMDIR, buffer) < 0) {
    ssh_buffer_free(buffer);
    ssh_string_free(filename);
    return -1;
  }
  ssh_buffer_free(buffer);
  ssh_string_free(filename);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp) < 0) {
      return -1;
    }
    msg = sftp_dequeue(sftp, id);
  }

  /* By specification, this command returns SSH_FXP_STATUS */
  if (msg->packet_type == SSH_FXP_STATUS) {
    status = parse_status_msg(msg);
    sftp_message_free(msg);
    if (status == NULL) {
      return -1;
    }
    sftp_set_error(sftp, status->status);
    switch (status->status) {
      case SSH_FX_OK:
        status_msg_free(status);
        return 0;
        break;
      default:
        break;
    }
    ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
        "SFTP server: %s", status->errormsg);
    status_msg_free(status);
    return -1;
  } else {
    ssh_set_error(sftp->session, SSH_FATAL,
        "Received message %d when attempting to remove directory",
        msg->packet_type);
    sftp_message_free(msg);
  }

  return -1;
}

sftp: Fix bug in sftp_mkdir not returning on error.

resolves: #84
(cherry picked from commit a92c97b2e17715c1b3cdd693d14af6c3311d8e44)


void sftp_free(sftp_session sftp){
  sftp_request_queue ptr;

  if (sftp == NULL) {
    return;
  }

  ssh_channel_send_eof(sftp->channel);
  ptr = sftp->queue;
  while(ptr) {
    sftp_request_queue old;
    sftp_message_free(ptr->message);
    old = ptr->next;
    SAFE_FREE(ptr);
    ptr = old;
  }

  ssh_channel_free(sftp->channel);
  sftp_ext_free(sftp->ext);
  ZERO_STRUCTP(sftp);

  SAFE_FREE(sftp);
}


static sftp_file parse_handle_msg(sftp_message msg){
  sftp_file file;

  if(msg->packet_type != SSH_FXP_HANDLE) {
    ssh_set_error(msg->sftp->session, SSH_FATAL,
        "Not a ssh_fxp_handle message passed in!");
    return NULL;
  }

  file = malloc(sizeof(struct sftp_file_struct));
  if (file == NULL) {
    ssh_set_error_oom(msg->sftp->session);
    return NULL;
  }
  ZERO_STRUCTP(file);

  file->handle = buffer_get_ssh_string(msg->payload);
  if (file->handle == NULL) {
    ssh_set_error(msg->sftp->session, SSH_FATAL,
        "Invalid SSH_FXP_HANDLE message");
    SAFE_FREE(file);
    return NULL;
  }

  file->sftp = msg->sftp;
  file->offset = 0;
  file->eof = 0;

  return file;
}


char *sftp_canonicalize_path(sftp_session sftp, const char *path) {
  sftp_status_message status = NULL;
  sftp_message msg = NULL;
  ssh_string name = NULL;
  ssh_string pathstr;
  ssh_buffer buffer;
  char *cname;
  uint32_t ignored;
  uint32_t id;

  if (sftp == NULL)
    return NULL;
  if (path == NULL) {
    ssh_set_error_invalid(sftp->session, __FUNCTION__);
    return NULL;
  }

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return NULL;
  }

  pathstr = ssh_string_from_char(path);
  if (pathstr == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return NULL;
  }

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, pathstr) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(pathstr);
    return NULL;
  }
  if (sftp_packet_write(sftp, SSH_FXP_REALPATH, buffer) < 0) {
    ssh_buffer_free(buffer);
    ssh_string_free(pathstr);
    return NULL;
  }
  ssh_buffer_free(buffer);
  ssh_string_free(pathstr);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp) < 0) {
      return NULL;
    }
    msg = sftp_dequeue(sftp, id);
  }

  if (msg->packet_type == SSH_FXP_NAME) {
    /* we don't care about "count" */
    buffer_get_u32(msg->payload, &ignored);
    /* we only care about the file name string */
    name = buffer_get_ssh_string(msg->payload);
    sftp_message_free(msg);
    if (name == NULL) {
      /* TODO: error message? */
      return NULL;
    }
    cname = ssh_string_to_char(name);
    ssh_string_free(name);
    if (cname == NULL) {
      ssh_set_error_oom(sftp->session);
    }
    return cname;
  } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */
    status = parse_status_msg(msg);
    sftp_message_free(msg);
    if (status == NULL) {
      return NULL;
    }
    ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
        "SFTP server: %s", status->errormsg);
    status_msg_free(status);
  } else { /* this shouldn't happen */
    ssh_set_error(sftp->session, SSH_FATAL,
        "Received message %d when attempting to set stats", msg->packet_type);
    sftp_message_free(msg);
  }

  return NULL;
}


static sftp_message sftp_dequeue(sftp_session sftp, uint32_t id){
  sftp_request_queue prev = NULL;
  sftp_request_queue queue;
  sftp_message msg;

  if(sftp->queue == NULL) {
    return NULL;
  }

  queue = sftp->queue;
  while (queue) {
    if(queue->message->id == id) {
      /* remove from queue */
      if (prev == NULL) {
        sftp->queue = queue->next;
      } else {
        prev->next = queue->next;
      }
      msg = queue->message;
      request_queue_free(queue);
      ssh_log(sftp->session, SSH_LOG_PACKET,
          "Dequeued msg id %d type %d",
          msg->id,
          msg->packet_type);
      return msg;
    }
    prev = queue;
    queue = queue->next;
  }

  return NULL;
}


unsigned long sftp_tell(sftp_file file) {
  return (unsigned long)file->offset;
}


int sftp_server_version(sftp_session sftp) {
  return sftp->server_version;
}


static int sftp_handle_close(sftp_session sftp, ssh_string handle) {
  sftp_status_message status;
  sftp_message msg = NULL;
  ssh_buffer buffer = NULL;
  uint32_t id;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return -1;
  }

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, handle) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return -1;
  }
  if (sftp_packet_write(sftp, SSH_FXP_CLOSE ,buffer) < 0) {
    ssh_buffer_free(buffer);
    return -1;
  }
  ssh_buffer_free(buffer);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp) < 0) {
      /* something nasty has happened */
      return -1;
    }
    msg = sftp_dequeue(sftp,id);
  }

  switch (msg->packet_type) {
    case SSH_FXP_STATUS:
      status = parse_status_msg(msg);
      sftp_message_free(msg);
      if(status == NULL) {
        return -1;
      }
      sftp_set_error(sftp, status->status);
      switch (status->status) {
        case SSH_FX_OK:
          status_msg_free(status);
          return 0;
          break;
        default:
          break;
      }
      ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
          "SFTP server: %s", status->errormsg);
      status_msg_free(status);
      return -1;
    default:
      ssh_set_error(sftp->session, SSH_FATAL,
          "Received message %d during sftp_handle_close!", msg->packet_type);
      sftp_message_free(msg);
  }

  return -1;
}


sftp_attributes sftp_lstat(sftp_session session, const char *path) {
  return sftp_xstat(session, path, SSH_FXP_LSTAT);
}


static sftp_request_queue request_queue_new(sftp_message msg) {
  sftp_request_queue queue = NULL;

  queue = malloc(sizeof(struct sftp_request_queue_struct));
  if (queue == NULL) {
    ssh_set_error_oom(msg->sftp->session);
    return NULL;
  }
  ZERO_STRUCTP(queue);

  queue->message = msg;

  return queue;
}


static void sftp_message_free(sftp_message msg) {
  sftp_session sftp;

  if (msg == NULL) {
    return;
  }

  sftp = msg->sftp;
  sftp_enter_function();

  ssh_buffer_free(msg->payload);
  SAFE_FREE(msg);

  sftp_leave_function();
}


static char *sftp_parse_longname(const char *longname,
        enum sftp_longname_field_e longname_field) {
    const char *p, *q;
    size_t len, field = 0;
    char *x;

    p = longname;
    /* Find the beginning of the field which is specified by sftp_longanme_field_e. */
    while(field != longname_field) {
        if(isspace(*p)) {
            field++;
            p++;
            while(*p && isspace(*p)) {
                p++;
            }
        } else {
            p++;
        }
    }

    q = p;
    while (! isspace(*q)) {
        q++;
    }

    /* There is no strndup on windows */
    len = q - p + 1;
    x = malloc(len);
    if (x == NULL) {
      return NULL;
    }

    snprintf(x, len, "%s", p);

    return x;
}


char *sftp_readlink(sftp_session sftp, const char *path) {
  sftp_status_message status = NULL;
  sftp_message msg = NULL;
  ssh_string path_s = NULL;
  ssh_string link_s = NULL;
  ssh_buffer buffer;
  char *lnk;
  uint32_t ignored;
  uint32_t id;

  if (sftp == NULL)
    return NULL;
  if (path == NULL) {
    ssh_set_error_invalid(sftp, __FUNCTION__);
    return NULL;
  }
  if (sftp->version < 3){
    ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_readlink",sftp->version);
    return NULL;
  }
  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return NULL;
  }

  path_s = ssh_string_from_char(path);
  if (path_s == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return NULL;
  }

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, path_s) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(path_s);
    return NULL;
  }
  if (sftp_packet_write(sftp, SSH_FXP_READLINK, buffer) < 0) {
    ssh_buffer_free(buffer);
    ssh_string_free(path_s);
    return NULL;
  }
  ssh_buffer_free(buffer);
  ssh_string_free(path_s);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp) < 0) {
      return NULL;
    }
    msg = sftp_dequeue(sftp, id);
  }

  if (msg->packet_type == SSH_FXP_NAME) {
    /* we don't care about "count" */
    buffer_get_u32(msg->payload, &ignored);
    /* we only care about the file name string */
    link_s = buffer_get_ssh_string(msg->payload);
    sftp_message_free(msg);
    if (link_s == NULL) {
      /* TODO: what error to set here? */
      return NULL;
    }
    lnk = ssh_string_to_char(link_s);
    ssh_string_free(link_s);

    return lnk;
  } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */
    status = parse_status_msg(msg);
    sftp_message_free(msg);
    if (status == NULL) {
      return NULL;
    }
    ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
        "SFTP server: %s", status->errormsg);
    status_msg_free(status);
  } else { /* this shouldn't happen */
    ssh_set_error(sftp->session, SSH_FATAL,
        "Received message %d when attempting to set stats", msg->packet_type);
    sftp_message_free(msg);
  }

  return NULL;
}


int sftp_closedir(sftp_dir dir){
  int err = SSH_NO_ERROR;

  SAFE_FREE(dir->name);
  if (dir->handle) {
    err = sftp_handle_close(dir->sftp, dir->handle);
    ssh_string_free(dir->handle);
  }
  /* FIXME: check server response and implement errno */
  ssh_buffer_free(dir->buffer);
  SAFE_FREE(dir);

  return err;
}


void sftp_rewind(sftp_file file) {
  file->offset = 0;
  file->eof = 0;
}


int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload){
  int size;

  if (buffer_prepend_data(payload, &type, sizeof(uint8_t)) < 0) {
    ssh_set_error_oom(sftp->session);
    return -1;
  }

  size = htonl(buffer_get_rest_len(payload));
  if (buffer_prepend_data(payload, &size, sizeof(uint32_t)) < 0) {
    ssh_set_error_oom(sftp->session);
    return -1;
  }

  size = ssh_channel_write(sftp->channel, buffer_get_rest(payload),
      buffer_get_rest_len(payload));
  if (size < 0) {
    return -1;
  } else if((uint32_t) size != buffer_get_rest_len(payload)) {
    ssh_log(sftp->session, SSH_LOG_PACKET,
        "Had to write %d bytes, wrote only %d",
        buffer_get_rest_len(payload),
        size);
  }

  return size;
}


void sftp_statvfs_free(sftp_statvfs_t statvfs) {
  if (statvfs == NULL) {
    return;
  }

  SAFE_FREE(statvfs);
}


void sftp_packet_free(sftp_packet packet) {
  if (packet == NULL) {
    return;
  }

  ssh_buffer_free(packet->payload);
  free(packet);
}


void sftp_file_set_nonblocking(sftp_file handle){
    handle->nonblocking=1;
}


int sftp_server_init(sftp_session sftp){
  ssh_session session = sftp->session;
  sftp_packet packet = NULL;
  ssh_buffer reply = NULL;
  uint32_t version;

  sftp_enter_function();

  packet = sftp_packet_read(sftp);
  if (packet == NULL) {
    sftp_leave_function();
    return -1;
  }

  if (packet->type != SSH_FXP_INIT) {
    ssh_set_error(session, SSH_FATAL,
        "Packet read of type %d instead of SSH_FXP_INIT",
        packet->type);

    sftp_packet_free(packet);
    sftp_leave_function();
    return -1;
  }

  ssh_log(session, SSH_LOG_PACKET, "Received SSH_FXP_INIT");

  buffer_get_u32(packet->payload, &version);
  version = ntohl(version);
  ssh_log(session, SSH_LOG_PACKET, "Client version: %d", version);
  sftp->client_version = version;

  sftp_packet_free(packet);

  reply = ssh_buffer_new();
  if (reply == NULL) {
    ssh_set_error_oom(session);
    sftp_leave_function();
    return -1;
  }

  if (buffer_add_u32(reply, ntohl(LIBSFTP_VERSION)) < 0) {
    ssh_set_error_oom(session);
    ssh_buffer_free(reply);
    sftp_leave_function();
    return -1;
  }

  if (sftp_packet_write(sftp, SSH_FXP_VERSION, reply) < 0) {
    ssh_buffer_free(reply);
    sftp_leave_function();
    return -1;
  }
  ssh_buffer_free(reply);

  ssh_log(session, SSH_LOG_RARE, "Server version sent");

  if (version > LIBSFTP_VERSION) {
    sftp->version = LIBSFTP_VERSION;
  } else {
    sftp->version=version;
  }

  sftp_leave_function();
  return 0;
}


static void sftp_set_error(sftp_session sftp, int errnum) {
  if (sftp != NULL) {
    sftp->errnum = errnum;
  }
}


int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) {
  sftp_status_message status = NULL;
  sftp_message msg = NULL;
  sftp_attributes errno_attr = NULL;
  struct sftp_attributes_struct attr;
  ssh_buffer buffer;
  ssh_string path;
  uint32_t id;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return -1;
  }

  path = ssh_string_from_char(directory);
  if (path == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return -1;
  }

  ZERO_STRUCT(attr);
  attr.permissions = mode;
  attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS;

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, path) < 0 ||
      buffer_add_attributes(buffer, &attr) < 0 ||
      sftp_packet_write(sftp, SSH_FXP_MKDIR, buffer) < 0) {
    ssh_buffer_free(buffer);
    ssh_string_free(path);
    return -1;
  }
  ssh_buffer_free(buffer);
  ssh_string_free(path);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp) < 0) {
      return -1;
    }
    msg = sftp_dequeue(sftp, id);
  }

  /* By specification, this command only returns SSH_FXP_STATUS */
  if (msg->packet_type == SSH_FXP_STATUS) {
    status = parse_status_msg(msg);
    sftp_message_free(msg);
    if (status == NULL) {
      return -1;
    }
    sftp_set_error(sftp, status->status);
    switch (status->status) {
      case SSH_FX_FAILURE:
        /*
         * mkdir always returns a failure, even if the path already exists.
         * To be POSIX conform and to be able to map it to EEXIST a stat
         * call is needed here.
         */
        errno_attr = sftp_lstat(sftp, directory);
        if (errno_attr != NULL) {
          SAFE_FREE(errno_attr);
          sftp_set_error(sftp, SSH_FX_FILE_ALREADY_EXISTS);
        }
        break;
      case SSH_FX_OK:
        status_msg_free(status);
        return 0;
        break;
      default:
        break;
    }
    /*
     * The status should be SSH_FX_OK if the command was successful, if it
     * didn't, then there was an error
     */
    ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
        "SFTP server: %s", status->errormsg);
    status_msg_free(status);
    return -1;
  } else {
    ssh_set_error(sftp->session, SSH_FATAL,
        "Received message %d when attempting to make directory",
        msg->packet_type);
    sftp_message_free(msg);
  }

  return -1;
}


static sftp_message sftp_message_new(sftp_session sftp){
  sftp_message msg = NULL;

  sftp_enter_function();

  msg = malloc(sizeof(struct sftp_message_struct));
  if (msg == NULL) {
    ssh_set_error_oom(sftp->session);
    return NULL;
  }
  ZERO_STRUCTP(msg);

  msg->payload = ssh_buffer_new();
  if (msg->payload == NULL) {
    ssh_set_error_oom(sftp->session);
    SAFE_FREE(msg);
    return NULL;
  }
  msg->sftp = sftp;

  sftp_leave_function();
  return msg;
}


void sftp_file_set_blocking(sftp_file handle){
    handle->nonblocking=0;
}


int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) {
  uint32_t id;
  ssh_buffer buffer;
  ssh_string path;
  sftp_message msg = NULL;
  sftp_status_message status = NULL;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return -1;
  }

  path = ssh_string_from_char(file);
  if (path == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return -1;
  }

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, path) < 0 ||
      buffer_add_attributes(buffer, attr) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(path);
    return -1;
  }
  if (sftp_packet_write(sftp, SSH_FXP_SETSTAT, buffer) < 0) {
    ssh_buffer_free(buffer);
    ssh_string_free(path);
    return -1;
  }
  ssh_buffer_free(buffer);
  ssh_string_free(path);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp) < 0) {
      return -1;
    }
    msg = sftp_dequeue(sftp, id);
  }

  /* By specification, this command only returns SSH_FXP_STATUS */
  if (msg->packet_type == SSH_FXP_STATUS) {
    status = parse_status_msg(msg);
    sftp_message_free(msg);
    if (status == NULL) {
      return -1;
    }
    sftp_set_error(sftp, status->status);
    switch (status->status) {
      case SSH_FX_OK:
        status_msg_free(status);
        return 0;
      default:
        break;
    }
    /*
     * The status should be SSH_FX_OK if the command was successful, if it
     * didn't, then there was an error
     */
    ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
        "SFTP server: %s", status->errormsg);
    status_msg_free(status);
    return -1;
  } else {
    ssh_set_error(sftp->session, SSH_FATAL,
        "Received message %d when attempting to set stats", msg->packet_type);
    sftp_message_free(msg);
  }

  return -1;
}


int sftp_seek64(sftp_file file, uint64_t new_offset) {
  if (file == NULL) {
    return -1;
  }

  file->offset = new_offset;
  file->eof = 0;

  return 0;
}


static sftp_attributes sftp_parse_attr_4(sftp_session sftp, ssh_buffer buf,
    int expectnames) {
  sftp_attributes attr;
  ssh_string owner = NULL;
  ssh_string group = NULL;
  uint32_t flags = 0;
  int ok = 0;

  /* unused member variable */
  (void) expectnames;

  attr = malloc(sizeof(struct sftp_attributes_struct));
  if (attr == NULL) {
    ssh_set_error_oom(sftp->session);
    return NULL;
  }
  ZERO_STRUCTP(attr);

  /* This isn't really a loop, but it is like a try..catch.. */
  do {
    if (buffer_get_u32(buf, &flags) != 4) {
      break;
    }

    flags = ntohl(flags);
    attr->flags = flags;

    if (flags & SSH_FILEXFER_ATTR_SIZE) {
      if (buffer_get_u64(buf, &attr->size) != 8) {
        break;
      }
      attr->size = ntohll(attr->size);
    }

    if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) {
      if((owner = buffer_get_ssh_string(buf)) == NULL ||
        (attr->owner = ssh_string_to_char(owner)) == NULL) {
        break;
      }
      if ((group = buffer_get_ssh_string(buf)) == NULL ||
        (attr->group = ssh_string_to_char(group)) == NULL) {
        break;
      }
    }

    if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
      if (buffer_get_u32(buf, &attr->permissions) != 4) {
        break;
      }
      attr->permissions = ntohl(attr->permissions);

      /* FIXME on windows! */
      switch (attr->permissions & S_IFMT) {
        case S_IFSOCK:
        case S_IFBLK:
        case S_IFCHR:
        case S_IFIFO:
          attr->type = SSH_FILEXFER_TYPE_SPECIAL;
          break;
        case S_IFLNK:
          attr->type = SSH_FILEXFER_TYPE_SYMLINK;
          break;
        case S_IFREG:
          attr->type = SSH_FILEXFER_TYPE_REGULAR;
          break;
        case S_IFDIR:
          attr->type = SSH_FILEXFER_TYPE_DIRECTORY;
          break;
        default:
          attr->type = SSH_FILEXFER_TYPE_UNKNOWN;
          break;
      }
    }

    if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) {
      if (buffer_get_u64(buf, &attr->atime64) != 8) {
        break;
      }
      attr->atime64 = ntohll(attr->atime64);
    }

    if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
      if (buffer_get_u32(buf, &attr->atime_nseconds) != 4) {
        break;
      }
      attr->atime_nseconds = ntohl(attr->atime_nseconds);
    }

    if (flags & SSH_FILEXFER_ATTR_CREATETIME) {
      if (buffer_get_u64(buf, &attr->createtime) != 8) {
        break;
      }
      attr->createtime = ntohll(attr->createtime);
    }

    if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
      if (buffer_get_u32(buf, &attr->createtime_nseconds) != 4) {
        break;
      }
      attr->createtime_nseconds = ntohl(attr->createtime_nseconds);
    }

    if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) {
      if (buffer_get_u64(buf, &attr->mtime64) != 8) {
        break;
      }
      attr->mtime64 = ntohll(attr->mtime64);
    }

    if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
      if (buffer_get_u32(buf, &attr->mtime_nseconds) != 4) {
        break;
      }
      attr->mtime_nseconds = ntohl(attr->mtime_nseconds);
    }

    if (flags & SSH_FILEXFER_ATTR_ACL) {
      if ((attr->acl = buffer_get_ssh_string(buf)) == NULL) {
        break;
      }
    }

    if (flags & SSH_FILEXFER_ATTR_EXTENDED) {
      if (buffer_get_u32(buf,&attr->extended_count) != 4) {
        break;
      }
      attr->extended_count = ntohl(attr->extended_count);

      while(attr->extended_count &&
          (attr->extended_type = buffer_get_ssh_string(buf)) &&
          (attr->extended_data = buffer_get_ssh_string(buf))){
        attr->extended_count--;
      }

      if (attr->extended_count) {
        break;
      }
    }
    ok = 1;
  } while (0);

  if (ok == 0) {
    /* break issued somewhere */
    ssh_string_free(owner);
    ssh_string_free(group);
    ssh_string_free(attr->acl);
    ssh_string_free(attr->extended_type);
    ssh_string_free(attr->extended_data);
    SAFE_FREE(attr->owner);
    SAFE_FREE(attr->group);
    SAFE_FREE(attr);

    ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure");

    return NULL;
  }

  return attr;
}


int sftp_rename(sftp_session sftp, const char *original, const char *newname) {
  sftp_status_message status = NULL;
  sftp_message msg = NULL;
  ssh_buffer buffer;
  ssh_string oldpath;
  ssh_string newpath;
  uint32_t id;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return -1;
  }

  oldpath = ssh_string_from_char(original);
  if (oldpath == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return -1;
  }

  newpath = ssh_string_from_char(newname);
  if (newpath == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(oldpath);
    return -1;
  }

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, oldpath) < 0 ||
      buffer_add_ssh_string(buffer, newpath) < 0 ||
      /* POSIX rename atomically replaces newpath, we should do the same
       * only available on >=v4 */
      sftp->version>=4 ? (buffer_add_u32(buffer, SSH_FXF_RENAME_OVERWRITE) < 0):0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(oldpath);
    ssh_string_free(newpath);
    return -1;
  }
  if (sftp_packet_write(sftp, SSH_FXP_RENAME, buffer) < 0) {
    ssh_buffer_free(buffer);
    ssh_string_free(oldpath);
    ssh_string_free(newpath);
    return -1;
  }
  ssh_buffer_free(buffer);
  ssh_string_free(oldpath);
  ssh_string_free(newpath);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp) < 0) {
      return -1;
    }
    msg = sftp_dequeue(sftp, id);
  }

  /* By specification, this command only returns SSH_FXP_STATUS */
  if (msg->packet_type == SSH_FXP_STATUS) {
    status = parse_status_msg(msg);
    sftp_message_free(msg);
    if (status == NULL) {
      return -1;
    }
    sftp_set_error(sftp, status->status);
    switch (status->status) {
      case SSH_FX_OK:
        status_msg_free(status);
        return 0;
      default:
        break;
    }
    /*
     * Status should be SSH_FX_OK if the command was successful, if it didn't,
     * then there was an error
     */
    ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
        "SFTP server: %s", status->errormsg);
    status_msg_free(status);
    return -1;
  } else {
    ssh_set_error(sftp->session, SSH_FATAL,
        "Received message %d when attempting to rename",
        msg->packet_type);
    sftp_message_free(msg);
  }

  return -1;
}


static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) {
	sftp_statvfs_t  statvfs;
  uint64_t tmp;
  int ok = 0;

  statvfs = malloc(sizeof(struct sftp_statvfs_struct));
  if (statvfs == NULL) {
    ssh_set_error_oom(sftp->session);
    return NULL;
  }
  ZERO_STRUCTP(statvfs);

  /* try .. catch */
  do {
    /* file system block size */
    if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
      break;
    }
    statvfs->f_bsize = ntohll(tmp);

    /* fundamental fs block size */
    if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
      break;
    }
    statvfs->f_frsize = ntohll(tmp);

    /* number of blocks (unit f_frsize) */
    if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
      break;
    }
    statvfs->f_blocks = ntohll(tmp);

    /* free blocks in file system */
    if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
      break;
    }
    statvfs->f_bfree = ntohll(tmp);

    /* free blocks for non-root */
    if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
      break;
    }
    statvfs->f_bavail = ntohll(tmp);

    /* total file inodes */
    if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
      break;
    }
    statvfs->f_files = ntohll(tmp);

    /* free file inodes */
    if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
      break;
    }
    statvfs->f_ffree = ntohll(tmp);

    /* free file inodes for to non-root */
    if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
      break;
    }
    statvfs->f_favail = ntohll(tmp);

    /* file system id */
    if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
      break;
    }
    statvfs->f_fsid = ntohll(tmp);

    /* bit mask of f_flag values */
    if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
      break;
    }
    statvfs->f_flag = ntohll(tmp);

    /* maximum filename length */
    if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
      break;
    }
    statvfs->f_namemax = ntohll(tmp);

    ok = 1;
  } while(0);

  if (!ok) {
    SAFE_FREE(statvfs);
    ssh_set_error(sftp->session, SSH_FATAL, "Invalid statvfs structure");
    return NULL;
  }

  return statvfs;
}


static sftp_attributes sftp_xstat(sftp_session sftp, const char *path,
    int param) {
  sftp_status_message status = NULL;
  sftp_message msg = NULL;
  ssh_string pathstr;
  ssh_buffer buffer;
  uint32_t id;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return NULL;
  }

  pathstr = ssh_string_from_char(path);
  if (pathstr == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return NULL;
  }

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, pathstr) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(pathstr);
    return NULL;
  }
  if (sftp_packet_write(sftp, param, buffer) < 0) {
    ssh_buffer_free(buffer);
    ssh_string_free(pathstr);
    return NULL;
  }
  ssh_buffer_free(buffer);
  ssh_string_free(pathstr);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp) < 0) {
      return NULL;
    }
    msg = sftp_dequeue(sftp, id);
  }

  if (msg->packet_type == SSH_FXP_ATTRS) {
    sftp_attributes attr = sftp_parse_attr(sftp, msg->payload, 0);
    sftp_message_free(msg);

    return attr;
  } else if (msg->packet_type == SSH_FXP_STATUS) {
    status = parse_status_msg(msg);
    sftp_message_free(msg);
    if (status == NULL) {
      return NULL;
    }
    sftp_set_error(sftp, status->status);
    ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
        "SFTP server: %s", status->errormsg);
    status_msg_free(status);
    return NULL;
  }
  ssh_set_error(sftp->session, SSH_FATAL,
      "Received mesg %d during stat()", msg->packet_type);
  sftp_message_free(msg);

  return NULL;
}


int sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id){
  sftp_session sftp = file->sftp;
  sftp_message msg = NULL;
  sftp_status_message status;
  ssh_string datastring;
  int err = SSH_OK;
  uint32_t len;

  sftp_enter_function();

  if (file->eof) {
    sftp_leave_function();
    return 0;
  }

  /* handle an existing request */
  while (msg == NULL) {
    if (file->nonblocking){
      if (ssh_channel_poll(sftp->channel, 0) == 0) {
        /* we cannot block */
        return SSH_AGAIN;
      }
    }

    if (sftp_read_and_dispatch(sftp) < 0) {
      /* something nasty has happened */
      sftp_leave_function();
      return SSH_ERROR;
    }

    msg = sftp_dequeue(sftp,id);
  }

  switch (msg->packet_type) {
    case SSH_FXP_STATUS:
      status = parse_status_msg(msg);
      sftp_message_free(msg);
      if (status == NULL) {
        sftp_leave_function();
        return -1;
      }
      sftp_set_error(sftp, status->status);
      if (status->status != SSH_FX_EOF) {
        ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
            "SFTP server : %s", status->errormsg);
        sftp_leave_function();
        err = SSH_ERROR;
      } else {
        file->eof = 1;
      }
      status_msg_free(status);
      sftp_leave_function();
      return err;
    case SSH_FXP_DATA:
      datastring = buffer_get_ssh_string(msg->payload);
      sftp_message_free(msg);
      if (datastring == NULL) {
        ssh_set_error(sftp->session, SSH_FATAL,
            "Received invalid DATA packet from sftp server");
        sftp_leave_function();
        return SSH_ERROR;
      }
      if (ssh_string_len(datastring) > size) {
        ssh_set_error(sftp->session, SSH_FATAL,
            "Received a too big DATA packet from sftp server: "
            "%" PRIdS " and asked for %u",
            ssh_string_len(datastring), size);
        ssh_string_free(datastring);
        sftp_leave_function();
        return SSH_ERROR;
      }
      len = ssh_string_len(datastring);
      /* Update the offset with the correct value */
      file->offset = file->offset - (size - len);
      memcpy(data, ssh_string_data(datastring), len);
      ssh_string_free(datastring);
      sftp_leave_function();
      return len;
    default:
      ssh_set_error(sftp->session,SSH_FATAL,"Received message %d during read!",msg->packet_type);
      sftp_message_free(msg);
      sftp_leave_function();
      return SSH_ERROR;
  }

  sftp_leave_function();
  return SSH_ERROR;
}


ssize_t sftp_read(sftp_file handle, void *buf, size_t count) {
  sftp_session sftp = handle->sftp;
  sftp_message msg = NULL;
  sftp_status_message status;
  ssh_string datastring;
  ssh_buffer buffer;
  int id;

  if (handle->eof) {
    return 0;
  }

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return -1;
  }
  id = sftp_get_new_id(handle->sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, handle->handle) < 0 ||
      buffer_add_u64(buffer, htonll(handle->offset)) < 0 ||
      buffer_add_u32(buffer,htonl(count)) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return -1;
  }
  if (sftp_packet_write(handle->sftp, SSH_FXP_READ, buffer) < 0) {
    ssh_buffer_free(buffer);
    return -1;
  }
  ssh_buffer_free(buffer);

  while (msg == NULL) {
    if (handle->nonblocking) {
      if (ssh_channel_poll(handle->sftp->channel, 0) == 0) {
        /* we cannot block */
        return 0;
      }
    }
    if (sftp_read_and_dispatch(handle->sftp) < 0) {
      /* something nasty has happened */
      return -1;
    }
    msg = sftp_dequeue(handle->sftp, id);
  }

  switch (msg->packet_type) {
    case SSH_FXP_STATUS:
      status = parse_status_msg(msg);
      sftp_message_free(msg);
      if (status == NULL) {
        return -1;
      }
      sftp_set_error(sftp, status->status);
      switch (status->status) {
        case SSH_FX_EOF:
          handle->eof = 1;
          status_msg_free(status);
          return 0;
        default:
          break;
      }
      ssh_set_error(sftp->session,SSH_REQUEST_DENIED,
          "SFTP server: %s", status->errormsg);
      status_msg_free(status);
      return -1;
    case SSH_FXP_DATA:
      datastring = buffer_get_ssh_string(msg->payload);
      sftp_message_free(msg);
      if (datastring == NULL) {
        ssh_set_error(sftp->session, SSH_FATAL,
            "Received invalid DATA packet from sftp server");
        return -1;
      }

      if (ssh_string_len(datastring) > count) {
        ssh_set_error(sftp->session, SSH_FATAL,
            "Received a too big DATA packet from sftp server: "
            "%" PRIdS " and asked for %" PRIdS,
            ssh_string_len(datastring), count);
        ssh_string_free(datastring);
        return -1;
      }
      count = ssh_string_len(datastring);
      handle->offset += count;
      memcpy(buf, ssh_string_data(datastring), count);
      ssh_string_free(datastring);
      return count;
    default:
      ssh_set_error(sftp->session, SSH_FATAL,
          "Received message %d during read!", msg->packet_type);
      sftp_message_free(msg);
      return -1;
  }

  return -1; /* not reached */
}


static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf,
    int expectname) {
  ssh_string longname = NULL;
  ssh_string name = NULL;
  sftp_attributes attr;
  uint32_t flags = 0;
  int ok = 0;

  attr = malloc(sizeof(struct sftp_attributes_struct));
  if (attr == NULL) {
    ssh_set_error_oom(sftp->session);
    return NULL;
  }
  ZERO_STRUCTP(attr);

  /* This isn't really a loop, but it is like a try..catch.. */
  do {
    if (expectname) {
      if ((name = buffer_get_ssh_string(buf)) == NULL ||
          (attr->name = ssh_string_to_char(name)) == NULL) {
        break;
      }
      ssh_string_free(name);

      ssh_log(sftp->session, SSH_LOG_RARE, "Name: %s", attr->name);

      if ((longname=buffer_get_ssh_string(buf)) == NULL ||
          (attr->longname=ssh_string_to_char(longname)) == NULL) {
        break;
      }
      ssh_string_free(longname);

      /* Set owner and group if we talk to openssh and have the longname */
      if (ssh_get_openssh_version(sftp->session)) {
        attr->owner = sftp_parse_longname(attr->longname, SFTP_LONGNAME_OWNER);
        if (attr->owner == NULL) {
          break;
        }

        attr->group = sftp_parse_longname(attr->longname, SFTP_LONGNAME_GROUP);
        if (attr->group == NULL) {
          break;
        }
      }
    }

    if (buffer_get_u32(buf, &flags) != sizeof(uint32_t)) {
      break;
    }
    flags = ntohl(flags);
    attr->flags = flags;
    ssh_log(sftp->session, SSH_LOG_RARE,
        "Flags: %.8lx\n", (long unsigned int) flags);

    if (flags & SSH_FILEXFER_ATTR_SIZE) {
      if(buffer_get_u64(buf, &attr->size) != sizeof(uint64_t)) {
        break;
      }
      attr->size = ntohll(attr->size);
      ssh_log(sftp->session, SSH_LOG_RARE,
          "Size: %llu\n",
          (long long unsigned int) attr->size);
    }

    if (flags & SSH_FILEXFER_ATTR_UIDGID) {
      if (buffer_get_u32(buf, &attr->uid) != sizeof(uint32_t)) {
        break;
      }
      if (buffer_get_u32(buf, &attr->gid) != sizeof(uint32_t)) {
        break;
      }
      attr->uid = ntohl(attr->uid);
      attr->gid = ntohl(attr->gid);
    }

    if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
      if (buffer_get_u32(buf, &attr->permissions) != sizeof(uint32_t)) {
        break;
      }
      attr->permissions = ntohl(attr->permissions);

      switch (attr->permissions & S_IFMT) {
        case S_IFSOCK:
        case S_IFBLK:
        case S_IFCHR:
        case S_IFIFO:
          attr->type = SSH_FILEXFER_TYPE_SPECIAL;
          break;
        case S_IFLNK:
          attr->type = SSH_FILEXFER_TYPE_SYMLINK;
          break;
        case S_IFREG:
          attr->type = SSH_FILEXFER_TYPE_REGULAR;
          break;
        case S_IFDIR:
          attr->type = SSH_FILEXFER_TYPE_DIRECTORY;
          break;
        default:
          attr->type = SSH_FILEXFER_TYPE_UNKNOWN;
          break;
      }
    }

    if (flags & SSH_FILEXFER_ATTR_ACMODTIME) {
      if (buffer_get_u32(buf, &attr->atime) != sizeof(uint32_t)) {
        break;
      }
      attr->atime = ntohl(attr->atime);
      if (buffer_get_u32(buf, &attr->mtime) != sizeof(uint32_t)) {
        break;
      }
      attr->mtime = ntohl(attr->mtime);
    }

    if (flags & SSH_FILEXFER_ATTR_EXTENDED) {
      if (buffer_get_u32(buf, &attr->extended_count) != sizeof(uint32_t)) {
        break;
      }

      attr->extended_count = ntohl(attr->extended_count);
      while (attr->extended_count &&
          (attr->extended_type = buffer_get_ssh_string(buf))
          && (attr->extended_data = buffer_get_ssh_string(buf))) {
        attr->extended_count--;
      }

      if (attr->extended_count) {
        break;
      }
    }
    ok = 1;
  } while (0);

  if (!ok) {
    /* break issued somewhere */
    ssh_string_free(name);
    ssh_string_free(longname);
    ssh_string_free(attr->extended_type);
    ssh_string_free(attr->extended_data);
    SAFE_FREE(attr->name);
    SAFE_FREE(attr->longname);
    SAFE_FREE(attr->owner);
    SAFE_FREE(attr->group);
    SAFE_FREE(attr);

    ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure");

    return NULL;
  }

  /* everything went smoothly */
  return attr;
}


int sftp_get_error(sftp_session sftp) {
  if (sftp == NULL) {
    return -1;
  }

  return sftp->errnum;
}


sftp_session sftp_new(ssh_session session){
  sftp_session sftp;

  if (session == NULL) {
    return NULL;
  }
  enter_function();

  sftp = malloc(sizeof(struct sftp_session_struct));
  if (sftp == NULL) {
    ssh_set_error_oom(session);
    leave_function();
    return NULL;
  }
  ZERO_STRUCTP(sftp);

  sftp->ext = sftp_ext_new();
  if (sftp->ext == NULL) {
    ssh_set_error_oom(session);
    SAFE_FREE(sftp);
    leave_function();
    return NULL;
  }

  sftp->session = session;
  sftp->channel = ssh_channel_new(session);
  if (sftp->channel == NULL) {
    SAFE_FREE(sftp);
    leave_function();
    return NULL;
  }

  if (ssh_channel_open_session(sftp->channel)) {
    ssh_channel_free(sftp->channel);
    SAFE_FREE(sftp);
    leave_function();
    return NULL;
  }

  if (ssh_channel_request_sftp(sftp->channel)) {
    sftp_free(sftp);
    leave_function();
    return NULL;
  }

  leave_function();
  return sftp;
}


int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group) {
	struct sftp_attributes_struct attr;
  ZERO_STRUCT(attr);

  attr.uid = owner;
  attr.gid = group;

  attr.flags = SSH_FILEXFER_ATTR_UIDGID;

  return sftp_setstat(sftp, file, &attr);
}


sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) {
  sftp_message msg = NULL;
  sftp_status_message status;
  sftp_attributes attr;
  ssh_buffer payload;
  uint32_t id;

  if (dir->buffer == NULL) {
    payload = ssh_buffer_new();
    if (payload == NULL) {
      ssh_set_error_oom(sftp->session);
      return NULL;
    }

    id = sftp_get_new_id(sftp);
    if (buffer_add_u32(payload, id) < 0 ||
        buffer_add_ssh_string(payload, dir->handle) < 0) {
      ssh_set_error_oom(sftp->session);
      ssh_buffer_free(payload);
      return NULL;
    }

    if (sftp_packet_write(sftp, SSH_FXP_READDIR, payload) < 0) {
      ssh_buffer_free(payload);
      return NULL;
    }
    ssh_buffer_free(payload);

    ssh_log(sftp->session, SSH_LOG_PACKET,
        "Sent a ssh_fxp_readdir with id %d", id);

    while (msg == NULL) {
      if (sftp_read_and_dispatch(sftp) < 0) {
        /* something nasty has happened */
        return NULL;
      }
      msg = sftp_dequeue(sftp, id);
    }

    switch (msg->packet_type){
      case SSH_FXP_STATUS:
        status = parse_status_msg(msg);
        sftp_message_free(msg);
        if (status == NULL) {
          return NULL;
        }
        sftp_set_error(sftp, status->status);
        switch (status->status) {
          case SSH_FX_EOF:
            dir->eof = 1;
            status_msg_free(status);
            return NULL;
          default:
            break;
        }

        ssh_set_error(sftp->session, SSH_FATAL,
            "Unknown error status: %d", status->status);
        status_msg_free(status);

        return NULL;
      case SSH_FXP_NAME:
        buffer_get_u32(msg->payload, &dir->count);
        dir->count = ntohl(dir->count);
        dir->buffer = msg->payload;
        msg->payload = NULL;
        sftp_message_free(msg);
        break;
      default:
        ssh_set_error(sftp->session, SSH_FATAL,
            "Unsupported message back %d", msg->packet_type);
        sftp_message_free(msg);

        return NULL;
    }
  }

  /* now dir->buffer contains a buffer and dir->count != 0 */
  if (dir->count == 0) {
    ssh_set_error(sftp->session, SSH_FATAL,
        "Count of files sent by the server is zero, which is invalid, or "
        "libsftp bug");
    return NULL;
  }

  ssh_log(sftp->session, SSH_LOG_RARE, "Count is %d", dir->count);

  attr = sftp_parse_attr(sftp, dir->buffer, 1);
  if (attr == NULL) {
    ssh_set_error(sftp->session, SSH_FATAL,
        "Couldn't parse the SFTP attributes");
    return NULL;
  }

  dir->count--;
  if (dir->count == 0) {
    ssh_buffer_free(dir->buffer);
    dir->buffer = NULL;
  }

  return attr;
}


int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) {
  uint32_t flags = (attr ? attr->flags : 0);

  flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID |
      SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME);

  if (buffer_add_u32(buffer, htonl(flags)) < 0) {
    return -1;
  }

  if (attr) {
    if (flags & SSH_FILEXFER_ATTR_SIZE) {
      if (buffer_add_u64(buffer, htonll(attr->size)) < 0) {
        return -1;
      }
    }

    if (flags & SSH_FILEXFER_ATTR_UIDGID) {
      if (buffer_add_u32(buffer,htonl(attr->uid)) < 0 ||
          buffer_add_u32(buffer,htonl(attr->gid)) < 0) {
        return -1;
      }
    }

    if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
      if (buffer_add_u32(buffer, htonl(attr->permissions)) < 0) {
        return -1;
      }
    }

    if (flags & SSH_FILEXFER_ATTR_ACMODTIME) {
      if (buffer_add_u32(buffer, htonl(attr->atime)) < 0 ||
          buffer_add_u32(buffer, htonl(attr->mtime)) < 0) {
        return -1;
      }
    }
  }

  return 0;
}


sftp_attributes sftp_fstat(sftp_file file) {
  sftp_status_message status = NULL;
  sftp_message msg = NULL;
  ssh_buffer buffer;
  uint32_t id;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(file->sftp->session);
    return NULL;
  }

  id = sftp_get_new_id(file->sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, file->handle) < 0) {
    ssh_set_error_oom(file->sftp->session);
    ssh_buffer_free(buffer);
    return NULL;
  }
  if (sftp_packet_write(file->sftp, SSH_FXP_FSTAT, buffer) < 0) {
    ssh_buffer_free(buffer);
    return NULL;
  }
  ssh_buffer_free(buffer);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(file->sftp) < 0) {
      return NULL;
    }
    msg = sftp_dequeue(file->sftp, id);
  }

  if (msg->packet_type == SSH_FXP_ATTRS){
    return sftp_parse_attr(file->sftp, msg->payload, 0);
  } else if (msg->packet_type == SSH_FXP_STATUS) {
    status = parse_status_msg(msg);
    sftp_message_free(msg);
    if (status == NULL) {
      return NULL;
    }
    ssh_set_error(file->sftp->session, SSH_REQUEST_DENIED,
        "SFTP server: %s", status->errormsg);
    status_msg_free(status);

    return NULL;
  }
  ssh_set_error(file->sftp->session, SSH_FATAL,
      "Received msg %d during fstat()", msg->packet_type);
  sftp_message_free(msg);

  return NULL;
}


sftp_file sftp_open(sftp_session sftp, const char *file, int flags,
    mode_t mode) {
  sftp_message msg = NULL;
  sftp_status_message status;
  struct sftp_attributes_struct attr;
  sftp_file handle;
  ssh_string filename;
  ssh_buffer buffer;
  uint32_t sftp_flags = 0;
  uint32_t id;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return NULL;
  }

  filename = ssh_string_from_char(file);
  if (filename == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return NULL;
  }

  ZERO_STRUCT(attr);
  attr.permissions = mode;
  attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS;

  if (flags == O_RDONLY)
    sftp_flags |= SSH_FXF_READ; /* if any of the other flag is set,
                                   READ should not be set initialy */
  if (flags & O_WRONLY)
    sftp_flags |= SSH_FXF_WRITE;
  if (flags & O_RDWR)
    sftp_flags |= (SSH_FXF_WRITE | SSH_FXF_READ);
  if (flags & O_CREAT)
    sftp_flags |= SSH_FXF_CREAT;
  if (flags & O_TRUNC)
    sftp_flags |= SSH_FXF_TRUNC;
  if (flags & O_EXCL)
    sftp_flags |= SSH_FXF_EXCL;
  ssh_log(sftp->session,SSH_LOG_PACKET,"Opening file %s with sftp flags %x",file,sftp_flags);
  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, filename) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(filename);
    return NULL;
  }
  ssh_string_free(filename);

  if (buffer_add_u32(buffer, htonl(sftp_flags)) < 0 ||
      buffer_add_attributes(buffer, &attr) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return NULL;
  }
  if (sftp_packet_write(sftp, SSH_FXP_OPEN, buffer) < 0) {
    ssh_buffer_free(buffer);
    return NULL;
  }
  ssh_buffer_free(buffer);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp) < 0) {
      /* something nasty has happened */
      return NULL;
    }
    msg = sftp_dequeue(sftp, id);
  }

  switch (msg->packet_type) {
    case SSH_FXP_STATUS:
      status = parse_status_msg(msg);
      sftp_message_free(msg);
      if (status == NULL) {
        return NULL;
      }
      sftp_set_error(sftp, status->status);
      ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
          "SFTP server: %s", status->errormsg);
      status_msg_free(status);

      return NULL;
    case SSH_FXP_HANDLE:
      handle = parse_handle_msg(msg);
      sftp_message_free(msg);
      return handle;
    default:
      ssh_set_error(sftp->session, SSH_FATAL,
          "Received message %d during open!", msg->packet_type);
      sftp_message_free(msg);
  }

  return NULL;
}


int sftp_unlink(sftp_session sftp, const char *file) {
  sftp_status_message status = NULL;
  sftp_message msg = NULL;
  ssh_string filename;
  ssh_buffer buffer;
  uint32_t id;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return -1;
  }

  filename = ssh_string_from_char(file);
  if (filename == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return -1;
  }

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, filename) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(filename);
    return -1;
  }
  if (sftp_packet_write(sftp, SSH_FXP_REMOVE, buffer) < 0) {
    ssh_buffer_free(buffer);
    ssh_string_free(filename);
    return -1;
  }
  ssh_string_free(filename);
  ssh_buffer_free(buffer);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp)) {
      return -1;
    }
    msg = sftp_dequeue(sftp, id);
  }

  if (msg->packet_type == SSH_FXP_STATUS) {
    /* by specification, this command's only supposed to return SSH_FXP_STATUS */
    status = parse_status_msg(msg);
    sftp_message_free(msg);
    if (status == NULL) {
      return -1;
    }
    sftp_set_error(sftp, status->status);
    switch (status->status) {
      case SSH_FX_OK:
        status_msg_free(status);
        return 0;
      default:
        break;
    }

    /*
     * The status should be SSH_FX_OK if the command was successful, if it
     * didn't, then there was an error
     */
    ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
        "SFTP server: %s", status->errormsg);
    status_msg_free(status);
    return -1;
  } else {
    ssh_set_error(sftp->session,SSH_FATAL,
        "Received message %d when attempting to remove file", msg->packet_type);
    sftp_message_free(msg);
  }

  return -1;
}


sftp_packet sftp_packet_read(sftp_session sftp) {
  unsigned char buffer[4096];
  sftp_packet packet = NULL;
  uint32_t size;
  int r;

  sftp_enter_function();

  packet = malloc(sizeof(struct sftp_packet_struct));
  if (packet == NULL) {
    ssh_set_error_oom(sftp->session);
    return NULL;
  }
  packet->sftp = sftp;
  packet->payload = ssh_buffer_new();
  if (packet->payload == NULL) {
    ssh_set_error_oom(sftp->session);
    SAFE_FREE(packet);
    return NULL;
  }

  r=ssh_channel_read(sftp->channel, buffer, 4, 0);
  if (r < 0) {
    ssh_buffer_free(packet->payload);
    SAFE_FREE(packet);
    sftp_leave_function();
    return NULL;
  }
  buffer_add_data(packet->payload,buffer, r);
  if (buffer_get_u32(packet->payload, &size) != sizeof(uint32_t)) {
    ssh_set_error(sftp->session, SSH_FATAL, "Short sftp packet!");
    ssh_buffer_free(packet->payload);
    SAFE_FREE(packet);
    sftp_leave_function();
    return NULL;
  }

  size = ntohl(size);
  r=ssh_channel_read(sftp->channel, buffer, 1, 0);
  if (r <= 0) {
    /* TODO: check if there are cases where an error needs to be set here */
    ssh_buffer_free(packet->payload);
    SAFE_FREE(packet);
    sftp_leave_function();
    return NULL;
  }
  buffer_add_data(packet->payload, buffer, r);
  buffer_get_u8(packet->payload, &packet->type);
  size=size-1;
  while (size>0){
    r=ssh_channel_read(sftp->channel,buffer,
        sizeof(buffer)>size ? size:sizeof(buffer),0);

    if(r <= 0) {
      /* TODO: check if there are cases where an error needs to be set here */
      ssh_buffer_free(packet->payload);
      SAFE_FREE(packet);
      sftp_leave_function();
      return NULL;
    }
    if(buffer_add_data(packet->payload,buffer,r)==SSH_ERROR){
      ssh_buffer_free(packet->payload);
      SAFE_FREE(packet);
      sftp_leave_function();
      ssh_set_error_oom(sftp->session);
      return NULL;
    }
    size -= r;
  }

  sftp_leave_function();
  return packet;
}


static void sftp_ext_free(sftp_ext ext) {
  unsigned int i;

  if (ext == NULL) {
    return;
  }

  if (ext->count) {
    for (i = 0; i < ext->count; i++) {
      SAFE_FREE(ext->name[i]);
      SAFE_FREE(ext->data[i]);
    }
    SAFE_FREE(ext->name);
    SAFE_FREE(ext->data);
  }

  SAFE_FREE(ext);
}


static sftp_message sftp_get_message(sftp_packet packet) {
  sftp_session sftp = packet->sftp;
  sftp_message msg = NULL;

  sftp_enter_function();

  msg = sftp_message_new(sftp);
  if (msg == NULL) {
    sftp_leave_function();
    return NULL;
  }

  msg->sftp = packet->sftp;
  msg->packet_type = packet->type;

  if ((packet->type != SSH_FXP_STATUS) && (packet->type!=SSH_FXP_HANDLE) &&
      (packet->type != SSH_FXP_DATA) && (packet->type != SSH_FXP_ATTRS) &&
      (packet->type != SSH_FXP_NAME) && (packet->type != SSH_FXP_EXTENDED_REPLY)) {
    ssh_set_error(packet->sftp->session, SSH_FATAL,
        "Unknown packet type %d", packet->type);
    sftp_message_free(msg);
    sftp_leave_function();
    return NULL;
  }

  if (buffer_get_u32(packet->payload, &msg->id) != sizeof(uint32_t)) {
    ssh_set_error(packet->sftp->session, SSH_FATAL,
        "Invalid packet %d: no ID", packet->type);
    sftp_message_free(msg);
    sftp_leave_function();
    return NULL;
  }

  ssh_log(packet->sftp->session, SSH_LOG_PACKET,
      "Packet with id %d type %d",
      msg->id,
      msg->packet_type);

  if (buffer_add_data(msg->payload, buffer_get_rest(packet->payload),
        buffer_get_rest_len(packet->payload)) < 0) {
    ssh_set_error_oom(sftp->session);
    sftp_message_free(msg);
    sftp_leave_function();
    return NULL;
  }

  sftp_leave_function();
  return msg;
}


static int sftp_read_and_dispatch(sftp_session sftp) {
  sftp_packet packet = NULL;
  sftp_message msg = NULL;

  sftp_enter_function();

  packet = sftp_packet_read(sftp);
  if (packet == NULL) {
    sftp_leave_function();
    return -1; /* something nasty happened reading the packet */
  }

  msg = sftp_get_message(packet);
  sftp_packet_free(packet);
  if (msg == NULL) {
    sftp_leave_function();
    return -1;
  }

  if (sftp_enqueue(sftp, msg) < 0) {
    sftp_message_free(msg);
    sftp_leave_function();
    return -1;
  }

  sftp_leave_function();
  return 0;
}


static sftp_status_message parse_status_msg(sftp_message msg){
  sftp_status_message status;

  if (msg->packet_type != SSH_FXP_STATUS) {
    ssh_set_error(msg->sftp->session, SSH_FATAL,
        "Not a ssh_fxp_status message passed in!");
    return NULL;
  }

  status = malloc(sizeof(struct sftp_status_message_struct));
  if (status == NULL) {
    ssh_set_error_oom(msg->sftp->session);
    return NULL;
  }
  ZERO_STRUCTP(status);

  status->id = msg->id;
  if (buffer_get_u32(msg->payload,&status->status) != 4){
    SAFE_FREE(status);
    ssh_set_error(msg->sftp->session, SSH_FATAL,
        "Invalid SSH_FXP_STATUS message");
    return NULL;
  }
  status->error = buffer_get_ssh_string(msg->payload);
  status->lang = buffer_get_ssh_string(msg->payload);
  if(status->error == NULL || status->lang == NULL){
    if(msg->sftp->version >=3){
      /* These are mandatory from version 3 */
      ssh_string_free(status->error);
    /* status->lang never get allocated if something failed */
      SAFE_FREE(status);
      ssh_set_error(msg->sftp->session, SSH_FATAL,
        "Invalid SSH_FXP_STATUS message");
      return NULL;
    }
  }

  status->status = ntohl(status->status);
  if(status->error)
    status->errormsg = ssh_string_to_char(status->error);
  else
    status->errormsg = strdup("No error message in packet");
  if(status->lang)
    status->langmsg = ssh_string_to_char(status->lang);
  else
    status->langmsg = strdup("");
  if (status->errormsg == NULL || status->langmsg == NULL) {
    ssh_set_error_oom(msg->sftp->session);
    status_msg_free(status);
    return NULL;
  }

  return status;
}


void sftp_attributes_free(sftp_attributes file){
  if (file == NULL) {
    return;
  }

  ssh_string_free(file->acl);
  ssh_string_free(file->extended_data);
  ssh_string_free(file->extended_type);

  SAFE_FREE(file->name);
  SAFE_FREE(file->longname);
  SAFE_FREE(file->group);
  SAFE_FREE(file->owner);

  SAFE_FREE(file);
}


int sftp_symlink(sftp_session sftp, const char *target, const char *dest) {
  sftp_status_message status = NULL;
  sftp_message msg = NULL;
  ssh_string target_s;
  ssh_string dest_s;
  ssh_buffer buffer;
  uint32_t id;

  if (sftp == NULL)
    return -1;
  if (target == NULL || dest == NULL) {
    ssh_set_error_invalid(sftp->session, __FUNCTION__);
    return -1;
  }

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return -1;
  }

  target_s = ssh_string_from_char(target);
  if (target_s == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return -1;
  }

  dest_s = ssh_string_from_char(dest);
  if (dest_s == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_string_free(target_s);
    ssh_buffer_free(buffer);
    return -1;
  }

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(dest_s);
    ssh_string_free(target_s);
    return -1;
  }
  if (ssh_get_openssh_version(sftp->session)) {
    /* TODO check for version number if they ever fix it. */
    if (buffer_add_ssh_string(buffer, target_s) < 0 ||
      buffer_add_ssh_string(buffer, dest_s) < 0) {
      ssh_set_error_oom(sftp->session);
      ssh_buffer_free(buffer);
      ssh_string_free(dest_s);
      ssh_string_free(target_s);
      return -1;
    }
  } else {
    if (buffer_add_ssh_string(buffer, dest_s) < 0 ||
      buffer_add_ssh_string(buffer, target_s) < 0) {
      ssh_set_error_oom(sftp->session);
      ssh_buffer_free(buffer);
      ssh_string_free(dest_s);
      ssh_string_free(target_s);
      return -1;
    }
  }

  if (sftp_packet_write(sftp, SSH_FXP_SYMLINK, buffer) < 0) {
    ssh_buffer_free(buffer);
    ssh_string_free(dest_s);
    ssh_string_free(target_s);
    return -1;
  }
  ssh_buffer_free(buffer);
  ssh_string_free(dest_s);
  ssh_string_free(target_s);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp) < 0) {
      return -1;
    }
    msg = sftp_dequeue(sftp, id);
  }

  /* By specification, this command only returns SSH_FXP_STATUS */
  if (msg->packet_type == SSH_FXP_STATUS) {
    status = parse_status_msg(msg);
    sftp_message_free(msg);
    if (status == NULL) {
      return -1;
    }
    sftp_set_error(sftp, status->status);
    switch (status->status) {
      case SSH_FX_OK:
        status_msg_free(status);
        return 0;
      default:
        break;
    }
    /*
     * The status should be SSH_FX_OK if the command was successful, if it
     * didn't, then there was an error
     */
    ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
        "SFTP server: %s", status->errormsg);
    status_msg_free(status);
    return -1;
  } else {
    ssh_set_error(sftp->session, SSH_FATAL,
        "Received message %d when attempting to set stats", msg->packet_type);
    sftp_message_free(msg);
  }

  return -1;
}


sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) {
  sftp_status_message status = NULL;
  sftp_message msg = NULL;
  ssh_string pathstr;
  ssh_string ext;
  ssh_buffer buffer;
  uint32_t id;

  if (sftp == NULL)
    return NULL;
  if (path == NULL) {
    ssh_set_error_invalid(sftp->session, __FUNCTION__);
    return NULL;
  }
  if (sftp->version < 3){
    ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_statvfs",sftp->version);
    return NULL;
  }

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return NULL;
  }

  ext = ssh_string_from_char("statvfs@openssh.com");
  if (ext == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return NULL;
  }

  pathstr = ssh_string_from_char(path);
  if (pathstr == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(ext);
    return NULL;
  }

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, ext) < 0 ||
      buffer_add_ssh_string(buffer, pathstr) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(ext);
    ssh_string_free(pathstr);
    return NULL;
  }
  if (sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer) < 0) {
    ssh_buffer_free(buffer);
    ssh_string_free(ext);
    ssh_string_free(pathstr);
    return NULL;
  }
  ssh_buffer_free(buffer);
  ssh_string_free(ext);
  ssh_string_free(pathstr);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp) < 0) {
      return NULL;
    }
    msg = sftp_dequeue(sftp, id);
  }

  if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) {
  	sftp_statvfs_t  buf = sftp_parse_statvfs(sftp, msg->payload);
    sftp_message_free(msg);
    if (buf == NULL) {
      return NULL;
    }

    return buf;
  } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */
    status = parse_status_msg(msg);
    sftp_message_free(msg);
    if (status == NULL) {
      return NULL;
    }
    ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
        "SFTP server: %s", status->errormsg);
    status_msg_free(status);
  } else { /* this shouldn't happen */
    ssh_set_error(sftp->session, SSH_FATAL,
        "Received message %d when attempting to get statvfs", msg->packet_type);
    sftp_message_free(msg);
  }

  return NULL;
}


sftp_attributes sftp_stat(sftp_session session, const char *path) {
  return sftp_xstat(session, path, SSH_FXP_STAT);
}


static void request_queue_free(sftp_request_queue queue) {
  if (queue == NULL) {
    return;
  }

  ZERO_STRUCTP(queue);
  SAFE_FREE(queue);
}


int sftp_chmod(sftp_session sftp, const char *file, mode_t mode) {
	struct sftp_attributes_struct attr;
  ZERO_STRUCT(attr);
  attr.permissions = mode;
  attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS;

  return sftp_setstat(sftp, file, &attr);
}


int sftp_async_read_begin(sftp_file file, uint32_t len){
  sftp_session sftp = file->sftp;
  ssh_buffer buffer;
  uint32_t id;

  sftp_enter_function();

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return -1;
  }

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, file->handle) < 0 ||
      buffer_add_u64(buffer, htonll(file->offset)) < 0 ||
      buffer_add_u32(buffer, htonl(len)) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return -1;
  }
  if (sftp_packet_write(sftp, SSH_FXP_READ, buffer) < 0) {
    ssh_buffer_free(buffer);
    return -1;
  }
  ssh_buffer_free(buffer);

  file->offset += len; /* assume we'll read len bytes */

  sftp_leave_function();
  return id;
}


static inline uint32_t sftp_get_new_id(sftp_session session) {
  return ++session->id_counter;
}


unsigned int sftp_extensions_get_count(sftp_session sftp) {
  if (sftp == NULL || sftp->ext == NULL) {
    return 0;
  }

  return sftp->ext->count;
}


ssize_t sftp_write(sftp_file file, const void *buf, size_t count) {
  sftp_session sftp = file->sftp;
  sftp_message msg = NULL;
  sftp_status_message status;
  ssh_string datastring;
  ssh_buffer buffer;
  uint32_t id;
  int len;
  int packetlen;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return -1;
  }

  datastring = ssh_string_new(count);
  if (datastring == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return -1;
  }
  ssh_string_fill(datastring, buf, count);

  id = sftp_get_new_id(file->sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, file->handle) < 0 ||
      buffer_add_u64(buffer, htonll(file->offset)) < 0 ||
      buffer_add_ssh_string(buffer, datastring) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(datastring);
    return -1;
  }
  ssh_string_free(datastring);
  packetlen=buffer_get_rest_len(buffer);
  len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer);
  ssh_buffer_free(buffer);
  if (len < 0) {
    return -1;
  } else  if (len != packetlen) {
    ssh_log(sftp->session, SSH_LOG_PACKET,
        "Could not write as much data as expected");
  }

  while (msg == NULL) {
    if (sftp_read_and_dispatch(file->sftp) < 0) {
      /* something nasty has happened */
      return -1;
    }
    msg = sftp_dequeue(file->sftp, id);
  }

  switch (msg->packet_type) {
    case SSH_FXP_STATUS:
      status = parse_status_msg(msg);
      sftp_message_free(msg);
      if (status == NULL) {
        return -1;
      }
      sftp_set_error(sftp, status->status);
      switch (status->status) {
        case SSH_FX_OK:
          file->offset += count;
          status_msg_free(status);
          return count;
        default:
          break;
      }
      ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
          "SFTP server: %s", status->errormsg);
      file->offset += count;
      status_msg_free(status);
      return -1;
    default:
      ssh_set_error(sftp->session, SSH_FATAL,
          "Received message %d during write!", msg->packet_type);
      sftp_message_free(msg);
      return -1;
  }

  return -1; /* not reached */
}


sftp_statvfs_t sftp_fstatvfs(sftp_file file) {
  sftp_status_message status = NULL;
  sftp_message msg = NULL;
  sftp_session sftp;
  ssh_string ext;
  ssh_buffer buffer;
  uint32_t id;

  if (file == NULL) {
    return NULL;
  }
  sftp = file->sftp;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    return NULL;
  }

  ext = ssh_string_from_char("fstatvfs@openssh.com");
  if (ext == NULL) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    return NULL;
  }

  id = sftp_get_new_id(sftp);
  if (buffer_add_u32(buffer, id) < 0 ||
      buffer_add_ssh_string(buffer, ext) < 0 ||
      buffer_add_ssh_string(buffer, file->handle) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    ssh_string_free(ext);
    return NULL;
  }
  if (sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer) < 0) {
    ssh_buffer_free(buffer);
    ssh_string_free(ext);
    return NULL;
  }
  ssh_buffer_free(buffer);
  ssh_string_free(ext);

  while (msg == NULL) {
    if (sftp_read_and_dispatch(sftp) < 0) {
      return NULL;
    }
    msg = sftp_dequeue(sftp, id);
  }

  if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) {
  	sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload);
    sftp_message_free(msg);
    if (buf == NULL) {
      return NULL;
    }

    return buf;
  } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */
    status = parse_status_msg(msg);
    sftp_message_free(msg);
    if (status == NULL) {
      return NULL;
    }
    ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
        "SFTP server: %s", status->errormsg);
    status_msg_free(status);
  } else { /* this shouldn't happen */
    ssh_set_error(sftp->session, SSH_FATAL,
        "Received message %d when attempting to set stats", msg->packet_type);
    sftp_message_free(msg);
  }

  return NULL;
}


int sftp_extension_supported(sftp_session sftp, const char *name,
    const char *data) {
  int i, n;

  n = sftp_extensions_get_count(sftp);
  for (i = 0; i < n; i++) {
    if (strcmp(sftp_extensions_get_name(sftp, i), name) == 0 &&
        strcmp(sftp_extensions_get_data(sftp, i), data) == 0) {
      return 1;
    }
  }

  return 0;
}


int sftp_init(sftp_session sftp) {
  sftp_packet packet = NULL;
  ssh_buffer buffer = NULL;
  ssh_string ext_name_s = NULL;
  ssh_string ext_data_s = NULL;
  char *ext_name = NULL;
  char *ext_data = NULL;
  uint32_t version = htonl(LIBSFTP_VERSION);

  sftp_enter_function();

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(sftp->session);
    sftp_leave_function();
    return -1;
  }

  if (buffer_add_u32(buffer, version) < 0) {
    ssh_set_error_oom(sftp->session);
    ssh_buffer_free(buffer);
    sftp_leave_function();
    return -1;
  }
  if (sftp_packet_write(sftp, SSH_FXP_INIT, buffer) < 0) {
    ssh_buffer_free(buffer);
    sftp_leave_function();
    return -1;
  }
  ssh_buffer_free(buffer);

  packet = sftp_packet_read(sftp);
  if (packet == NULL) {
    sftp_leave_function();
    return -1;
  }

  if (packet->type != SSH_FXP_VERSION) {
    ssh_set_error(sftp->session, SSH_FATAL,
        "Received a %d messages instead of SSH_FXP_VERSION", packet->type);
    sftp_packet_free(packet);
    sftp_leave_function();
    return -1;
  }

  /* TODO: are we sure there are 4 bytes ready? */
  buffer_get_u32(packet->payload, &version);
  version = ntohl(version);
  ssh_log(sftp->session, SSH_LOG_RARE,
      "SFTP server version %d",
      version);

  ext_name_s = buffer_get_ssh_string(packet->payload);
  while (ext_name_s != NULL) {
    int count = sftp->ext->count;
    char **tmp;

    ext_data_s = buffer_get_ssh_string(packet->payload);
    if (ext_data_s == NULL) {
      ssh_string_free(ext_name_s);
      break;
    }

    ext_name = ssh_string_to_char(ext_name_s);
    ext_data = ssh_string_to_char(ext_data_s);
    if (ext_name == NULL || ext_data == NULL) {
      ssh_set_error_oom(sftp->session);
      SAFE_FREE(ext_name);
      SAFE_FREE(ext_data);
      ssh_string_free(ext_name_s);
      ssh_string_free(ext_data_s);
      return -1;
    }
    ssh_log(sftp->session, SSH_LOG_RARE,
        "SFTP server extension: %s, version: %s",
        ext_name, ext_data);

    count++;
    tmp = realloc(sftp->ext->name, count * sizeof(char *));
    if (tmp == NULL) {
      ssh_set_error_oom(sftp->session);
      SAFE_FREE(ext_name);
      SAFE_FREE(ext_data);
      ssh_string_free(ext_name_s);
      ssh_string_free(ext_data_s);
      return -1;
    }
    tmp[count - 1] = ext_name;
    sftp->ext->name = tmp;

    tmp = realloc(sftp->ext->data, count * sizeof(char *));
    if (tmp == NULL) {
      ssh_set_error_oom(sftp->session);
      SAFE_FREE(ext_name);
      SAFE_FREE(ext_data);
      ssh_string_free(ext_name_s);
      ssh_string_free(ext_data_s);
      return -1;
    }
    tmp[count - 1] = ext_data;
    sftp->ext->data = tmp;

    sftp->ext->count = count;

    ssh_string_free(ext_name_s);
    ssh_string_free(ext_data_s);

    ext_name_s = buffer_get_ssh_string(packet->payload);
  }

  sftp_packet_free(packet);

  sftp->version = sftp->server_version = version;

  sftp_leave_function();

  return 0;
}


sftp_attributes sftp_parse_attr(sftp_session session, ssh_buffer buf,
    int expectname) {
  switch(session->version) {
    case 4:
      return sftp_parse_attr_4(session, buf, expectname);
    case 3:
    case 2:
    case 1:
    case 0:
      return sftp_parse_attr_3(session, buf, expectname);
    default:
      ssh_set_error(session->session, SSH_FATAL,
          "Version %d unsupported by client", session->server_version);
      return NULL;
  }

  return NULL;
}


const char *sftp_extensions_get_data(sftp_session sftp, unsigned int idx) {
  if (sftp == NULL)
    return NULL;
  if (sftp->ext == NULL || sftp->ext->name == NULL) {
    ssh_set_error_invalid(sftp->session, __FUNCTION__);
    return NULL;
  }

  if (idx > sftp->ext->count) {
    ssh_set_error_invalid(sftp->session, __FUNCTION__);
    return NULL;
  }

  return sftp->ext->data[idx];
}


BIO *PKCS7_dataDecode(PKCS7 *p7, EVP_PKEY *pkey, BIO *in_bio, X509 *pcert)
{
    int i, j;
    BIO *out = NULL, *btmp = NULL, *etmp = NULL, *bio = NULL;
    unsigned char *tmp = NULL;
    X509_ALGOR *xa;
    ASN1_OCTET_STRING *data_body = NULL;
    const EVP_MD *evp_md;
    const EVP_CIPHER *evp_cipher = NULL;
    EVP_CIPHER_CTX *evp_ctx = NULL;
    X509_ALGOR *enc_alg = NULL;
    STACK_OF(X509_ALGOR) *md_sk = NULL;
    STACK_OF(PKCS7_RECIP_INFO) *rsk = NULL;
    PKCS7_RECIP_INFO *ri = NULL;

    if (p7 == NULL) {
        PKCS7err(PKCS7_F_PKCS7_DATADECODE, PKCS7_R_INVALID_NULL_POINTER);
        return NULL;
    }

    if (p7->d.ptr == NULL) {
        PKCS7err(PKCS7_F_PKCS7_DATADECODE, PKCS7_R_NO_CONTENT);
        return NULL;
    }

    i = OBJ_obj2nid(p7->type);
    p7->state = PKCS7_S_HEADER;

    switch (i) {
    case NID_pkcs7_signed:
        /*
         * p7->d.sign->contents is a PKCS7 structure consisting of a contentType
         * field and optional content.
         * data_body is NULL if that structure has no (=detached) content
         * or if the contentType is wrong (i.e., not "data").
         */
        data_body = PKCS7_get_octet_string(p7->d.sign->contents);
        md_sk = p7->d.sign->md_algs;
        break;
    case NID_pkcs7_signedAndEnveloped:
        rsk = p7->d.signed_and_enveloped->recipientinfo;
        md_sk = p7->d.signed_and_enveloped->md_algs;
        /* data_body is NULL if the optional EncryptedContent is missing. */
        data_body = p7->d.signed_and_enveloped->enc_data->enc_data;
        enc_alg = p7->d.signed_and_enveloped->enc_data->algorithm;
        evp_cipher = EVP_get_cipherbyobj(enc_alg->algorithm);
        if (evp_cipher == NULL) {
            PKCS7err(PKCS7_F_PKCS7_DATADECODE,
                     PKCS7_R_UNSUPPORTED_CIPHER_TYPE);
            goto err;
        }
        break;
    case NID_pkcs7_enveloped:
        rsk = p7->d.enveloped->recipientinfo;
        enc_alg = p7->d.enveloped->enc_data->algorithm;
        /* data_body is NULL if the optional EncryptedContent is missing. */
        data_body = p7->d.enveloped->enc_data->enc_data;
        evp_cipher = EVP_get_cipherbyobj(enc_alg->algorithm);
        if (evp_cipher == NULL) {
            PKCS7err(PKCS7_F_PKCS7_DATADECODE,
                     PKCS7_R_UNSUPPORTED_CIPHER_TYPE);
            goto err;
        }
        break;
    default:
        PKCS7err(PKCS7_F_PKCS7_DATADECODE, PKCS7_R_UNSUPPORTED_CONTENT_TYPE);
        goto err;
    }

    /* Detached content must be supplied via in_bio instead. */
    if (data_body == NULL && in_bio == NULL) {
        PKCS7err(PKCS7_F_PKCS7_DATADECODE, PKCS7_R_NO_CONTENT);
        goto err;
    }

    /* We will be checking the signature */
    if (md_sk != NULL) {
        for (i = 0; i < sk_X509_ALGOR_num(md_sk); i++) {
            xa = sk_X509_ALGOR_value(md_sk, i);
            if ((btmp = BIO_new(BIO_f_md())) == NULL) {
                PKCS7err(PKCS7_F_PKCS7_DATADECODE, ERR_R_BIO_LIB);
                goto err;
            }

            j = OBJ_obj2nid(xa->algorithm);
            evp_md = EVP_get_digestbynid(j);
            if (evp_md == NULL) {
                PKCS7err(PKCS7_F_PKCS7_DATADECODE,
                         PKCS7_R_UNKNOWN_DIGEST_TYPE);
                goto err;
            }

            BIO_set_md(btmp, evp_md);
            if (out == NULL)
                out = btmp;
            else
                BIO_push(out, btmp);
            btmp = NULL;
        }
    }

    if (evp_cipher != NULL) {
#if 0
        unsigned char key[EVP_MAX_KEY_LENGTH];
        unsigned char iv[EVP_MAX_IV_LENGTH];
        unsigned char *p;
        int keylen, ivlen;
        int max;
        X509_OBJECT ret;
#endif
        unsigned char *tkey = NULL;
        int tkeylen;
        int jj;

        if ((etmp = BIO_new(BIO_f_cipher())) == NULL) {
            PKCS7err(PKCS7_F_PKCS7_DATADECODE, ERR_R_BIO_LIB);
            goto err;
        }

        /*
         * It was encrypted, we need to decrypt the secret key with the
         * private key
         */

        /*
         * Find the recipientInfo which matches the passed certificate (if
         * any)
         */

        if (pcert) {
            for (i = 0; i < sk_PKCS7_RECIP_INFO_num(rsk); i++) {
                ri = sk_PKCS7_RECIP_INFO_value(rsk, i);
                if (!pkcs7_cmp_ri(ri, pcert))
                    break;
                ri = NULL;
            }
            if (ri == NULL) {
                PKCS7err(PKCS7_F_PKCS7_DATADECODE,
                         PKCS7_R_NO_RECIPIENT_MATCHES_CERTIFICATE);
                goto err;
            }
        }

        jj = EVP_PKEY_size(pkey);
        tmp = (unsigned char *)OPENSSL_malloc(jj + 10);
        if (tmp == NULL) {
            PKCS7err(PKCS7_F_PKCS7_DATADECODE, ERR_R_MALLOC_FAILURE);
            goto err;
        }

        /* If we haven't got a certificate try each ri in turn */

        if (pcert == NULL) {
            /*
             * Temporary storage in case EVP_PKEY_decrypt overwrites output
             * buffer on error.
             */
            unsigned char *tmp2;
            tmp2 = OPENSSL_malloc(jj);
            if (!tmp2)
                goto err;
            jj = -1;
            /*
             * Always attempt to decrypt all cases to avoid leaking timing
             * information about a successful decrypt.
             */
            for (i = 0; i < sk_PKCS7_RECIP_INFO_num(rsk); i++) {
                int tret;
                ri = sk_PKCS7_RECIP_INFO_value(rsk, i);
                tret = EVP_PKEY_decrypt(tmp2,
                                        M_ASN1_STRING_data(ri->enc_key),
                                        M_ASN1_STRING_length(ri->enc_key),
                                        pkey);
                if (tret > 0) {
                    memcpy(tmp, tmp2, tret);
                    OPENSSL_cleanse(tmp2, tret);
                    jj = tret;
                }
                ERR_clear_error();
            }
            OPENSSL_free(tmp2);
        } else {
            jj = EVP_PKEY_decrypt(tmp,
                                  M_ASN1_STRING_data(ri->enc_key),
                                  M_ASN1_STRING_length(ri->enc_key), pkey);
            ERR_clear_error();
        }

        evp_ctx = NULL;
        BIO_get_cipher_ctx(etmp, &evp_ctx);
        if (EVP_CipherInit_ex(evp_ctx, evp_cipher, NULL, NULL, NULL, 0) <= 0)
            goto err;
        if (EVP_CIPHER_asn1_to_param(evp_ctx, enc_alg->parameter) < 0)
            goto err;
        /* Generate random key to counter MMA */
        tkeylen = EVP_CIPHER_CTX_key_length(evp_ctx);
        tkey = OPENSSL_malloc(tkeylen);
        if (!tkey)
            goto err;
        if (EVP_CIPHER_CTX_rand_key(evp_ctx, tkey) <= 0)
            goto err;
        /* If we have no key use random key */
        if (jj <= 0) {
            OPENSSL_free(tmp);
            jj = tkeylen;
            tmp = tkey;
            tkey = NULL;
        }

        if (jj != tkeylen) {
            /*
             * Some S/MIME clients don't use the same key and effective key
             * length. The key length is determined by the size of the
             * decrypted RSA key.
             */
            if (!EVP_CIPHER_CTX_set_key_length(evp_ctx, jj)) {
                /* As MMA defence use random key instead */
                OPENSSL_cleanse(tmp, jj);
                OPENSSL_free(tmp);
                jj = tkeylen;
                tmp = tkey;
                tkey = NULL;
            }
        }
        ERR_clear_error();
        if (EVP_CipherInit_ex(evp_ctx, NULL, NULL, tmp, NULL, 0) <= 0)
            goto err;

        OPENSSL_cleanse(tmp, jj);

        if (tkey) {
            OPENSSL_cleanse(tkey, tkeylen);
            OPENSSL_free(tkey);
        }

        if (out == NULL)
            out = etmp;
        else
            BIO_push(out, etmp);
        etmp = NULL;
    }
#if 1
    if (in_bio != NULL) {
        bio = in_bio;
    } else {
# if 0
        bio = BIO_new(BIO_s_mem());
        /*
         * We need to set this so that when we have read all the data, the
         * encrypt BIO, if present, will read EOF and encode the last few
         * bytes
         */
        BIO_set_mem_eof_return(bio, 0);

        if (data_body->length > 0)
            BIO_write(bio, (char *)data_body->data, data_body->length);
# else
        if (data_body->length > 0)
            bio = BIO_new_mem_buf(data_body->data, data_body->length);
        else {
            bio = BIO_new(BIO_s_mem());
            BIO_set_mem_eof_return(bio, 0);
        }
        if (bio == NULL)
            goto err;
# endif
    }
    BIO_push(out, bio);
    bio = NULL;
#endif
    if (0) {
 err:
        if (out != NULL)
            BIO_free_all(out);
        if (btmp != NULL)
            BIO_free_all(btmp);
        if (etmp != NULL)
            BIO_free_all(etmp);
        if (bio != NULL)
            BIO_free_all(bio);
        out = NULL;
    }
    if (tmp != NULL)
        OPENSSL_free(tmp);
    return (out);
}

PKCS#7: Fix NULL dereference with missing EncryptedContent.

CVE-2015-1790

Reviewed-by: Rich Salz <rsalz@openssl.org>


void SSL_CTX_sess_set_remove_cb(SSL_CTX *ctx,
                                void (*cb) (SSL_CTX *ctx, SSL_SESSION *sess))
{
    ctx->remove_session_cb = cb;
}

Fix race condition in NewSessionTicket

If a NewSessionTicket is received by a multi-threaded client when
attempting to reuse a previous ticket then a race condition can occur
potentially leading to a double free of the ticket data.

CVE-2015-1791

This also fixes RT#3808 where a session ID is changed for a session already
in the client session cache. Since the session ID is the key to the cache
this breaks the cache access.

Parts of this patch were inspired by this Akamai change:
https://github.com/akamai/openssl/commit/c0bf69a791239ceec64509f9f19fcafb2461b0d3

Reviewed-by: Rich Salz <rsalz@openssl.org>
(cherry picked from commit 27c76b9b8010b536687318739c6f631ce4194688)

Conflicts:
	ssl/ssl.h
	ssl/ssl_err.c


int SSL_CTX_remove_session(SSL_CTX *ctx, SSL_SESSION *c)
{
    return remove_session_lock(ctx, c, 1);
}


long SSL_SESSION_set_time(SSL_SESSION *s, long t)
{
    if (s == NULL)
        return (0);
    s->time = t;
    return (t);
}


long SSL_CTX_get_timeout(const SSL_CTX *s)
{
    if (s == NULL)
        return (0);
    return (s->session_timeout);
}


void *SSL_SESSION_get_ex_data(const SSL_SESSION *s, int idx)
{
    return (CRYPTO_get_ex_data(&s->ex_data, idx));
}


int SSL_CTX_set_client_cert_engine(SSL_CTX *ctx, ENGINE *e)
{
    if (!ENGINE_init(e)) {
        SSLerr(SSL_F_SSL_CTX_SET_CLIENT_CERT_ENGINE, ERR_R_ENGINE_LIB);
        return 0;
    }
    if (!ENGINE_get_ssl_client_cert_function(e)) {
        SSLerr(SSL_F_SSL_CTX_SET_CLIENT_CERT_ENGINE,
               SSL_R_NO_CLIENT_CERT_METHOD);
        ENGINE_finish(e);
        return 0;
    }
    ctx->client_cert_engine = e;
    return 1;
}


long SSL_SESSION_get_time(const SSL_SESSION *s)
{
    if (s == NULL)
        return (0);
    return (s->time);
}


SSL_SESSION *SSL_get_session(const SSL *ssl)
/* aka SSL_get0_session; gets 0 objects, just returns a copy of the pointer */
{
    return (ssl->session);
}


int ssl3_check_cert_and_algorithm(SSL *s)
{
    int i, idx;
    long alg_k, alg_a;
    EVP_PKEY *pkey = NULL;
    int pkey_bits;
    SESS_CERT *sc;
#ifndef OPENSSL_NO_RSA
    RSA *rsa;
#endif
#ifndef OPENSSL_NO_DH
    DH *dh;
#endif

    alg_k = s->s3->tmp.new_cipher->algorithm_mkey;
    alg_a = s->s3->tmp.new_cipher->algorithm_auth;

    /* we don't have a certificate */
    if ((alg_a & (SSL_aDH | SSL_aNULL | SSL_aKRB5)) || (alg_k & SSL_kPSK))
        return (1);

    sc = s->session->sess_cert;
    if (sc == NULL) {
        SSLerr(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM, ERR_R_INTERNAL_ERROR);
        goto err;
    }
#ifndef OPENSSL_NO_RSA
    rsa = s->session->sess_cert->peer_rsa_tmp;
#endif
#ifndef OPENSSL_NO_DH
    dh = s->session->sess_cert->peer_dh_tmp;
#endif

    /* This is the passed certificate */

    idx = sc->peer_cert_type;
#ifndef OPENSSL_NO_ECDH
    if (idx == SSL_PKEY_ECC) {
        if (ssl_check_srvr_ecc_cert_and_alg(sc->peer_pkeys[idx].x509, s) == 0) {
            /* check failed */
            SSLerr(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM, SSL_R_BAD_ECC_CERT);
            goto f_err;
        } else {
            return 1;
        }
    }
#endif
    pkey = X509_get_pubkey(sc->peer_pkeys[idx].x509);
    pkey_bits = EVP_PKEY_bits(pkey);
    i = X509_certificate_type(sc->peer_pkeys[idx].x509, pkey);
    EVP_PKEY_free(pkey);

    /* Check that we have a certificate if we require one */
    if ((alg_a & SSL_aRSA) && !has_bits(i, EVP_PK_RSA | EVP_PKT_SIGN)) {
        SSLerr(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM,
               SSL_R_MISSING_RSA_SIGNING_CERT);
        goto f_err;
    }
#ifndef OPENSSL_NO_DSA
    else if ((alg_a & SSL_aDSS) && !has_bits(i, EVP_PK_DSA | EVP_PKT_SIGN)) {
        SSLerr(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM,
               SSL_R_MISSING_DSA_SIGNING_CERT);
        goto f_err;
    }
#endif
#ifndef OPENSSL_NO_RSA
    if ((alg_k & SSL_kRSA) &&
        !(has_bits(i, EVP_PK_RSA | EVP_PKT_ENC) || (rsa != NULL))) {
        SSLerr(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM,
               SSL_R_MISSING_RSA_ENCRYPTING_CERT);
        goto f_err;
    }
#endif
#ifndef OPENSSL_NO_DH
    if ((alg_k & SSL_kEDH) && dh == NULL) {
        SSLerr(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM, ERR_R_INTERNAL_ERROR);
        goto f_err;
    }
    if ((alg_k & SSL_kDHr) && !has_bits(i, EVP_PK_DH | EVP_PKS_RSA)) {
        SSLerr(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM,
               SSL_R_MISSING_DH_RSA_CERT);
        goto f_err;
    }
# ifndef OPENSSL_NO_DSA
    if ((alg_k & SSL_kDHd) && !has_bits(i, EVP_PK_DH | EVP_PKS_DSA)) {
        SSLerr(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM,
               SSL_R_MISSING_DH_DSA_CERT);
        goto f_err;
    }
# endif

    /* Check DHE only: static DH not implemented. */
    if (alg_k & SSL_kEDH) {
        int dh_size = BN_num_bits(dh->p);
        if ((!SSL_C_IS_EXPORT(s->s3->tmp.new_cipher) && dh_size < 768)
            || (SSL_C_IS_EXPORT(s->s3->tmp.new_cipher) && dh_size < 512)) {
            SSLerr(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM, SSL_R_DH_KEY_TOO_SMALL);
            goto f_err;
        }
    }
#endif  /* !OPENSSL_NO_DH */

    if (SSL_C_IS_EXPORT(s->s3->tmp.new_cipher) &&
        pkey_bits > SSL_C_EXPORT_PKEYLENGTH(s->s3->tmp.new_cipher)) {
#ifndef OPENSSL_NO_RSA
        if (alg_k & SSL_kRSA) {
            if (rsa == NULL
                || RSA_size(rsa) * 8 >
                SSL_C_EXPORT_PKEYLENGTH(s->s3->tmp.new_cipher)) {
                SSLerr(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM,
                       SSL_R_MISSING_EXPORT_TMP_RSA_KEY);
                goto f_err;
            }
        } else
#endif
#ifndef OPENSSL_NO_DH
        if (alg_k & (SSL_kEDH | SSL_kDHr | SSL_kDHd)) {
            if (dh == NULL
                || DH_size(dh) * 8 >
                SSL_C_EXPORT_PKEYLENGTH(s->s3->tmp.new_cipher)) {
                SSLerr(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM,
                       SSL_R_MISSING_EXPORT_TMP_DH_KEY);
                goto f_err;
            }
        } else
#endif
        {
            SSLerr(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM,
                   SSL_R_UNKNOWN_KEY_EXCHANGE_TYPE);
            goto f_err;
        }
    }
    return (1);
 f_err:
    ssl3_send_alert(s, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
 err:
    return (0);
}


static int remove_session_lock(SSL_CTX *ctx, SSL_SESSION *c, int lck)
{
    SSL_SESSION *r;
    int ret = 0;

    if ((c != NULL) && (c->session_id_length != 0)) {
        if (lck)
            CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX);
        if ((r = lh_SSL_SESSION_retrieve(ctx->sessions, c)) == c) {
            ret = 1;
            r = lh_SSL_SESSION_delete(ctx->sessions, c);
            SSL_SESSION_list_remove(ctx, c);
        }

        if (lck)
            CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);

        if (ret) {
            r->not_resumable = 1;
            if (ctx->remove_session_cb != NULL)
                ctx->remove_session_cb(ctx, r);
            SSL_SESSION_free(r);
        }
    } else
        ret = 0;
    return (ret);
}


void SSL_CTX_sess_set_new_cb(SSL_CTX *ctx,
                             int (*cb) (struct ssl_st *ssl,
                                        SSL_SESSION *sess))
{
    ctx->new_session_cb = cb;
}


SSL_SESSION *SSL_SESSION_new(void)
{
    SSL_SESSION *ss;

    ss = (SSL_SESSION *)OPENSSL_malloc(sizeof(SSL_SESSION));
    if (ss == NULL) {
        SSLerr(SSL_F_SSL_SESSION_NEW, ERR_R_MALLOC_FAILURE);
        return (0);
    }
    memset(ss, 0, sizeof(SSL_SESSION));

    ss->verify_result = 1;      /* avoid 0 (= X509_V_OK) just in case */
    ss->references = 1;
    ss->timeout = 60 * 5 + 4;   /* 5 minute timeout by default */
    ss->time = (unsigned long)time(NULL);
    ss->prev = NULL;
    ss->next = NULL;
    ss->compress_meth = 0;
#ifndef OPENSSL_NO_TLSEXT
    ss->tlsext_hostname = NULL;
# ifndef OPENSSL_NO_EC
    ss->tlsext_ecpointformatlist_length = 0;
    ss->tlsext_ecpointformatlist = NULL;
    ss->tlsext_ellipticcurvelist_length = 0;
    ss->tlsext_ellipticcurvelist = NULL;
# endif
#endif
    CRYPTO_new_ex_data(CRYPTO_EX_INDEX_SSL_SESSION, ss, &ss->ex_data);
#ifndef OPENSSL_NO_PSK
    ss->psk_identity_hint = NULL;
    ss->psk_identity = NULL;
#endif
#ifndef OPENSSL_NO_SRP
    ss->srp_username = NULL;
#endif
    return (ss);
}


int SSL_set_session(SSL *s, SSL_SESSION *session)
{
    int ret = 0;
    const SSL_METHOD *meth;

    if (session != NULL) {
        meth = s->ctx->method->get_ssl_method(session->ssl_version);
        if (meth == NULL)
            meth = s->method->get_ssl_method(session->ssl_version);
        if (meth == NULL) {
            SSLerr(SSL_F_SSL_SET_SESSION, SSL_R_UNABLE_TO_FIND_SSL_METHOD);
            return (0);
        }

        if (meth != s->method) {
            if (!SSL_set_ssl_method(s, meth))
                return (0);
        }
#ifndef OPENSSL_NO_KRB5
        if (s->kssl_ctx && !s->kssl_ctx->client_princ &&
            session->krb5_client_princ_len > 0) {
            s->kssl_ctx->client_princ =
                (char *)OPENSSL_malloc(session->krb5_client_princ_len + 1);
            memcpy(s->kssl_ctx->client_princ, session->krb5_client_princ,
                   session->krb5_client_princ_len);
            s->kssl_ctx->client_princ[session->krb5_client_princ_len] = '\0';
        }
#endif                          /* OPENSSL_NO_KRB5 */

        /* CRYPTO_w_lock(CRYPTO_LOCK_SSL); */
        CRYPTO_add(&session->references, 1, CRYPTO_LOCK_SSL_SESSION);
        if (s->session != NULL)
            SSL_SESSION_free(s->session);
        s->session = session;
        s->verify_result = s->session->verify_result;
        /* CRYPTO_w_unlock(CRYPTO_LOCK_SSL); */
        ret = 1;
    } else {
        if (s->session != NULL) {
            SSL_SESSION_free(s->session);
            s->session = NULL;
        }

        meth = s->ctx->method;
        if (meth != s->method) {
            if (!SSL_set_ssl_method(s, meth))
                return (0);
        }
        ret = 1;
    }
    return (ret);
}


int (*SSL_CTX_sess_get_new_cb(SSL_CTX *ctx)) (SSL *ssl, SSL_SESSION *sess) {
    return ctx->new_session_cb;
}


int SSL_SESSION_set_ex_data(SSL_SESSION *s, int idx, void *arg)
{
    return (CRYPTO_set_ex_data(&s->ex_data, idx, arg));
}


X509 *SSL_SESSION_get0_peer(SSL_SESSION *s)
{
    return s->peer;
}


long SSL_CTX_set_timeout(SSL_CTX *s, long t)
{
    long l;
    if (s == NULL)
        return (0);
    l = s->session_timeout;
    s->session_timeout = t;
    return (l);
}


void SSL_CTX_flush_sessions(SSL_CTX *s, long t)
{
    unsigned long i;
    TIMEOUT_PARAM tp;

    tp.ctx = s;
    tp.cache = s->sessions;
    if (tp.cache == NULL)
        return;
    tp.time = t;
    CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX);
    i = CHECKED_LHASH_OF(SSL_SESSION, tp.cache)->down_load;
    CHECKED_LHASH_OF(SSL_SESSION, tp.cache)->down_load = 0;
    lh_SSL_SESSION_doall_arg(tp.cache, LHASH_DOALL_ARG_FN(timeout),
                             TIMEOUT_PARAM, &tp);
    CHECKED_LHASH_OF(SSL_SESSION, tp.cache)->down_load = i;
    CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);
}


static void SSL_SESSION_list_add(SSL_CTX *ctx, SSL_SESSION *s)
{
    if ((s->next != NULL) && (s->prev != NULL))
        SSL_SESSION_list_remove(ctx, s);

    if (ctx->session_cache_head == NULL) {
        ctx->session_cache_head = s;
        ctx->session_cache_tail = s;
        s->prev = (SSL_SESSION *)&(ctx->session_cache_head);
        s->next = (SSL_SESSION *)&(ctx->session_cache_tail);
    } else {
        s->next = ctx->session_cache_head;
        s->next->prev = s;
        s->prev = (SSL_SESSION *)&(ctx->session_cache_head);
        ctx->session_cache_head = s;
    }
}


void SSL_CTX_set_client_cert_cb(SSL_CTX *ctx,
                                int (*cb) (SSL *ssl, X509 **x509,
                                           EVP_PKEY **pkey))
{
    ctx->client_cert_cb = cb;
}


static void SSL_SESSION_list_remove(SSL_CTX *ctx, SSL_SESSION *s)
{
    if ((s->next == NULL) || (s->prev == NULL))
        return;

    if (s->next == (SSL_SESSION *)&(ctx->session_cache_tail)) {
        /* last element in list */
        if (s->prev == (SSL_SESSION *)&(ctx->session_cache_head)) {
            /* only one element in list */
            ctx->session_cache_head = NULL;
            ctx->session_cache_tail = NULL;
        } else {
            ctx->session_cache_tail = s->prev;
            s->prev->next = (SSL_SESSION *)&(ctx->session_cache_tail);
        }
    } else {
        if (s->prev == (SSL_SESSION *)&(ctx->session_cache_head)) {
            /* first element in list */
            ctx->session_cache_head = s->next;
            s->next->prev = (SSL_SESSION *)&(ctx->session_cache_head);
        } else {
            /* middle of list */
            s->next->prev = s->prev;
            s->prev->next = s->next;
        }
    }
    s->prev = s->next = NULL;
}


int SSL_SESSION_set1_id_context(SSL_SESSION *s, const unsigned char *sid_ctx,
                                unsigned int sid_ctx_len)
{
    if (sid_ctx_len > SSL_MAX_SID_CTX_LENGTH) {
        SSLerr(SSL_F_SSL_SESSION_SET1_ID_CONTEXT,
               SSL_R_SSL_SESSION_ID_CONTEXT_TOO_LONG);
        return 0;
    }
    s->sid_ctx_length = sid_ctx_len;
    memcpy(s->sid_ctx, sid_ctx, sid_ctx_len);

    return 1;
}


SSL_SESSION *ssl_session_dup(SSL_SESSION *src, int ticket)
{
    SSL_SESSION *dest;

    dest = OPENSSL_malloc(sizeof(*src));
    if (dest == NULL) {
        goto err;
    }
    memcpy(dest, src, sizeof(*dest));

#ifndef OPENSSL_NO_KRB5
    dest->krb5_client_princ_len = dest->krb5_client_princ_len;
    if (src->krb5_client_princ_len > 0)
        memcpy(dest->krb5_client_princ, src->krb5_client_princ,
               src->krb5_client_princ_len);
#endif

#ifndef OPENSSL_NO_PSK
    if (src->psk_identity_hint) {
        dest->psk_identity_hint = BUF_strdup(src->psk_identity_hint);
        if (dest->psk_identity_hint == NULL) {
            goto err;
        }
    } else {
        dest->psk_identity_hint = NULL;
    }
    if (src->psk_identity) {
        dest->psk_identity = BUF_strdup(src->psk_identity);
        if (dest->psk_identity == NULL) {
            goto err;
        }
    } else {
        dest->psk_identity = NULL;
    }
#endif

    if (src->sess_cert != NULL)
        CRYPTO_add(&src->sess_cert->references, 1, CRYPTO_LOCK_SSL_SESS_CERT);

    if (src->peer != NULL)
        CRYPTO_add(&src->peer->references, 1, CRYPTO_LOCK_X509);

    dest->references = 1;

    if(src->ciphers != NULL) {
        dest->ciphers = sk_SSL_CIPHER_dup(src->ciphers);
        if (dest->ciphers == NULL)
            goto err;
    } else {
        dest->ciphers = NULL;
    }

    if (!CRYPTO_dup_ex_data(CRYPTO_EX_INDEX_SSL_SESSION,
                                            &dest->ex_data, &src->ex_data)) {
        goto err;
    }

    /* We deliberately don't copy the prev and next pointers */
    dest->prev = NULL;
    dest->next = NULL;

#ifndef OPENSSL_NO_TLSEXT
    if (src->tlsext_hostname) {
        dest->tlsext_hostname = BUF_strdup(src->tlsext_hostname);
        if (dest->tlsext_hostname == NULL) {
            goto err;
        }
    } else {
        dest->tlsext_hostname = NULL;
    }
# ifndef OPENSSL_NO_EC
    if (src->tlsext_ecpointformatlist) {
        dest->tlsext_ecpointformatlist =
            BUF_memdup(src->tlsext_ecpointformatlist,
                       src->tlsext_ecpointformatlist_length);
        if (dest->tlsext_ecpointformatlist == NULL)
            goto err;
        dest->tlsext_ecpointformatlist_length =
            src->tlsext_ecpointformatlist_length;
    }
    if (src->tlsext_ellipticcurvelist) {
        dest->tlsext_ellipticcurvelist =
            BUF_memdup(src->tlsext_ellipticcurvelist,
                       src->tlsext_ellipticcurvelist_length);
        if (dest->tlsext_ellipticcurvelist == NULL)
            goto err;
        dest->tlsext_ellipticcurvelist_length =
            src->tlsext_ellipticcurvelist_length;
    }
# endif
#endif

    if (ticket != 0) {
        dest->tlsext_tick_lifetime_hint = src->tlsext_tick_lifetime_hint;
        dest->tlsext_ticklen = src->tlsext_ticklen;
        if((dest->tlsext_tick = OPENSSL_malloc(src->tlsext_ticklen)) == NULL) {
            goto err;
        }
    }

#ifndef OPENSSL_NO_SRP
    dest->srp_username = NULL;
    if (src->srp_username) {
        dest->srp_username = BUF_strdup(src->srp_username);
        if (dest->srp_username == NULL) {
            goto err;
        }
    } else {
        dest->srp_username = NULL;
    }
#endif

    return dest;
err:
    SSLerr(SSL_F_SSL_SESSION_DUP, ERR_R_MALLOC_FAILURE);
    SSL_SESSION_free(dest);
    return NULL;
}