/* * sftplib.cpp * Copyright (C) 2002 Florin Malita * * This file is part of LUFS, a free userspace filesystem implementation. * See http://lufs.sourceforge.net/ for updates. * * LUFS 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. * * LUFS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sftplib.h" #ifndef SSHPROG #error ssh not found!!! #endif char *args[] = { SSHPROG, "-oFallBackToRsh no", "-oForwardX11 no", "-oForwardAgent no", "-oClearAllForwardings yes", "-oProtocol 2", NULL, NULL, "-s", NULL, "sftp", NULL }; void hton(void *buf, ...){ va_list ap; char *p; int i; long l; va_start(ap, buf); for(p = (char*)buf; (i = va_arg(ap, int)); p += i){ switch(i){ case 1: break; case 2: *((short*)p) = htons(*((short*)p)); TRACE("fixing short"); break; case 4: *((long*)p) = htonl(*((long*)p)); TRACE("fixing long"); break; case 8: l = *((long*)p); *((long*)p) = htonl(*(((long*)p) + 1)); *(((long*)p) + 1) = htonl(l); TRACE("fixing quad"); break; default: WARN("invalid size " << i); break; } } va_end(ap); } void ntoh(void *buf, ...){ va_list ap; char *p; int i; long l; va_start(ap, buf); for(p = (char*)buf; (i = va_arg(ap, int)); p += i){ switch(i){ case 1: break; case 2: *((short*)p) = ntohs(*((short*)p)); TRACE("fixing short"); break; case 4: *((long*)p) = ntohl(*((long*)p)); TRACE("fixing long"); break; case 8: l = *((long*)p); *((long*)p) = ntohl(*(((long*)p) + 1)); *(((long*)p) + 1) = ntohl(l); TRACE("fixing quad"); break; default: WARN("invalid size " << i); break; } } va_end(ap); } SConnection::SConnection(){ TRACE("in constructor"); connected = 0; seq = 0; } SConnection::~SConnection(){ TRACE("in destructor"); if(connected) disconnect(); } int SConnection::connect(char *host, char *user, int port){ char portstr[32]; int pin[2], pout[2]; int c_in, c_out; int err_fd; struct s_hdr hdr; TRACE("attempting to connect to " << host << ":" << port << " as " << user); string usr = string("-l") + user; sprintf(portstr, "-p%d", port); args[6] = portstr; args[7] = (char*)usr.c_str(); args[9] = host; for(int i = 0; args[i]; i++) TRACE("args[" << i << "]=" << args[i]); if((pipe(pin) == -1) || (pipe(pout) == -1)){ WARN("pipe failed"); return -1; } f_in = pin[0]; f_out = pout[1]; c_in = pout[0]; c_out = pin[1]; if((sshpid = fork()) == -1){ WARN("fork failed!"); return -1; }else if(sshpid == 0){ if((err_fd = ::open("/dev/null", O_WRONLY)) < 0){ WARN("could not open /dev/null!"); exit(1); } TRACE("child launching ssh..."); if((dup2(c_in, 0) == -1) || (dup2(c_out, 1) == -1) || (dup2(err_fd, 2) == -1)){ WARN("dup2 failed!"); exit(1); } ::close(f_in); ::close(f_out); ::close(c_in); ::close(c_out); ::close(err_fd); execv(SSHPROG, args); WARN("execv failed!"); exit(1); } ::close(c_in); ::close(c_out); int version = htonl(SSH2_FILEXFER_VERSION); if(send_packet(SSH2_FXP_INIT, &version, 4) < 0){ WARN("failed to init!"); disconnect(); return -1; } if(recv_packet(&hdr, NULL, 0) < 0){ WARN("failed to read version!"); disconnect(); return -1; } if(hdr.type != SSH2_FXP_VERSION){ WARN("unknown response!"); disconnect(); return -1; } ntoh(this->buf, 4, 0); TRACE("server protocol V" << *((long*)this->buf)); connected = 1; username = string(user); this->host = string(host); this->port = port; return 0; } void SConnection::disconnect(){ ::close(f_in); ::close(f_out); kill(sshpid, SIGTERM); connected = 0; } int SConnection::reconnect(){ disconnect(); return connect((char*)host.c_str(), (char*)username.c_str(), port); } void SConnection::show_error(int xlate){ if(xlate) ntoh(buf, 4, 4, 0); string err = string(&buf[12], ntohl(*((uint32_t*)&buf[8]))); TRACE("SERVER FAILURE: "<buf; max = MAXDATA; } ntoh(hdr, 4, 0); hdr->len--; if(hdr->len >= max){ WARN("packet too big!"); return -1; } ((char*)buf)[hdr->len] = 0; return lu_atomic_read(f_in, (char*)buf, hdr->len, 0); } int SConnection::execute(unsigned type, void *buf, unsigned len, struct s_hdr *hdr){ if((send_packet(type, buf, len) < 0) || (recv_packet(hdr, NULL, 0) < 0)){ TRACE("oops!"); disconnect(); pthread_exit(NULL); } last_cmd = type; return hdr->type; } int SConnection::execute(unsigned type, struct iovec *iov, int count, struct s_hdr *hdr){ if((send_packet(type, iov, count) < 0) || (recv_packet(hdr, NULL, 0) < 0)){ TRACE("oops!"); disconnect(); pthread_exit(NULL); } last_cmd = type; return hdr->type; } string SConnection::opendir(char *dir){ struct iovec iov[3]; struct s_hdr hdr; uint32_t id, slen; int res; string fail(""); TRACE("ssh_opendir: " << dir); id = htonl(seq++); slen = htonl(strlen(dir)); iov[0].iov_base = &id; iov[0].iov_len = 4; iov[1].iov_base = &slen; iov[1].iov_len = 4; iov[2].iov_base = dir; iov[2].iov_len = ntohl(slen);; if((res = execute(SSH2_FXP_OPENDIR, iov, 3, &hdr)) < 0){ WARN("execute failed!"); return fail; } if(res != SSH2_FXP_HANDLE){ WARN("unexpected response (" << res << ")!"); if(res == SSH2_FXP_STATUS) show_error(1); return fail; } ntoh(buf, 4, 4, 0); id = *((uint32_t*)buf); slen = *((uint32_t*)&buf[4]); TRACE("id=" << id << ", slen=" << slen); if((id != seq - 1) || (slen > MAXDATA - 9)){ WARN("wrong params!"); return fail; } return string(&buf[8], slen); } int SConnection::close(string &handle){ struct iovec iov[3]; struct s_hdr hdr; uint32_t id, slen; int res; id = htonl(seq++); slen = htonl(handle.size()); iov[0].iov_base = &id; iov[0].iov_len = 4; iov[1].iov_base = &slen; iov[1].iov_len = 4; iov[2].iov_base = (void*)handle.data(); iov[2].iov_len = handle.size(); if((res = execute(SSH2_FXP_CLOSE, iov, 3, &hdr)) < 0){ WARN("execute failed!"); return res; } if(res != SSH2_FXP_STATUS){ WARN("unexpected response!"); return -1; } ntoh(buf, 4, 4, 0); id = *((uint32_t*)buf); slen = *((uint32_t*)&buf[4]); if((id != seq -1) || (slen != SSH2_FX_OK)){ WARN("wrong params!"); return -1; } return 0; } int SConnection::readdir(string &handle){ struct iovec iov[3]; struct s_hdr hdr; uint32_t id, slen; int res; id = htonl(seq++); slen = htonl(handle.size()); iov[0].iov_base = &id; iov[0].iov_len = 4; iov[1].iov_base = &slen; iov[1].iov_len = 4; iov[2].iov_base = (void*)handle.data(); iov[2].iov_len = handle.size(); res = execute(SSH2_FXP_READDIR, iov, 3, &hdr); if(ntohl(*((uint32_t*)buf)) != seq - 1){ WARN("out of sequence!"); return -1; } return res; } int SConnection::readlink(char *link){ struct iovec iov[3]; struct s_hdr hdr; uint32_t id, len; int res; id = htonl(seq++); len = htonl(strlen(link)); iov[0].iov_base = &id; iov[0].iov_len = 4; iov[1].iov_base = &len; iov[1].iov_len = 4; iov[2].iov_base = link; iov[2].iov_len = ntohl(len); res = execute(SSH2_FXP_READLINK, iov, 3, &hdr); if(ntohl(*((uint32_t*)buf)) != seq - 1){ WARN("out of sequence!"); return -1; } return res; } char* SConnection::attr2fattr(char *ptr, struct lufs_fattr *fattr){ uint32_t flags = ntohl(*(uint32_t*)ptr); ptr += 4; if(flags & SSH2_FILEXFER_ATTR_SIZE){ fattr->f_size = ntohl(*(uint32_t*)(ptr+4)); TRACE("size: " << fattr->f_size); ptr += 8; } if(flags & SSH2_FILEXFER_ATTR_UIDGID){ ntoh(ptr, 4, 4, 0); fattr->f_uid = *(uint32_t*)ptr; fattr->f_gid = *(uint32_t*)(ptr+4); TRACE("uid: " << fattr->f_uid << ", gid: " << fattr->f_gid); ptr += 8; } if(flags & SSH2_FILEXFER_ATTR_PERMISSIONS){ fattr->f_mode = ntohl(*(uint32_t*)ptr); TRACE("mode: " << std::oct<f_mode<f_atime = *(uint32_t*)ptr; fattr->f_ctime = fattr->f_mtime = *(uint32_t*)(ptr+4); TRACE("atime: " << fattr->f_atime << ", mtime: " << fattr->f_mtime); ptr += 8; } if(flags & SSH2_FILEXFER_ATTR_EXTENDED){ TRACE("extended attributes!!!"); uint32_t count = *(uint32_t*)ptr; ptr+=4; for(; count > 0; count--){ string type = string(ptr + 4, ntohl(*(uint32_t*)ptr)); ptr += 4 + type.size(); string data = string(ptr + 4, ntohl(*(uint32_t*)ptr)); ptr += 4 + data.size(); TRACE("type: " << type); TRACE("count: " << data); } } return ptr; } int SConnection::lname2fattr(string &lname, struct lufs_fattr *fattr){ unsigned b, e; if((b = lname.find_first_not_of(" ")) == string::npos) return -1; if((b = lname.find(" ", b)) == string::npos) return -1; if((b = lname.find_first_not_of(" ", b)) == string::npos) return -1; if((e = lname.find(" ", b)) == string::npos) return -1; string nlink = lname.substr(b, e - b); TRACE("nlink: " << nlink); fattr->f_nlink = atoi(nlink.c_str()); return 0; } int SConnection::create(char *file, unsigned mode){ struct iovec iov[6]; struct s_hdr hdr; uint32_t id, slen, pflags, attr, perms; int res; TRACE("ssh_create: " << file << ", mode: " < readcache.offset) && (offset + count <= readcache.offset + readcache.count)){ TRACE("data in cache"); memcpy(b, buf + 8 + (offset - readcache.offset), count); return count; }else{ TRACE("data not in cache, reading..."); id = htonl(seq++); slen = htonl(handle.size()); len = htonl(MAXDATA - 20); off = (uint64_t)offset; hton(&off, 8, 0); iov[0].iov_base = &id; iov[0].iov_len = 4; iov[1].iov_base = &slen; iov[1].iov_len = 4; iov[2].iov_base = (void*)handle.data(); iov[2].iov_len = ntohl(slen); iov[3].iov_base = &off; iov[3].iov_len = 8; iov[4].iov_base = &len; iov[4].iov_len = 4; res = execute(SSH2_FXP_READ, iov, 5, &hdr); if(check_reply(res, SSH2_FXP_DATA) < 0) return -1; readcache.handle = handle; readcache.offset = offset; readcache.count = ntohl(*(uint32_t*)(buf + 4)); TRACE(readcache.count << " bytes read in cache"); res = (readcache.count > count) ? count : readcache.count; memcpy(b, buf + 8, res); return res; } } int SConnection::mkdir(char *dir, int mode){ struct iovec iov[5]; struct s_hdr hdr; uint32_t id, slen, flags, perms; int res; TRACE("ssh_mkdir " << dir << ", " << mode); id = htonl(seq++); slen = htonl(strlen(dir)); flags = htonl(SSH2_FILEXFER_ATTR_PERMISSIONS); perms = htonl(mode); iov[0].iov_base = &id; iov[0].iov_len = 4; iov[1].iov_base = &slen; iov[1].iov_len = 4; iov[2].iov_base = dir; iov[2].iov_len = ntohl(slen); iov[3].iov_base = &flags; iov[3].iov_len = 4; iov[4].iov_base = &perms; iov[4].iov_len = 4; res = execute(SSH2_FXP_MKDIR, iov, 5, &hdr); if((res = check_status(res, SSH2_FX_OK)) < 0) return res; return 0; } int SConnection::rmdir(char *dir){ struct iovec iov[3]; struct s_hdr hdr; uint32_t id, slen; int res; TRACE("ssh_rmdir: " << dir); id = htonl(seq++); slen = htonl(strlen(dir)); iov[0].iov_base = &id; iov[0].iov_len = 4; iov[1].iov_base = &slen; iov[1].iov_len = 4; iov[2].iov_base = dir; iov[2].iov_len = ntohl(slen); res = execute(SSH2_FXP_RMDIR, iov, 3, &hdr); if((res = check_status(res, SSH2_FX_OK)) < 0) return res; return 0; } int SConnection::remove(char *file){ struct iovec iov[3]; struct s_hdr hdr; int res; uint32_t id, slen; TRACE("ssh_remove: " << file); id = htonl(seq++); slen = htonl(strlen(file)); iov[0].iov_base = &id; iov[0].iov_len = 4; iov[1].iov_base = &slen; iov[1].iov_len = 4; iov[2].iov_base = file; iov[2].iov_len = ntohl(slen); res = execute(SSH2_FXP_REMOVE, iov, 3, &hdr); if((res = check_status(res, SSH2_FX_OK)) < 0) return res; return 0; } int SConnection::rename(char *old, char *nnew){ struct iovec iov[5]; struct s_hdr hdr; int res; uint32_t id, slen1, slen2; TRACE("ssh_rename: " << old << " to " << nnew); /* must delete existing entity first, otherwise will fail... */ remove(nnew); rmdir(nnew); id = htonl(seq++); slen1 = htonl(strlen(old)); slen2 = htonl(strlen(nnew)); iov[0].iov_base = &id; iov[0].iov_len = 4; iov[1].iov_base = &slen1; iov[1].iov_len = 4; iov[2].iov_base = old; iov[2].iov_len = ntohl(slen1); iov[3].iov_base = &slen2; iov[3].iov_len = 4; iov[4].iov_base = nnew; iov[4].iov_len = ntohl(slen2); if((res = check_status(execute(SSH2_FXP_RENAME, iov, 5, &hdr), SSH2_FX_OK)) < 0) return res; return 0; } int SConnection::setattr(char *file, struct lufs_fattr *fattr){ struct iovec iov[4]; struct s_hdr hdr; uint32_t id, slen; uint32_t pack[4]; int res; TRACE("ssh_setattr: " << file); TRACE("mode: "<f_mode<f_mode)){ TRACE("it's a link, skip it..."); return 0; } if(!S_ISDIR(fattr->f_mode)){ id = htonl(seq++); pack[0] = SSH2_FILEXFER_ATTR_SIZE; *(uint64_t*)&pack[1] = fattr->f_size; hton(pack, 4, 8, 0); iov[3].iov_len = 12; TRACE("setting size..."); if((res = check_status(execute(SSH2_FXP_SETSTAT, iov, 4, &hdr), SSH2_FX_OK)) < 0){ WARN("couldn't set size"); return res; } } id = htonl(seq++); pack[0] = SSH2_FILEXFER_ATTR_ACMODTIME | SSH2_FILEXFER_ATTR_PERMISSIONS; pack[1] = fattr->f_mode; pack[2] = fattr->f_atime; pack[3] = fattr->f_mtime; hton(pack, 4, 4, 4, 4, 0); iov[3].iov_len = 16; TRACE("setting atime & mtime..."); if((res = check_status(execute(SSH2_FXP_SETSTAT, iov, 4, &hdr), SSH2_FX_OK)) < 0){ WARN("couldn't set times"); return res; } return 0; } int SConnection::write(string &handle, long long offset, unsigned long count, char *b){ struct iovec iov[6]; struct s_hdr hdr; int res; uint32_t id, slen1, slen2; uint64_t off; TRACE("ssh_write: " << offset << ", " << count); id = htonl(seq++); slen1 = htonl(handle.size()); slen2 = htonl(count); off = (uint64_t)offset; hton(&off, 8, 0); iov[0].iov_base = &id; iov[0].iov_len = 4; iov[1].iov_base = &slen1; iov[1].iov_len = 4; iov[2].iov_base = (void*)handle.data(); iov[2].iov_len = ntohl(slen1); iov[3].iov_base = &off; iov[3].iov_len = 8; iov[4].iov_base = &slen2; iov[4].iov_len = 4; iov[5].iov_base = b; iov[5].iov_len = ntohl(slen2); if((res = check_status(execute(SSH2_FXP_WRITE, iov, 6, &hdr), SSH2_FX_OK)) < 0) return res; return 0; } int SConnection::symlink(char *file, char *link){ struct iovec iov[5]; struct s_hdr hdr; int res; uint32_t id, slen1, slen2; TRACE("ssh_symlink: " << file << " <=> " << link); id = htonl(seq++); slen1 = htonl(strlen(file)); slen2 = htonl(strlen(link)); iov[0].iov_base = &id; iov[0].iov_len = 4; iov[1].iov_base = &slen1; iov[1].iov_len = 4; iov[2].iov_base = file; iov[2].iov_len = ntohl(slen1); iov[3].iov_base = &slen2; iov[3].iov_len = 4; iov[4].iov_base = link; iov[4].iov_len = ntohl(slen2); if((res = check_status(execute(SSH2_FXP_SYMLINK, iov, 5, &hdr), SSH2_FX_OK)) < 0) return res; return 0; } int SConnection::stat(char *file, struct lufs_fattr *fattr){ struct iovec iov[3]; struct s_hdr hdr; int res; uint32_t id, slen; TRACE("ssh_stat: " << file); id = htonl(seq++); slen = htonl(strlen(file)); iov[0].iov_base = &id; iov[0].iov_len = 4; iov[1].iov_base = &slen; iov[1].iov_len = 4; iov[2].iov_base = file; iov[2].iov_len = ntohl(slen); if((res = check_reply(execute(SSH2_FXP_LSTAT, iov, 3, &hdr), SSH2_FXP_ATTRS)) < 0) return res; attr2fattr(buf + 4, fattr); fattr->f_nlink = 1; return 0; }