1# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
2# See the COPYRIGHT file for more information
3
4"""
5This module contains ...
6"""
7
8from __future__ import absolute_import, division
9
10import os
11
12import twisted
13import twisted.conch.ls
14from twisted.conch.interfaces import ISFTPFile, ISFTPServer
15from twisted.conch.ssh import filetransfer
16from twisted.conch.ssh.filetransfer import FXF_APPEND, FXF_CREAT, FXF_EXCL, FXF_READ, FXF_TRUNC, FXF_WRITE
17from twisted.python import log
18from twisted.python.compat import nativeString
19
20from zope.interface import implementer
21
22import cowrie.shell.pwd as pwd
23from cowrie.core.config import CowrieConfig
24
25
26@implementer(ISFTPFile)
27class CowrieSFTPFile(object):
28    """
29    SFTPTFile
30    """
31    transfer_completed = 0
32    bytesReceived = 0
33    bytesReceivedLimit = CowrieConfig().getint('honeypot', 'download_limit_size', fallback=0)
34
35    def __init__(self, sftpserver, filename, flags, attrs):
36        self.sftpserver = sftpserver
37        self.filename = filename
38
39        openFlags = 0
40        if flags & FXF_READ == FXF_READ and flags & FXF_WRITE == 0:
41            openFlags = os.O_RDONLY
42        if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == 0:
43            openFlags = os.O_WRONLY
44        if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == FXF_READ:
45            openFlags = os.O_RDWR
46        if flags & FXF_APPEND == FXF_APPEND:
47            openFlags |= os.O_APPEND
48        if flags & FXF_CREAT == FXF_CREAT:
49            openFlags |= os.O_CREAT
50        if flags & FXF_TRUNC == FXF_TRUNC:
51            openFlags |= os.O_TRUNC
52        if flags & FXF_EXCL == FXF_EXCL:
53            openFlags |= os.O_EXCL
54        if "permissions" in attrs:
55            filemode = attrs["permissions"]
56            del attrs["permissions"]
57        else:
58            filemode = 0o777
59        fd = sftpserver.fs.open(filename, openFlags, filemode)
60        if attrs:
61            self.sftpserver.setAttrs(filename, attrs)
62        self.fd = fd
63
64        # Cache a copy of file in memory to read from in readChunk
65        if flags & FXF_READ == FXF_READ:
66            self.contents = self.sftpserver.fs.file_contents(self.filename)
67
68    def close(self):
69        if self.bytesReceived > 0:
70            self.sftpserver.fs.update_size(self.filename, self.bytesReceived)
71        return self.sftpserver.fs.close(self.fd)
72
73    def readChunk(self, offset, length):
74        return self.contents[offset:offset + length]
75
76    def writeChunk(self, offset, data):
77        self.bytesReceived += len(data)
78        if self.bytesReceivedLimit and self.bytesReceived > self.bytesReceivedLimit:
79            raise filetransfer.SFTPError(filetransfer.FX_FAILURE, "Quota exceeded")
80        self.sftpserver.fs.lseek(self.fd, offset, os.SEEK_SET)
81        self.sftpserver.fs.write(self.fd, data)
82
83    def getAttrs(self):
84        s = self.sftpserver.fs.stat(self.filename)
85        return self.sftpserver.getAttrs(s)
86
87    def setAttrs(self, attrs):
88        raise NotImplementedError
89
90
91class CowrieSFTPDirectory(object):
92
93    def __init__(self, server, directory):
94        self.server = server
95        self.files = server.fs.listdir(directory)
96        self.files = [".", ".."] + self.files
97        self.dir = directory
98
99    def __iter__(self):
100        return self
101
102    def next(self):
103        """
104        Py2 compatibility
105        """
106        return self.__next__()
107
108    def __next__(self):
109        try:
110            f = self.files.pop(0)
111        except IndexError:
112            raise StopIteration
113
114        if f == "..":
115            directory = self.dir.strip().split("/")
116            pdir = "/" + "/".join(directory[:-1])
117            s1 = self.server.fs.lstat(pdir)
118            s = self.server.fs.lstat(pdir)
119            s1.st_uid = pwd.Passwd().getpwuid(s.st_uid)["pw_name"]
120            s1.st_gid = pwd.Group().getgrgid(s.st_gid)["gr_name"]
121            longname = twisted.conch.ls.lsLine(f, s1)
122            attrs = self.server._getAttrs(s)
123            return (f, longname, attrs)
124        elif f == ".":
125            s1 = self.server.fs.lstat(self.dir)
126            s = self.server.fs.lstat(self.dir)
127            s1.st_uid = pwd.Passwd().getpwuid(s.st_uid)["pw_name"]
128            s1.st_gid = pwd.Group().getgrgid(s.st_gid)["gr_name"]
129            longname = twisted.conch.ls.lsLine(f, s1)
130            attrs = self.server._getAttrs(s)
131            return (f, longname, attrs)
132        else:
133            s = self.server.fs.lstat(os.path.join(self.dir, f))
134            s2 = self.server.fs.lstat(os.path.join(self.dir, f))
135            s2.st_uid = pwd.Passwd().getpwuid(s.st_uid)["pw_name"]
136            s2.st_gid = pwd.Group().getgrgid(s.st_gid)["gr_name"]
137            longname = twisted.conch.ls.lsLine(f, s2)
138            attrs = self.server._getAttrs(s)
139            return (f, longname, attrs)
140
141    def close(self):
142        self.files = []
143
144
145@implementer(ISFTPServer)
146class SFTPServerForCowrieUser(object):
147
148    def __init__(self, avatar):
149        self.avatar = avatar
150        self.avatar.server.initFileSystem(self.avatar.home)
151        self.fs = self.avatar.server.fs
152
153    def _absPath(self, path):
154        home = self.avatar.home
155        return os.path.abspath(os.path.join(nativeString(home), nativeString(path)))
156
157    def _setAttrs(self, path, attrs):
158        if "uid" in attrs and "gid" in attrs:
159            self.fs.chown(path, attrs["uid"], attrs["gid"])
160        if "permissions" in attrs:
161            self.fs.chmod(path, attrs["permissions"])
162        if "atime" in attrs and "mtime" in attrs:
163            self.fs.utime(path, attrs["atime"], attrs["mtime"])
164
165    def _getAttrs(self, s):
166        return {
167            "size": s.st_size,
168            "uid": s.st_uid,
169            "gid": s.st_gid,
170            "permissions": s.st_mode,
171            "atime": int(s.st_atime),
172            "mtime": int(s.st_mtime)
173        }
174
175    def gotVersion(self, otherVersion, extData):
176        return {}
177
178    def openFile(self, filename, flags, attrs):
179        log.msg("SFTP openFile: {}".format(filename))
180        return CowrieSFTPFile(self, self._absPath(filename), flags, attrs)
181
182    def removeFile(self, filename):
183        log.msg("SFTP removeFile: {}".format(filename))
184        return self.fs.remove(self._absPath(filename))
185
186    def renameFile(self, oldpath, newpath):
187        log.msg("SFTP renameFile: {} {}".format(oldpath, newpath))
188        return self.fs.rename(self._absPath(oldpath), self._absPath(newpath))
189
190    def makeDirectory(self, path, attrs):
191        log.msg("SFTP makeDirectory: {}".format(path))
192        path = self._absPath(path)
193        self.fs.mkdir2(path)
194        self._setAttrs(path, attrs)
195
196    def removeDirectory(self, path):
197        log.msg("SFTP removeDirectory: {}".format(path))
198        return self.fs.rmdir(self._absPath(path))
199
200    def openDirectory(self, path):
201        log.msg("SFTP OpenDirectory: {}".format(path))
202        return CowrieSFTPDirectory(self, self._absPath(path))
203
204    def getAttrs(self, path, followLinks):
205        log.msg("SFTP getAttrs: {}".format(path))
206        path = self._absPath(path)
207        if followLinks:
208            s = self.fs.stat(path)
209        else:
210            s = self.fs.lstat(path)
211        return self._getAttrs(s)
212
213    def setAttrs(self, path, attrs):
214        log.msg("SFTP setAttrs: {}".format(path))
215        path = self._absPath(path)
216        return self._setAttrs(path, attrs)
217
218    def readLink(self, path):
219        log.msg("SFTP readLink: {}".format(path))
220        path = self._absPath(path)
221        return self.fs.readlink(path)
222
223    def makeLink(self, linkPath, targetPath):
224        log.msg("SFTP makeLink: {} {}".format(linkPath, targetPath))
225        linkPath = self._absPath(linkPath)
226        targetPath = self._absPath(targetPath)
227        return self.fs.symlink(targetPath, linkPath)
228
229    def realPath(self, path):
230        return self.fs.realpath(self._absPath(path))
231
232    def extendedRequest(self, extName, extData):
233        raise NotImplementedError
234