"Fossies" - the Fresh Open Source Software archive

Member "cftp-0.12/sftp.c" of archive cftp-0.12.tar.gz:


/*
  $NiH: sftp.c,v 1.22 2002/09/16 12:42:42 dillo Exp $

  sftp.c -- sftp protocol functions
  Copyright (C) 2001, 2002 Dieter Baron

  This file is part of cftp, a fullscreen ftp client
  The author can be contacted at <dillo@giga.or.at>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/



#include "config.h"
#ifdef USE_SFTP

#include <sys/types.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "directory.h"
#include "display.h"
#include "ftp.h"
#include "methods.h"
#include "status.h"
#include "util.h"



#define SFTP_MAX_PACKET_SIZE	32*1024
#define SFTP_HEADER_LEN		9	/* length + type + id */

#define SFTP_DATA_HEADER_LEN	SFTP_HEADER_LEN+4
					/* no of bytes before data in a
					   SSH_FXP_DATA packet */
#define SFTP_DATA_LEN		4096	/* data length used for read/write */

#define SFTP_FL_LOG		0x1	/* log packet */
#define SFTP_FL_IS_PATH		0x2	/* string is path (prepend cwd) */
#define SFTP_FL_NONBLOCK	0x4	/* don't block in i/o */
#define SFTP_FL_CONT		0x8	/* continue with packet */

struct packet {
    int type;		/* packet type */
    int id;		/* packet id */
    char *dat;		/* packet data (starting after id) */
    int plen;		/* length of packet data */
    int pn;		/* number of bytes read/write */
};

struct handle {
    int len;
    char *str;
};

enum sftp_file_state {
    SFTP_FS_EOF, SFTP_FS_ERROR, SFTP_FS_SEND, SFTP_FS_RECEIVE
};

struct sftp_file {
    int flags;
    struct handle *hnd;	/* sftp handle */
    size_t off;		/* current offset within file */
    enum sftp_file_state state;
    			/* current state */
    int doff;		/* offset to unreturned data */
    int dend;		/* end of data within current packet */
    int woff;		/* offset to length in write packet */
};



int sftp_file_close(struct handle *hnd);
struct handle *sftp_file_open(char *file, int flags);
struct handle *sftp_get_handle(void);
int sftp_put_handle(int type, struct handle *hnd, int flags);
int sftp_put_str(int type, char *str, int len, int flags);
int sftp_readdir(struct handle *hnd, struct packet *pkt, int flags);
int sftp_send_init(int proto_version);
int sftp_start_read(struct sftp_file *f, int nbytes);
int sftp_status(void);

static int _sftp_get_packet(struct packet *p, int flags);
static char *_sftp_get_string(char *buf, char **endp);
static unsigned int _sftp_get_uint32(char *p, char **endp);
static unsigned long long _sftp_get_uint64(char *p, char **endp);
static void _sftp_log_handle(char *buf, char *data, char **endp);
static void _sftp_log_packet(int dir, struct packet *pkt);
void _sftp_log_pflags(char *buf, char *data, char **endp);
static void _sftp_log_str(char *buf, char *data, char **endp);
static void _sftp_make_packet(struct packet *pkt, int type, char *end);
static int _sftp_parse_name(char *p, char **endp, direntry *e);
static int _sftp_parse_status(struct packet *pkt);
static void _sftp_put_handle(char *buf, char **endp, struct handle *hnd);
static int _sftp_put_packet(struct packet *pkt, int flags);
static void _sftp_put_string(char *buf, char **endp,
			     char *str, int slen, int flags);
static void _sftp_put_uint32(char *p, char **endp, unsigned int i);
static void _sftp_put_uint64(char *p, char **endp, unsigned long long i);
static int _sftp_read(int fd, void *buf, size_t nbytes, int flags);
static directory *_sftp_read_dir(struct handle *hnd);
static void _sftp_send_error_packet(char *fmt, ...);
static void _sftp_start_ssh(int fdin, int fdout);
int _sftp_start_write(struct sftp_file *f, int len);
static char *_sftp_strerror(int error);
static int _sftp_writev(int fd, struct iovec *iov, int niov, int flags);



#define SFTP_PROTO_VERSION	3

/* packet types */

#define SSH_FXP_INIT		1
#define SSH_FXP_VERSION		2
#define SSH_FXP_OPEN		3
#define SSH_FXP_CLOSE		4
#define SSH_FXP_READ		5
#define SSH_FXP_WRITE		6
#define SSH_FXP_LSTAT		7
#define SSH_FXP_FSTAT		8
#define SSH_FXP_SETSTAT		9
#define SSH_FXP_FSETSTAT	10
#define SSH_FXP_OPENDIR		11
#define SSH_FXP_READDIR		12
#define SSH_FXP_REMOVE		13
#define SSH_FXP_MKDIR		14
#define SSH_FXP_RMDIR		15
#define SSH_FXP_REALPATH	16
#define SSH_FXP_STAT		17
#define SSH_FXP_RENAME		18
#define SSH_FXP_STATUS		101
#define SSH_FXP_HANDLE		102
#define SSH_FXP_DATA		103
#define SSH_FXP_NAME		104
#define SSH_FXP_ATTRS		105
#define SSH_FXP_EXTENDED	200
#define SSH_FXP_EXTENDED_REPLY	201

/* attribute flag bits */

#define SSH_FILEXFER_ATTR_SIZE		0x00000001
#define SSH_FILEXFER_ATTR_UIDGID	0x00000002
#define SSH_FILEXFER_ATTR_PERMISSIONS	0x00000004
#define SSH_FILEXFER_ATTR_ACMODTIME	0x00000008
#define SSH_FILEXFER_ATTR_EXTENDED	0x80000000

/* open pflags bits */

#define SSH_FXF_READ		0x00000001
#define SSH_FXF_WRITE		0x00000002
#define SSH_FXF_APPEND		0x00000004
#define SSH_FXF_CREAT		0x00000008
#define SSH_FXF_TRUNC		0x00000010
#define SSH_FXF_EXCL		0x00000020

/* error/status code definitions */

#define SSH_FX_OK			0
#define SSH_FX_EOF			1
#define SSH_FX_NO_SUCH_FILE		2
#define SSH_FX_PERMISSION_DENIED	3
#define SSH_FX_FAILURE			4
#define SSH_FX_BAD_MESSAGE		5
#define SSH_FX_NO_CONNECTION		6
#define SSH_FX_CONNECTION_LOST		7
#define SSH_FX_OP_UNSUPPORTED		8



static char _sftp_buffer[SFTP_MAX_PACKET_SIZE];
static struct packet _sftp_packet_s;
#define _sftp_packet (&_sftp_packet_s)

static char *_sftp_errlist[] = {
    "OK",
    "End of file",
    "No such file or directory",
    "Permission denied",
    "Failure",
    "Bad message",
    "No connection",
    "Connection lost",
    "Operation unsupported"
};

static int _sftp_nerr = sizeof(_sftp_errlist) / sizeof(_sftp_errlist[0]);

static int _sftp_nextid;	/* id to use in next packet */
static pid_t _sftp_ssh_pid;	/* PID of ssh subprocess */
static int _conin, _conout;	/* file descriptors to ssh subprocess */



/*
  Implements mkdir method.
*/

int
sftp_mkdir(char *path)
{
    sftp_put_str(SSH_FXP_MKDIR, path, 0, SFTP_FL_LOG|SFTP_FL_IS_PATH);
    if (sftp_status() != SSH_FX_OK)
	return -1;
    return 0;
}



/*
  Implements rmdir method.
*/

int
sftp_rmdir(char *path)
{
    sftp_put_str(SSH_FXP_RMDIR, path, 0, SFTP_FL_LOG|SFTP_FL_IS_PATH);
    if (sftp_status() != SSH_FX_OK)
	return -1;
    return 0;
}



/*
  Implements list method.
*/

directory *
sftp_list(char *path)
{
    directory *dir;
    struct handle *hnd;

    sftp_put_str(SSH_FXP_OPENDIR, path, 0, SFTP_FL_LOG|SFTP_FL_IS_PATH);

    if ((hnd=sftp_get_handle()) == NULL)
	return NULL;

    dir = _sftp_read_dir(hnd);
    if (dir)
	dir->path = strdup(path);
    
    sftp_file_close(hnd);
    
    return dir;
}



static directory *
_sftp_read_dir(struct handle *hnd)
{
    directory *dir;
    direntry entry;
    char *p;
    time_t oldt, newt;
    int n, pn;

    dir = dir_new();
    oldt = 0;

    n = 0;
    while (sftp_readdir(hnd, _sftp_packet, 0) == 0
	   && _sftp_packet->type == SSH_FXP_NAME) {
	pn = _sftp_get_uint32(_sftp_packet->dat, &p);
	while (pn--) {
	    if (_sftp_parse_name(p, &p, &entry) == 0) {
		if (strcmp(entry.name, ".") == 0
		    || strcmp(entry.name, "..") == 0) {
		    free(entry.name);
		    free(entry.line);
		    continue;
		}
		dir_add(dir, &entry);
		n++;
	    }

	    if ((newt=time(NULL)) != oldt) {
		disp_status(DISP_STATUS, "listed %d", n);
		oldt = newt;
	    }
	}
    }

    if (_sftp_parse_status(_sftp_packet) != SSH_FX_EOF)
	_sftp_log_packet(1, _sftp_packet);

    return dir;
}    



/*
  Return string corresponding to error number ERROR.  This is a static
  string that remains valid until next call to _sftp_strerror.
*/

static char *
_sftp_strerror(int error)
{
    static char buf[128];

    if (error < 0 || error >= _sftp_nerr) {
	sprintf(buf, "Unknown error %d", error);
	return buf;
    }

    return _sftp_errlist[error];
}



/*
  Read packet into P.  Return -1 in case of error, 0 if packet is
  complete, 1 if not completely read (non-blocking case).
*/

static int
_sftp_get_packet(struct packet *p, int flags)
{
    int n, off;
    char *cp;

    if (_conin < 0)
	return -1;

    if (!(flags & SFTP_FL_CONT)) {
	p->plen = 0;
	p->pn = 0;
    }

    if (p->pn < SFTP_HEADER_LEN) {
	/* read length and type */
	if ((n=_sftp_read(_conin, p->dat+p->pn, SFTP_HEADER_LEN-p->pn,
			  flags)) < 0)
	    return -1;

	p->pn += n;

	if (p->pn < SFTP_HEADER_LEN)
	    return 1;

	p->plen = _sftp_get_uint32(p->dat, &cp);
	p->type = *(cp++);
	p->id = _sftp_get_uint32(cp, &cp);
	p->plen -= SFTP_HEADER_LEN - 4;
    }

    off = p->pn - SFTP_HEADER_LEN;
    if ((n=_sftp_read(_conin, p->dat+off, p->plen-off, flags)) < 0)
	return -1;

    p->pn += n;

    if (off+n < p->plen)
	return 1;

    if (flags & SFTP_FL_LOG)
	_sftp_log_packet(1, p);

    return 0;
}



static void
_sftp_log_packet(int dir, struct packet *pkt)
{
    static char *req[] = {
	"init %I",
	"version %I",
	"open %s %p %a",
	"close %h",
	"read %h @ %l + %i",
	"write %h @ %l + %i",
	"lstat %s",
	"fstat %h",
	"setstat, %s %a",
	"fsetstat %h %a",
	"opendir %s",
	"readdir %h",
	"remove %s",
	"mkdir %s",
	"rmdir %s",
	"realpath %s",
	"stat %s",
	"rename %s %s"
    };
    static char *rsp[] = {
	"status %m",
	"handle %h",
	"data %i",
	"name %i %s",
	"attrs %a"
    };
    static char *ext[] = {
	"extended %s",
	"extended-reply"
    };
    static char *dir_pre[] = { "-> ", "<= " };

    char buf[8192], *fmt, *bp, *fp, *fq, *pp;
    int n;

    if (pkt->type >= SSH_FXP_INIT && pkt->type <= SSH_FXP_RENAME)
	fmt = req[pkt->type-SSH_FXP_INIT];
    else if (pkt->type >= SSH_FXP_STATUS && pkt->type <= SSH_FXP_ATTRS)
	fmt = rsp[pkt->type-SSH_FXP_STATUS];
    else if (pkt->type >= SSH_FXP_EXTENDED
	     && pkt->type <= SSH_FXP_EXTENDED_REPLY) {
	fmt = ext[pkt->type-SSH_FXP_EXTENDED];
    }
    else
	fmt = "unknown packet type %t";

    strcpy(buf, dir_pre[dir!=0]);
    bp = buf+strlen(buf);

    if (pkt->type != SSH_FXP_INIT && pkt->type != SSH_FXP_VERSION) {
	sprintf(bp, "[%d] ", pkt->id);
	bp += strlen(bp);
    }

    /* XXX: check for buf overrun */

    fp=fmt;
    pp = pkt->dat;
    while ((fq=strchr(fp, '%'))) {
	n = fq-fp;
	if (n) {
	    strncpy(bp, fp, n);
	    bp += n;
	}

	switch (fq[1]) {
	case 'a':
	    *bp = '\0';
	    break;
	case 'h':
	    _sftp_log_handle(bp, pp, &pp);
	    break;
	case 'I':
	    sprintf(bp, "%i", pkt->id);
	    break;
	case 'i':
	    sprintf(bp, "%u", _sftp_get_uint32(pp, &pp));
	    break;
	case 'l':
	    pp += 8;
	    break;
	case 'm':
	    strcpy(bp, _sftp_strerror(_sftp_get_uint32(pp, &pp)));
	    break;
	case 'p':
	    _sftp_log_pflags(bp, pp, &pp);
	    break;
	case 's':
	    _sftp_log_str(bp, pp, &pp);
	    break;
	case 'T':
	    sprintf(bp, "%u", pkt->type);
	    break;
	default:
	    bp[0] = '%';
	    bp[1] = fq[1];
	    break;
	}

	fp = fq+2;
	bp += strlen(bp);
    }
    strcpy(bp, fp);

    disp_status(DISP_PROTO, "%s", buf);

    if (pkt->type == SSH_FXP_INIT || pkt->type == SSH_FXP_VERSION) {
	bp = buf+3;
	while (pp < pkt->dat+pkt->plen) {
	    _sftp_log_str(bp, pp, &pp);
	    disp_status(DISP_HIST, "%s", buf);
	    pp += _sftp_get_uint32(pp, NULL) + 4;
	}
    }

}



static int
_sftp_parse_name(char *p, char **endp, direntry *e)
{
    unsigned int flags;
    int n, len;
    mode_t mode;

    e->name = _sftp_get_string(p, &p);

    len = _sftp_get_uint32(p, &p);
    e->line = malloc(len+2);
    e->line[0] = ' ';
    strncpy(e->line+1, p, len);
    e->line[len+1] = '\0';
    p += len;
    
    e->link = NULL;

    flags = _sftp_get_uint32(p, &p);

    if (flags & SSH_FILEXFER_ATTR_SIZE) {
	e->size = _sftp_get_uint64(p, &p);
    }
    else
	e->size = -1;

    if (flags & SSH_FILEXFER_ATTR_UIDGID)
	p += 8;

    if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
	mode = _sftp_get_uint32(p, &p);

	switch (mode & S_IFMT) {
	case S_IFDIR:
	    e->type = 'd';
	    break;
	case S_IFREG:
	case S_IFIFO:
	    e->type = 'f';
	    break;
	case S_IFLNK:
	    e->type = 'l';
	    break;
	default:
	    e->type = 'x';
	}
    }
    else
	e->type = 'x';

    if (flags & SSH_FILEXFER_ATTR_ACMODTIME) {
	p += 4; /* skip atime */
	e->mtime = _sftp_get_uint32(p, &p);
    }
    else
	e->mtime = 0;

    if (flags & SSH_FILEXFER_ATTR_EXTENDED) {
	n = _sftp_get_uint32(p, &p);

	while (n--) {
	    p += _sftp_get_uint32(p, NULL) + 4;
	    p += _sftp_get_uint32(p, NULL) + 4;
	}
    }

    if (endp)
	*endp = p;

    return 0;
}



/*
  Return uint32 at P.  If END is non-NULL, point *END to next byte to
  be processed.
*/

static unsigned int
_sftp_get_uint32(char *p, char **end)
{
    unsigned char *up;
    
    if (end)
	*end = p+4;

    up = (unsigned char *)p;

    return (up[0]<<24) | (up[1]<<16) | (up[2]<<8) | up[3];
}



/*
  Return uint64 at P.  If END is non-NULL, point *END to next byte to
  be processed.
*/

static unsigned long long
_sftp_get_uint64(char *p, char **end)
{
    unsigned char *up;
    
    if (end)
	*end = p+8;
    
    up = (unsigned char *)p;

    return ((unsigned long long)up[0]<<56) | ((unsigned long long)up[1]<<48)
	| ((unsigned long long)up[2]<<40) | ((unsigned long long)up[3]<<32)
	| (up[4]<<24) | (up[5]<<16) | (up[6]<<8) | up[7];
}



/*
  Return string at P (malloc()ed, NUL-terminated.  If END is non-NULL,
  point *END to next byte to be processed.
*/

static char *
_sftp_get_string(char *buf, char **endp)
{
    char *s, *p;
    int len;

    len = _sftp_get_uint32(buf, &p);
    if (!len)
	return NULL;

    if ((s=malloc(len+1)) == NULL)
	return NULL;

    memcpy(s, p, len);
    s[len] = '\0';

    if (endp)
	*endp = p+len;

    return s;
}



/*
  Read status packet and return status code.  If packet read is not a
  status packet, or if an error occurs, return -1.
*/

int
sftp_status(void)
{
    if (_sftp_get_packet(_sftp_packet, SFTP_FL_LOG) < 0)
	return -1;

    return _sftp_parse_status(_sftp_packet);
}



/*
  Return status code from status packet.  If packet is not a status
  packet, return -1.
*/

static int
_sftp_parse_status(struct packet *pkt)
{
    if (pkt->type != SSH_FXP_STATUS || pkt->plen < 4)
	return -1;

    return _sftp_get_uint32(pkt->dat, NULL);
}



/*
  Read handle packet and return handle.  If packet read is not a
  handle packet or if an error occurs, return NULL.
*/

struct handle *
sftp_get_handle(void)
{
    struct handle *hnd;
    char *p;

    if (_sftp_get_packet(_sftp_packet, SFTP_FL_LOG) < 0
	|| _sftp_packet->type != SSH_FXP_HANDLE)
	return NULL;

    hnd = malloc(sizeof(*hnd));

    hnd->len = _sftp_get_uint32(_sftp_packet->dat, &p);
    hnd->str = malloc(hnd->len);
    memcpy(hnd->str, p, hnd->len);
    
    return hnd;
}



/*
  Close file associated with HND on server.  Return status returned
  from server.
*/

int
sftp_file_close(struct handle *hnd)
{
    sftp_put_handle(SSH_FXP_CLOSE, hnd, SFTP_FL_LOG);

    free(hnd->str);
    free(hnd);

    return sftp_status();
}



/*
  Send a readdir request on HND, and read response into PKT.  Return
  value returned by _sftp_get_packet.
*/

int
sftp_readdir(struct handle *hnd, struct packet *pkt, int flags)
{
    sftp_put_handle(SSH_FXP_READDIR, hnd, flags);

    return _sftp_get_packet(pkt, flags);
}



/*
  Send a packet of type TYPE, containing only string STR.
*/

int
sftp_put_str(int type, char *str, int slen, int flags)
{
    char *p;

    p = _sftp_packet->dat;
    _sftp_put_string(p, &p, str, slen, flags);
    _sftp_make_packet(_sftp_packet, type, p);
    
    return _sftp_put_packet(_sftp_packet, flags&~SFTP_FL_CONT);
}



/*
  Put unit32 I at P.  If END is non-NULL, point *END to next byte to
  be filled.  */

static void
_sftp_put_uint32(char *p, char **end, unsigned int i)
{
    unsigned char *q;
    
    if (end)
	*end = p+4;

    q = (unsigned char *)p;
    
    q[0] = (i>>24) & 0xff;
    q[1] = (i>>16) & 0xff;
    q[2] = (i>>8) & 0xff;
    q[3] = i & 0xff;
}



/*
  Put unit64 I at P.  If END is non-NULL, point *END to next byte to
  be filled.
*/

static void
_sftp_put_uint64(char *p, char **end, unsigned long long i)
{
    unsigned char *q;
    
    if (end)
	*end = p+8;

    q = (unsigned char *)p;

    q[0] = (i>>56) & 0xff;
    q[1] = (i>>48) & 0xff;
    q[2] = (i>>40) & 0xff;
    q[3] = (i>>32) & 0xff;
    q[4] = (i>>24) & 0xff;
    q[5] = (i>>16) & 0xff;
    q[6] = (i>>8) & 0xff;
    q[7] = i & 0xff;
}



/*
  Write packet PKG.  If PKG->id is -1, use next unused id.  Return -1
  in case of error, 0 if packet was completely written, 1 else.
*/

static int
_sftp_put_packet(struct packet *pkt, int flags)
{
    char b[SFTP_HEADER_LEN], *p;
    struct iovec iov[2];
    int niov, n, off;
    
    niov = 0;

    if (!(flags & SFTP_FL_CONT)) {
	if (pkt->id == -1)
	    pkt->id = _sftp_nextid++;
	pkt->pn = 0;
    }

    if (pkt->pn < SFTP_HEADER_LEN) {
	_sftp_put_uint32(b, &p, pkt->plen + SFTP_HEADER_LEN-4);
	*(p++) = pkt->type;
	_sftp_put_uint32(p, &p, pkt->id);

	iov[niov].iov_base = b+pkt->pn;
	iov[niov].iov_len = SFTP_HEADER_LEN-pkt->pn;
	niov++;
    }

    off = pkt->pn-SFTP_HEADER_LEN;
    if (off < 0)
	off = 0;
	
    if (pkt->plen) {
	iov[niov].iov_base = pkt->dat + off;
	iov[niov].iov_len = pkt->plen - off;
	niov++;
    }
    
    if ((n=_sftp_writev(_conout, iov, niov, flags)) < 0)
	return -1;

    pkt->pn += n;

    if (pkt->pn < pkt->plen+SFTP_HEADER_LEN)
	return 1;

    if (flags & SFTP_FL_LOG)
	_sftp_log_packet(0, pkt);

    return 0;
}



/*
  Implements login method.
*/

int
sftp_login(char *user, char *pass)
{
    /* XXX: handle user != NULL */

    status.host = mkhoststr(0, 0);
    
    return 0;
}



/*
  Implements open method.
*/

int
sftp_open(char *host, char *port, char *user, char *pass)
{
    int pin[2], pout[2];
    pid_t pid;

    ftp_remember_host(host, port);
    ftp_remember_user(user, pass);

    _sftp_packet->dat = _sftp_buffer;

    if (pipe(pin) != 0 || pipe(pout) != 0) {
	disp_status(DISP_ERROR, "cannot create pipes: %s",
		    strerror(errno));
	return -1;
    }

    switch ((pid=fork())) {
    case -1:
	disp_status(DISP_ERROR, "cannot fork: %s", strerror(errno));
	return -1;

    case 0:
	close(pin[0]);
	close(pout[1]);
	_sftp_start_ssh(pout[0], pin[1]);
	/* NOTREACHED */

    default:
	_sftp_ssh_pid = pid;
	
	close(pin[1]);
	close(pout[0]);

	_conin = pin[0];
	_conout = pout[1];

	fcntl(_conin, F_SETFD, 1);
	fcntl(_conout, F_SETFD, 1);
	
	/* XXX: check for minmal server version */
	if (sftp_send_init(SFTP_PROTO_VERSION) < 0) {
	    sftp_close();
	    return -1;
	}
	return 0;
    }
}



/*
  Set up environment and exec ssh.  In case of error, write status
  packet to pipe and exit(0).
*/

static void
_sftp_start_ssh(int fdin, int fdout)
{
    char *args[20];
    int n;

    signal(SIGPIPE, SIG_DFL);

    close(0);
    close(1);

    /* for use in _sftp_send_error_packet() */
    _conout = fdout;

    if (dup(fdin) != 0 || dup(fdout) != 1) {
	_sftp_send_error_packet("cannot dup: %s", strerror(errno));
	exit(0);
    }

    close(fdin);
    close(fdout);

    n = 0;
    args[n++] = "ssh";
    args[n++] = "-2";
    args[n++] = "-s";
    if (ftp_prt()) {
	args[n++] = "-p";
	args[n++] = ftp_prt();
    }
    if (ftp_user()) {
	args[n++] = "-l";
	args[n++] = ftp_user();
    }
    args[n++] = ftp_host();
    args[n++] = "sftp";
    args[n++] = NULL;

    execvp("ssh", args);
    _sftp_send_error_packet("exec failed: %s", strerror(errno));
    exit(0);
}



/*
  Implements close method.
*/

int
sftp_close(void)
{
    int status;
    
    if (_conin >= 0) {
	close(_conin);
	_conin = -1;
    }
    if (_conout >= 0) {
	close(_conout);
	_conout = -1;
    }
    if (_sftp_ssh_pid != 0) {
	if (waitpid(_sftp_ssh_pid, &status, 0) == -1) {
	    disp_status(DISP_ERROR, "cannot wait on ssh (pid %d): %s",
			(int)_sftp_ssh_pid, strerror(errno));
	    return -1;
	}
	if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
	    disp_status(DISP_ERROR, "ssh exited with %d",
			WEXITSTATUS(status));
	    return -1;
	}
	else if (WIFSIGNALED(status)) {
	    disp_status(DISP_ERROR, "ssh exited due to signal %d",
			WTERMSIG(status));
	    return -1;
	}
    }

    return 0;
}



/*
  Send init packet and read reply.  Return protocol version from
  reply, or -1 in case of error or if response packet is not of type
  version.
*/

int
sftp_send_init(int proto_version)
{
    _sftp_make_packet(_sftp_packet, SSH_FXP_INIT, _sftp_packet->dat);
    _sftp_packet->id = proto_version;

    if (_sftp_put_packet(_sftp_packet, SFTP_FL_LOG) < 0)
	return -1;
    
    if (_sftp_get_packet(_sftp_packet, SFTP_FL_LOG) < 0
	|| _sftp_packet->type != SSH_FXP_VERSION)
	return -1;

    return _sftp_packet->id;
}



/*
  Implements pwd method.
*/

char *
sftp_pwd(void)
{
    char *dir;
    
    if (sftp_put_str(SSH_FXP_REALPATH, ftp_lcwd ? ftp_lcwd : "", 0,
		     SFTP_FL_LOG) < 0)
	return NULL;
    
    if (_sftp_get_packet(_sftp_packet, SFTP_FL_LOG) <0
	|| _sftp_packet->type != SSH_FXP_NAME)
	return NULL;

    /* XXX: magic number */
    dir = _sftp_get_string(_sftp_buffer+4, NULL);

    if (dir) {
	free(ftp_lcwd);
	ftp_lcwd = strdup(dir);
    }

    return dir;
}



static void
_sftp_log_str(char *buf, char *data, char **endp)
{
    int len;
    char *p;

    len = _sftp_get_uint32(data, &p);
    sprintf(buf, "\"%.*s\"", len, p);

    if (endp)
	*endp = p+len;
}



static void
_sftp_log_handle(char *buf, char *data, char **endp)
{
    int i, len;
    char *p;

    len = _sftp_get_uint32(data, &p);
    *(buf++) = '<';

    for (i=0; i<len; i++) {
#define HEX_DIGIT(x) ((x) < 10 ? (x)+'0' : (x)+'a')
	*(buf++) = HEX_DIGIT(p[i]>>4);
	*(buf++) = HEX_DIGIT(p[i]&0xf);
#undef HEX_DIGIT
    }
    strcpy(buf, ">");

    if (endp)
	*endp = p+len;
}



/*
  Put string representation of open pflags at DATA into BUF.  If ENDP
  is non-NULL, point it at next unprocessed byte.
*/

void
_sftp_log_pflags(char *buf, char *data, char **endp)
{
    static char *fl[] = {
	"read", "write", "append", "creat", "trunc", "excl"
    };
    
    int flags, i, n;

    flags = _sftp_get_uint32(data, endp);

    n = 0;
    *(buf++) = '(';
    for (i=0; i<sizeof(fl)/sizeof(fl[0]); i++) {
	if (flags & (1<<i)) {
	    sprintf(buf, "%s%s", (n ? ", " : ""), fl[i]);
	    buf += strlen(buf);
	    n = 1;
	}
    }
    strcpy(buf, ")");
}



/*
  Send a packet of type TYPE, containing only string HND.
*/

int
sftp_put_handle(int type, struct handle *hnd, int flags)
{
    return sftp_put_str(type, hnd->str, hnd->len, flags & ~SFTP_FL_IS_PATH);
}



/*
  Implements deidle method.
*/

int
sftp_deidle(void)
{
    /* XXX: deidle connection */
    return 0;
}



/*
  Implements retr method.
*/

void *
sftp_retr(char *file, int mode, long *startp, long *sizep)
{
    struct handle *hnd;
    struct sftp_file *f;

    if ((hnd=sftp_file_open(file, SSH_FXF_READ)) == NULL)
	return NULL;
    if ((f=malloc(sizeof(*f))) == NULL)
	return NULL;

    f->hnd = hnd;
    f->flags = SSH_FXF_READ;
    if (startp)
	f->off = *startp;
    else
	f->off = 0;

    return f;
}



/*
  Implements stor method.
*/

void *
sftp_stor(char *file, int mode)
{
    struct handle *hnd;
    struct sftp_file *f;

    if ((f=malloc(sizeof(*f))) == NULL)
	return NULL;

    if ((hnd=sftp_file_open(file, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_TRUNC))
	== NULL) {
	free(f);
	return NULL;
    }

    f->hnd = hnd;
    f->flags = SSH_FXF_WRITE;
    f->off = 0;

    return f;
}



/*
  Implements fclose method.
*/

int
sftp_fclose(void *f)
{
    int ret;
    struct sftp_file *file;

    file = (struct sftp_file *)f;

    ret = sftp_file_close(file->hnd);
    free(file);
    
    return ret;
}



/*
  Implements site method.
*/

int
sftp_site(char *cmd)
{
    /* map to extended somehow? */
    /* what return value? */
    return 0;
}



/*
  Implements cwd method.
*/

int
sftp_cwd(char *path)
{
    int flags, off;
    mode_t mode;
    
    if (sftp_put_str(SSH_FXP_STAT, path, 0, SFTP_FL_LOG|SFTP_FL_IS_PATH) < 0)
	return -1;
    if (_sftp_get_packet(_sftp_packet, SFTP_FL_LOG) < 0
	|| _sftp_packet->type != SSH_FXP_ATTRS)
	return -1;

    flags = _sftp_get_uint32(_sftp_packet->dat, NULL);

    /* XXX: magic number */
    if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
	off = 4 + (flags & SSH_FILEXFER_ATTR_SIZE ? 8 : 0)
	    + (flags & SSH_FILEXFER_ATTR_UIDGID ? 8 : 0);
	mode = _sftp_get_uint32(_sftp_packet->dat+off, NULL);

	if ((mode & S_IFMT) != S_IFDIR)
	    return -1;
    }

    free(ftp_pcwd);
    ftp_pcwd = strdup(path);
    
    return 0;
}



/*
  Open file FILE on server with FLAGS.  Return handle to file, NULL in
  case of error.
*/

struct handle *
sftp_file_open(char *file, int flags)
{
    char *p;

    p = _sftp_packet->dat;
    _sftp_put_string(p, &p, file, 0, SFTP_FL_IS_PATH);
    _sftp_put_uint32(p, &p, flags);
    _sftp_put_uint32(p, &p, 0);
    _sftp_make_packet(_sftp_packet, SSH_FXP_OPEN, p);

    if (_sftp_put_packet(_sftp_packet, SFTP_FL_LOG) < 0)
	return NULL;

    return sftp_get_handle();
}



/*
  Put string STR of length SLEN at BUF.  If SLEN is 0, STR is assumed
  to be NUL-terminated.  If SFTP_FL_IS_PATH is set in FLAGS, prepend
  cwd.  If ENDP is non-NULL, point it at next byte to be filled.
*/
  
static void
_sftp_put_string(char *buf, char **endp, char *str, int slen, int flags)
{
    int len;
    char *p;

    p = buf+4;
    len = 0;
    if ((flags & SFTP_FL_IS_PATH) && str[0] != '/') {
	strcpy(p+len, ftp_lcwd);
	len += strlen(ftp_lcwd);
	p[len++] = '/';
    }
    if (!slen)
	slen = strlen(str);
    memcpy(p+len, str, slen);
    len += slen;

    _sftp_put_uint32(buf, NULL, len);

    if (endp)
	*endp = p+len;
}



/*
  Implements xfer_read method.
*/

int
sftp_xfer_read(void *buf, size_t nbytes, void *file)
{
    struct sftp_file *f;
    int n, nret, ret;

    f = file;
    nret = 0;

    while (nret < nbytes) {
	switch (f->state) {
	case SFTP_FS_EOF:
	case SFTP_FS_ERROR:
	    if (nret)
		return nret;
	    else
		return -1;
	    
	case SFTP_FS_SEND:
	    ret = _sftp_put_packet(_sftp_packet,
				   SFTP_FL_NONBLOCK|SFTP_FL_CONT);

	    if (ret < 0) {
		f->state = SFTP_FS_ERROR;
		break;
	    }
	    else if (ret == 1)
		return nret;

	    /* reset packet */
	    _sftp_make_packet(_sftp_packet, 0, _sftp_packet->dat);
	    f->doff = f->dend = 0;
	    f->state = SFTP_FS_RECEIVE;
	    break;
	    
	case SFTP_FS_RECEIVE:
	    ret = _sftp_get_packet(_sftp_packet,
				   SFTP_FL_NONBLOCK|SFTP_FL_CONT);

	    if (ret < 0) {
		f->state = SFTP_FS_ERROR;
		break;
	    }
	    else if (_sftp_packet->pn < SFTP_DATA_HEADER_LEN)
		return nret;
	    
	    if (_sftp_packet->type == SSH_FXP_DATA) {
		if (f->doff == 0) {
		    f->doff = SFTP_DATA_HEADER_LEN - SFTP_HEADER_LEN;
		    f->dend = f->doff
			+ _sftp_get_uint32(_sftp_packet->dat, NULL);
		}
	    }
	    else {
		if (_sftp_packet->type == SSH_FXP_STATUS
		    && _sftp_get_uint32(_sftp_packet->dat, NULL) == SSH_FX_EOF)
		    f->state = SFTP_FS_EOF;
		else {
		    _sftp_log_packet(1, _sftp_packet);
		    f->state = SFTP_FS_ERROR;
		}
		break;
	    }

	    n = _sftp_packet->pn - SFTP_HEADER_LEN;
	    if (n > f->dend)
		n = f->dend;
	    n -= f->doff;
	    if (n > nbytes-nret)
		n = nbytes-nret;
	    
	    memcpy(((char *)buf)+nret, _sftp_packet->dat+f->doff, n);
	    f->doff += n;
	    nret += n;

	    if (ret == 0 && f->doff >= f->dend)
		sftp_start_read(f, SFTP_DATA_LEN);
	}
    }

    return nret;
}



/*
  Implements xfer_start method.
*/

int
sftp_xfer_start(void *file)
{
    struct sftp_file *f;

    f = file;

    if (f->flags & SSH_FXF_WRITE)
	_sftp_start_write(f, SFTP_DATA_LEN);
    else {
	if (sftp_start_read(f, SFTP_DATA_LEN) < 0)
	    return -1;
    }
    
    set_file_blocking(_conin, 0);
    set_file_blocking(_conout, 0);

    return 0;
}



/*
  Implements xfer_stop method.
*/

int
sftp_xfer_stop(void *file, int aborting)
{
    struct sftp_file *f;
    int n;

    set_file_blocking(_conin, 1);
    set_file_blocking(_conout, 1);

    f = file;

    if (aborting) {
	/* don't send packet unless already begun */
	if (f->state == SFTP_FS_SEND && _sftp_packet->pn == 0)
	    f->state = SFTP_FS_EOF;
    }
    
    if ((f->flags & SSH_FXF_WRITE
	 && f->state == SFTP_FS_SEND
	 && f->doff < f->dend)) {
	/* fix up incomplete write packet */
	n = f->doff - (f->woff+4);
	if (n == 0)
	    f->state = SFTP_FS_EOF;
	else {
	    _sftp_packet->plen = f->doff;
	    _sftp_put_uint32(_sftp_buffer+f->woff, NULL, n);
	}
    }

    while (f->state == SFTP_FS_SEND) {
	switch (_sftp_put_packet(_sftp_packet, SFTP_FL_CONT)) {
	case -1:
	    return -1;
	case 0:
	    f->state = SFTP_FS_RECEIVE;
	    _sftp_make_packet(_sftp_packet, 0, _sftp_packet->dat);
	}
    }
    while (f->state == SFTP_FS_RECEIVE) {
	switch (_sftp_get_packet(_sftp_packet, SFTP_FL_CONT)) {
	case -1:
	    return -1;

	case 0:
	    f->state = SFTP_FS_EOF;
	    if (_sftp_parse_status(_sftp_packet) != SSH_FX_OK) {
		_sftp_log_packet(1, _sftp_packet);
		return -1;
	    }
	}
    }
    
    return 0;
}



/*
  Implements xfer_write method.
*/

int
sftp_xfer_write(void *buf, size_t nbytes, void *file)
{
    struct sftp_file *f;
    int n, type, nret, ret;

    f = file;
    nret = 0;

    while (nret < nbytes) {
	switch (f->state) {
	case SFTP_FS_EOF:
	case SFTP_FS_ERROR:
	    if (nret)
		return nret;
	    else
		return -1;
	    
	case SFTP_FS_SEND:
	    n = f->dend - f->doff;
	    if (n > nbytes-nret)
		n = nbytes-nret;

	    if (n) {
		memcpy(_sftp_packet->dat+f->doff, buf, n);
		f->doff += n;
		nret += n;
	    }

	    if (f->doff >= f->dend) {
		
		ret = _sftp_put_packet(_sftp_packet,
				     SFTP_FL_NONBLOCK|SFTP_FL_CONT);

		if (ret < 0) {
		    f->state = SFTP_FS_ERROR;
		    break;
		}
		else if (ret == 1)
		    return nret;

		f->off += _sftp_get_uint32(_sftp_buffer+f->woff, NULL);
		_sftp_make_packet(_sftp_packet, 0, _sftp_packet->dat);
		f->state = SFTP_FS_RECEIVE;
	    }

	    break;
	    
	case SFTP_FS_RECEIVE:
	    ret = _sftp_get_packet(_sftp_packet,
				   SFTP_FL_NONBLOCK|SFTP_FL_CONT);
	    
	    if (ret < 0) {
		f->state = SFTP_FS_ERROR;
		break;
	    }
	    else if (ret == 1)
		return nret;
	    
	    type = _sftp_buffer[4];
	    
	    if (_sftp_parse_status(_sftp_packet) != SSH_FX_OK) {
		_sftp_log_packet(1, _sftp_packet);
		f->state = SFTP_FS_ERROR;
	    }

	    _sftp_start_write(f, SFTP_DATA_LEN);
	    break;
	}
    }

    return nret;
}



/*
  Implements xfer_eof method.
*/

int
sftp_xfer_eof(void *file)
{
    struct sftp_file *f;

    f = file;

    return f->state == SFTP_FS_EOF;
}



/*
  Start read cycle: prepare read packet for NBYTES bytes, step offest
  of F, and put F in state receive.
*/

int
sftp_start_read(struct sftp_file *f, int nbytes)
{
    char *p;

    p = _sftp_packet->dat;
    _sftp_put_handle(p, &p, f->hnd);
    _sftp_put_uint64(p, &p, f->off);
    _sftp_put_uint32(p, &p, nbytes);
    _sftp_make_packet(_sftp_packet, SSH_FXP_READ, p);
    _sftp_packet->id = _sftp_nextid++;

    f->state = SFTP_FS_SEND;
    f->off += nbytes;

    return 0;
}



/*
  Put handle HND at BUF.  If ENDP is non-NULL, point it at next byte
  to be filled.
*/
  
static void
_sftp_put_handle(char *buf, char **endp, struct handle *hnd)
{
    _sftp_put_string(buf, endp, hnd->str, hnd->len, 0);
}



static int
_sftp_read(int fd, void *buf, size_t nbytes, int flags)
{
    int n, nret;
    fd_set fdset;

    nret = 0;
    while (nbytes) {
	n = read(fd, ((char *)buf)+nret, nbytes);

	if (n < 0) {
	    if (errno == EINTR) {
		if (flags & SFTP_FL_NONBLOCK)
		    return nret;
		else
		    continue;
	    }
	    else if (errno == EAGAIN) {
		FD_ZERO(&fdset);
		FD_SET(fd, &fdset);
		
		if (select(fd+1, &fdset, NULL, NULL, NULL) == -1
		    || !FD_ISSET(fd, &fdset)) {
		    if (errno == EINTR) {
			if (flags & SFTP_FL_NONBLOCK)
			    return nret;
			else
			    continue;
		    }
		    
		    if (nret)
			return nret;
		    else
			return -1;
		}
		continue;
	    }
	    return -1;
	}
	
	nbytes -= n;
	nret += n;
	if (flags & SFTP_FL_NONBLOCK)
	    return nret;
    }

    return nret;
}



static int
_sftp_writev(int fd, struct iovec *iov, int niov, int flags)
{
    int n, nret;
    fd_set fdset;

    nret = 0;
    while (niov) {
	n = writev(fd, iov, niov);

	if (n < 0) {
	    if (errno == EINTR) {
		if (flags & SFTP_FL_NONBLOCK)
		    return nret;
		else
		    continue;
	    }
	    else if (errno == EAGAIN) {
		FD_ZERO(&fdset);
		FD_SET(fd, &fdset);
		
		if (select(fd+1, NULL, &fdset, NULL, NULL) == -1
		    || !FD_ISSET(fd, &fdset)) {
		    if (errno == EINTR) {
			if (flags & SFTP_FL_NONBLOCK)
			    return nret;
			else
			    continue;
		    }
		    if (nret)
			return nret;
		    else
			return -1;
		}
		continue;
	    }
	    return -1;
	}

	nret += n;
	while (n) {
	    if (n >= iov[0].iov_len) {
		n -= iov[0].iov_len;
		iov++;
		--niov;
	    }
	    else {
		iov[0].iov_len -= n;
		iov[0].iov_base = ((char *)iov[0].iov_base) + n;
		n = 0;
	    }
	}
	if (flags & SFTP_FL_NONBLOCK)
	    return nret;
    }

    return nret;
}



/*
  Start write cycle: prepare write packet for LEN bytes, initialize members of F, and put F in state send.
*/

int
_sftp_start_write(struct sftp_file *f, int len)
{
    char *p;
    
    p = _sftp_packet->dat;
    _sftp_put_handle(p, &p, f->hnd);
    _sftp_put_uint64(p, &p, f->off);
    f->woff = p-_sftp_packet->dat;
    _sftp_put_uint32(p, &p, len);

    f->state = SFTP_FS_SEND;
    f->doff = p-_sftp_buffer;
    p += len;
    f->dend = p-_sftp_buffer;

    _sftp_make_packet(_sftp_packet, SSH_FXP_WRITE, p);
    _sftp_packet->id = _sftp_nextid++;

    return 0;
}



static void
_sftp_make_packet(struct packet *pkt, int type, char *end)
{
    pkt->type = type;
    pkt->id = -1;
    pkt->pn = 0;
    pkt->plen = end - pkt->dat;
}



static void
_sftp_send_error_packet(char *fmt, ...)
{
    char *p, *q;
    va_list argp;

    p = _sftp_packet->dat;
    _sftp_put_uint32(p, &p, SSH_FX_NO_CONNECTION);
    q = p+4;
    va_start(argp, fmt);
    vsprintf(q, fmt, argp);
    va_end(argp);
    q += strlen(q);
    _sftp_put_uint32(p, NULL, q-(p+4));
    _sftp_make_packet(_sftp_packet, SSH_FXP_STATUS, q);
    _sftp_put_packet(_sftp_packet, 0);
}

#endif /* USE_SFTP */