| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
from twisted.cred import portal |
|---|
| 5 |
from twisted.python import components, log |
|---|
| 6 |
from twisted.internet.error import ProcessExitedAlready |
|---|
| 7 |
from zope import interface |
|---|
| 8 |
from ssh import session, forwarding, filetransfer |
|---|
| 9 |
from ssh.filetransfer import FXF_READ, FXF_WRITE, FXF_APPEND, FXF_CREAT, FXF_TRUNC, FXF_EXCL |
|---|
| 10 |
from twisted.conch.ls import lsLine |
|---|
| 11 |
|
|---|
| 12 |
from avatar import ConchUser |
|---|
| 13 |
from error import ConchError |
|---|
| 14 |
from interfaces import ISession, ISFTPServer, ISFTPFile |
|---|
| 15 |
|
|---|
| 16 |
import struct, os, time, socket |
|---|
| 17 |
import fcntl, tty |
|---|
| 18 |
import pwd, grp |
|---|
| 19 |
import pty |
|---|
| 20 |
import ttymodes |
|---|
| 21 |
|
|---|
| 22 |
try: |
|---|
| 23 |
import utmp |
|---|
| 24 |
except ImportError: |
|---|
| 25 |
utmp = None |
|---|
| 26 |
|
|---|
| 27 |
class UnixSSHRealm: |
|---|
| 28 |
interface.implements(portal.IRealm) |
|---|
| 29 |
|
|---|
| 30 |
def requestAvatar(self, username, mind, *interfaces): |
|---|
| 31 |
user = UnixConchUser(username) |
|---|
| 32 |
return interfaces[0], user, user.logout |
|---|
| 33 |
|
|---|
| 34 |
|
|---|
| 35 |
class UnixConchUser(ConchUser): |
|---|
| 36 |
|
|---|
| 37 |
def __init__(self, username): |
|---|
| 38 |
ConchUser.__init__(self) |
|---|
| 39 |
self.username = username |
|---|
| 40 |
self.pwdData = pwd.getpwnam(self.username) |
|---|
| 41 |
l = [self.pwdData[3]] |
|---|
| 42 |
for groupname, password, gid, userlist in grp.getgrall(): |
|---|
| 43 |
if username in userlist: |
|---|
| 44 |
l.append(gid) |
|---|
| 45 |
self.otherGroups = l |
|---|
| 46 |
self.listeners = {} |
|---|
| 47 |
self.channelLookup.update( |
|---|
| 48 |
{"session": session.SSHSession, |
|---|
| 49 |
"direct-tcpip": forwarding.openConnectForwardingClient}) |
|---|
| 50 |
|
|---|
| 51 |
self.subsystemLookup.update( |
|---|
| 52 |
{"sftp": filetransfer.FileTransferServer}) |
|---|
| 53 |
|
|---|
| 54 |
def getUserGroupId(self): |
|---|
| 55 |
return self.pwdData[2:4] |
|---|
| 56 |
|
|---|
| 57 |
def getOtherGroups(self): |
|---|
| 58 |
return self.otherGroups |
|---|
| 59 |
|
|---|
| 60 |
def getHomeDir(self): |
|---|
| 61 |
return self.pwdData[5] |
|---|
| 62 |
|
|---|
| 63 |
def getShell(self): |
|---|
| 64 |
return self.pwdData[6] |
|---|
| 65 |
|
|---|
| 66 |
def global_tcpip_forward(self, data): |
|---|
| 67 |
hostToBind, portToBind = forwarding.unpackGlobal_tcpip_forward(data) |
|---|
| 68 |
from twisted.internet import reactor |
|---|
| 69 |
try: listener = self._runAsUser( |
|---|
| 70 |
reactor.listenTCP, portToBind, |
|---|
| 71 |
forwarding.SSHListenForwardingFactory(self.conn, |
|---|
| 72 |
(hostToBind, portToBind), |
|---|
| 73 |
forwarding.SSHListenServerForwardingChannel), |
|---|
| 74 |
interface = hostToBind) |
|---|
| 75 |
except: |
|---|
| 76 |
return 0 |
|---|
| 77 |
else: |
|---|
| 78 |
self.listeners[(hostToBind, portToBind)] = listener |
|---|
| 79 |
if portToBind == 0: |
|---|
| 80 |
portToBind = listener.getHost()[2] |
|---|
| 81 |
return 1, struct.pack('>L', portToBind) |
|---|
| 82 |
else: |
|---|
| 83 |
return 1 |
|---|
| 84 |
|
|---|
| 85 |
def global_cancel_tcpip_forward(self, data): |
|---|
| 86 |
hostToBind, portToBind = forwarding.unpackGlobal_tcpip_forward(data) |
|---|
| 87 |
listener = self.listeners.get((hostToBind, portToBind), None) |
|---|
| 88 |
if not listener: |
|---|
| 89 |
return 0 |
|---|
| 90 |
del self.listeners[(hostToBind, portToBind)] |
|---|
| 91 |
self._runAsUser(listener.stopListening) |
|---|
| 92 |
return 1 |
|---|
| 93 |
|
|---|
| 94 |
def logout(self): |
|---|
| 95 |
|
|---|
| 96 |
for listener in self.listeners.itervalues(): |
|---|
| 97 |
self._runAsUser(listener.stopListening) |
|---|
| 98 |
log.msg('avatar %s logging out (%i)' % (self.username, len(self.listeners))) |
|---|
| 99 |
|
|---|
| 100 |
def _runAsUser(self, f, *args, **kw): |
|---|
| 101 |
euid = os.geteuid() |
|---|
| 102 |
egid = os.getegid() |
|---|
| 103 |
groups = os.getgroups() |
|---|
| 104 |
uid, gid = self.getUserGroupId() |
|---|
| 105 |
os.setegid(0) |
|---|
| 106 |
os.seteuid(0) |
|---|
| 107 |
os.setgroups(self.getOtherGroups()) |
|---|
| 108 |
os.setegid(gid) |
|---|
| 109 |
os.seteuid(uid) |
|---|
| 110 |
try: |
|---|
| 111 |
f = iter(f) |
|---|
| 112 |
except TypeError: |
|---|
| 113 |
f = [(f, args, kw)] |
|---|
| 114 |
try: |
|---|
| 115 |
for i in f: |
|---|
| 116 |
func = i[0] |
|---|
| 117 |
args = len(i)>1 and i[1] or () |
|---|
| 118 |
kw = len(i)>2 and i[2] or {} |
|---|
| 119 |
r = func(*args, **kw) |
|---|
| 120 |
finally: |
|---|
| 121 |
os.setegid(0) |
|---|
| 122 |
os.seteuid(0) |
|---|
| 123 |
os.setgroups(groups) |
|---|
| 124 |
os.setegid(egid) |
|---|
| 125 |
os.seteuid(euid) |
|---|
| 126 |
return r |
|---|
| 127 |
|
|---|
| 128 |
class SSHSessionForUnixConchUser: |
|---|
| 129 |
|
|---|
| 130 |
interface.implements(ISession) |
|---|
| 131 |
|
|---|
| 132 |
def __init__(self, avatar): |
|---|
| 133 |
self.avatar = avatar |
|---|
| 134 |
self. environ = {'PATH':'/bin:/usr/bin:/usr/local/bin'} |
|---|
| 135 |
self.pty = None |
|---|
| 136 |
self.ptyTuple = 0 |
|---|
| 137 |
|
|---|
| 138 |
def addUTMPEntry(self, loggedIn=1): |
|---|
| 139 |
if not utmp: |
|---|
| 140 |
return |
|---|
| 141 |
ipAddress = self.avatar.conn.transport.transport.getPeer().host |
|---|
| 142 |
packedIp ,= struct.unpack('L', socket.inet_aton(ipAddress)) |
|---|
| 143 |
ttyName = self.ptyTuple[2][5:] |
|---|
| 144 |
t = time.time() |
|---|
| 145 |
t1 = int(t) |
|---|
| 146 |
t2 = int((t-t1) * 1e6) |
|---|
| 147 |
entry = utmp.UtmpEntry() |
|---|
| 148 |
entry.ut_type = loggedIn and utmp.USER_PROCESS or utmp.DEAD_PROCESS |
|---|
| 149 |
entry.ut_pid = self.pty.pid |
|---|
| 150 |
entry.ut_line = ttyName |
|---|
| 151 |
entry.ut_id = ttyName[-4:] |
|---|
| 152 |
entry.ut_tv = (t1,t2) |
|---|
| 153 |
if loggedIn: |
|---|
| 154 |
entry.ut_user = self.avatar.username |
|---|
| 155 |
entry.ut_host = socket.gethostbyaddr(ipAddress)[0] |
|---|
| 156 |
entry.ut_addr_v6 = (packedIp, 0, 0, 0) |
|---|
| 157 |
a = utmp.UtmpRecord(utmp.UTMP_FILE) |
|---|
| 158 |
a.pututline(entry) |
|---|
| 159 |
a.endutent() |
|---|
| 160 |
b = utmp.UtmpRecord(utmp.WTMP_FILE) |
|---|
| 161 |
b.pututline(entry) |
|---|
| 162 |
b.endutent() |
|---|
| 163 |
|
|---|
| 164 |
|
|---|
| 165 |
def getPty(self, term, windowSize, modes): |
|---|
| 166 |
self.environ['TERM'] = term |
|---|
| 167 |
self.winSize = windowSize |
|---|
| 168 |
self.modes = modes |
|---|
| 169 |
master, slave = pty.openpty() |
|---|
| 170 |
ttyname = os.ttyname(slave) |
|---|
| 171 |
self.environ['SSH_TTY'] = ttyname |
|---|
| 172 |
self.ptyTuple = (master, slave, ttyname) |
|---|
| 173 |
|
|---|
| 174 |
def openShell(self, proto): |
|---|
| 175 |
from twisted.internet import reactor |
|---|
| 176 |
if not self.ptyTuple: |
|---|
| 177 |
log.msg('tried to get shell without pty, failing') |
|---|
| 178 |
raise ConchError("no pty") |
|---|
| 179 |
uid, gid = self.avatar.getUserGroupId() |
|---|
| 180 |
homeDir = self.avatar.getHomeDir() |
|---|
| 181 |
shell = self.avatar.getShell() |
|---|
| 182 |
self.environ['USER'] = self.avatar.username |
|---|
| 183 |
self.environ['HOME'] = homeDir |
|---|
| 184 |
self.environ['SHELL'] = shell |
|---|
| 185 |
shellExec = os.path.basename(shell) |
|---|
| 186 |
peer = self.avatar.conn.transport.transport.getPeer() |
|---|
| 187 |
host = self.avatar.conn.transport.transport.getHost() |
|---|
| 188 |
self.environ['SSH_CLIENT'] = '%s %s %s' % (peer.host, peer.port, host.port) |
|---|
| 189 |
self.getPtyOwnership() |
|---|
| 190 |
self.pty = reactor.spawnProcess(proto, \ |
|---|
| 191 |
shell, ['-%s' % shellExec], self.environ, homeDir, uid, gid, |
|---|
| 192 |
usePTY = self.ptyTuple) |
|---|
| 193 |
self.addUTMPEntry() |
|---|
| 194 |
fcntl.ioctl(self.pty.fileno(), tty.TIOCSWINSZ, |
|---|
| 195 |
struct.pack('4H', *self.winSize)) |
|---|
| 196 |
if self.modes: |
|---|
| 197 |
self.setModes() |
|---|
| 198 |
self.oldWrite = proto.transport.write |
|---|
| 199 |
proto.transport.write = self._writeHack |
|---|
| 200 |
self.avatar.conn.transport.transport.setTcpNoDelay(1) |
|---|
| 201 |
|
|---|
| 202 |
def execCommand(self, proto, cmd): |
|---|
| 203 |
from twisted.internet import reactor |
|---|
| 204 |
uid, gid = self.avatar.getUserGroupId() |
|---|
| 205 |
homeDir = self.avatar.getHomeDir() |
|---|
| 206 |
shell = self.avatar.getShell() or '/bin/sh' |
|---|
| 207 |
command = (shell, '-c', cmd) |
|---|
| 208 |
peer = self.avatar.conn.transport.transport.getPeer() |
|---|
| 209 |
host = self.avatar.conn.transport.transport.getHost() |
|---|
| 210 |
self.environ['SSH_CLIENT'] = '%s %s %s' % (peer.host, peer.port, host.port) |
|---|
| 211 |
if self.ptyTuple: |
|---|
| 212 |
self.getPtyOwnership() |
|---|
| 213 |
self.pty = reactor.spawnProcess(proto, \ |
|---|
| 214 |
shell, command, self.environ, homeDir, |
|---|
| 215 |
uid, gid, usePTY = self.ptyTuple or 0) |
|---|
| 216 |
if self.ptyTuple: |
|---|
| 217 |
self.addUTMPEntry() |
|---|
| 218 |
if self.modes: |
|---|
| 219 |
self.setModes() |
|---|
| 220 |
|
|---|
| 221 |
|
|---|
| 222 |
self.avatar.conn.transport.transport.setTcpNoDelay(1) |
|---|
| 223 |
|
|---|
| 224 |
def getPtyOwnership(self): |
|---|
| 225 |
ttyGid = os.stat(self.ptyTuple[2])[5] |
|---|
| 226 |
uid, gid = self.avatar.getUserGroupId() |
|---|
| 227 |
euid, egid = os.geteuid(), os.getegid() |
|---|
| 228 |
os.setegid(0) |
|---|
| 229 |
os.seteuid(0) |
|---|
| 230 |
try: |
|---|
| 231 |
os.chown(self.ptyTuple[2], uid, ttyGid) |
|---|
| 232 |
finally: |
|---|
| 233 |
os.setegid(egid) |
|---|
| 234 |
os.seteuid(euid) |
|---|
| 235 |
|
|---|
| 236 |
def setModes(self): |
|---|
| 237 |
pty = self.pty |
|---|
| 238 |
attr = tty.tcgetattr(pty.fileno()) |
|---|
| 239 |
for mode, modeValue in self.modes: |
|---|
| 240 |
if not ttymodes.TTYMODES.has_key(mode): continue |
|---|
| 241 |
ttyMode = ttymodes.TTYMODES[mode] |
|---|
| 242 |
if len(ttyMode) == 2: |
|---|
| 243 |
flag, ttyAttr = ttyMode |
|---|
| 244 |
if not hasattr(tty, ttyAttr): continue |
|---|
| 245 |
ttyval = getattr(tty, ttyAttr) |
|---|
| 246 |
if modeValue: |
|---|
| 247 |
attr[flag] = attr[flag]|ttyval |
|---|
| 248 |
else: |
|---|
| 249 |
attr[flag] = attr[flag]&~ttyval |
|---|
| 250 |
elif ttyMode == 'OSPEED': |
|---|
| 251 |
attr[tty.OSPEED] = getattr(tty, 'B%s'%modeValue) |
|---|
| 252 |
elif ttyMode == 'ISPEED': |
|---|
| 253 |
attr[tty.ISPEED] = getattr(tty, 'B%s'%modeValue) |
|---|
| 254 |
else: |
|---|
| 255 |
if not hasattr(tty, ttyMode): continue |
|---|
| 256 |
ttyval = getattr(tty, ttyMode) |
|---|
| 257 |
attr[tty.CC][ttyval] = chr(modeValue) |
|---|
| 258 |
tty.tcsetattr(pty.fileno(), tty.TCSANOW, attr) |
|---|
| 259 |
|
|---|
| 260 |
def eofReceived(self): |
|---|
| 261 |
if self.pty: |
|---|
| 262 |
self.pty.closeStdin() |
|---|
| 263 |
|
|---|
| 264 |
def closed(self): |
|---|
| 265 |
if self.ptyTuple and os.path.exists(self.ptyTuple[2]): |
|---|
| 266 |
ttyGID = os.stat(self.ptyTuple[2])[5] |
|---|
| 267 |
os.chown(self.ptyTuple[2], 0, ttyGID) |
|---|
| 268 |
if self.pty: |
|---|
| 269 |
try: |
|---|
| 270 |
self.pty.signalProcess('HUP') |
|---|
| 271 |
except (OSError,ProcessExitedAlready): |
|---|
| 272 |
pass |
|---|
| 273 |
self.pty.loseConnection() |
|---|
| 274 |
self.addUTMPEntry(0) |
|---|
| 275 |
log.msg('shell closed') |
|---|
| 276 |
|
|---|
| 277 |
def windowChanged(self, winSize): |
|---|
| 278 |
self.winSize = winSize |
|---|
| 279 |
fcntl.ioctl(self.pty.fileno(), tty.TIOCSWINSZ, |
|---|
| 280 |
struct.pack('4H', *self.winSize)) |
|---|
| 281 |
|
|---|
| 282 |
def _writeHack(self, data): |
|---|
| 283 |
""" |
|---|
| 284 |
Hack to send ignore messages when we aren't echoing. |
|---|
| 285 |
""" |
|---|
| 286 |
if self.pty is not None: |
|---|
| 287 |
attr = tty.tcgetattr(self.pty.fileno())[3] |
|---|
| 288 |
if not attr & tty.ECHO and attr & tty.ICANON: |
|---|
| 289 |
self.avatar.conn.transport.sendIgnore('\x00'*(8+len(data))) |
|---|
| 290 |
self.oldWrite(data) |
|---|
| 291 |
|
|---|
| 292 |
|
|---|
| 293 |
class SFTPServerForUnixConchUser: |
|---|
| 294 |
|
|---|
| 295 |
interface.implements(ISFTPServer) |
|---|
| 296 |
|
|---|
| 297 |
def __init__(self, avatar): |
|---|
| 298 |
self.avatar = avatar |
|---|
| 299 |
|
|---|
| 300 |
|
|---|
| 301 |
def _setAttrs(self, path, attrs): |
|---|
| 302 |
""" |
|---|
| 303 |
NOTE: this function assumes it runs as the logged-in user: |
|---|
| 304 |
i.e. under _runAsUser() |
|---|
| 305 |
""" |
|---|
| 306 |
if attrs.has_key("uid") and attrs.has_key("gid"): |
|---|
| 307 |
os.chown(path, attrs["uid"], attrs["gid"]) |
|---|
| 308 |
if attrs.has_key("permissions"): |
|---|
| 309 |
os.chmod(path, attrs["permissions"]) |
|---|
| 310 |
if attrs.has_key("atime") and attrs.has_key("mtime"): |
|---|
| 311 |
os.utime(path, (attrs["atime"], attrs["mtime"])) |
|---|
| 312 |
|
|---|
| 313 |
def _getAttrs(self, s): |
|---|
| 314 |
return { |
|---|
| 315 |
"size" : s.st_size, |
|---|
| 316 |
"uid" : s.st_uid, |
|---|
| 317 |
"gid" : s.st_gid, |
|---|
| 318 |
"permissions" : s.st_mode, |
|---|
| 319 |
"atime" : int(s.st_atime), |
|---|
| 320 |
"mtime" : int(s.st_mtime) |
|---|
| 321 |
} |
|---|
| 322 |
|
|---|
| 323 |
def _absPath(self, path): |
|---|
| 324 |
home = self.avatar.getHomeDir() |
|---|
| 325 |
return os.path.abspath(os.path.join(home, path)) |
|---|
| 326 |
|
|---|
| 327 |
def gotVersion(self, otherVersion, extData): |
|---|
| 328 |
return {} |
|---|
| 329 |
|
|---|
| 330 |
def openFile(self, filename, flags, attrs): |
|---|
| 331 |
return UnixSFTPFile(self, self._absPath(filename), flags, attrs) |
|---|
| 332 |
|
|---|
| 333 |
def removeFile(self, filename): |
|---|
| 334 |
filename = self._absPath(filename) |
|---|
| 335 |
return self.avatar._runAsUser(os.remove, filename) |
|---|
| 336 |
|
|---|
| 337 |
def renameFile(self, oldpath, newpath): |
|---|
| 338 |
oldpath = self._absPath(oldpath) |
|---|
| 339 |
newpath = self._absPath(newpath) |
|---|
| 340 |
return self.avatar._runAsUser(os.rename, oldpath, newpath) |
|---|
| 341 |
|
|---|
| 342 |
def makeDirectory(self, path, attrs): |
|---|
| 343 |
path = self._absPath(path) |
|---|
| 344 |
return self.avatar._runAsUser([(os.mkdir, (path,)), |
|---|
| 345 |
(self._setAttrs, (path, attrs))]) |
|---|
| 346 |
|
|---|
| 347 |
def removeDirectory(self, path): |
|---|
| 348 |
path = self._absPath(path) |
|---|
| 349 |
self.avatar._runAsUser(os.rmdir, path) |
|---|
| 350 |
|
|---|
| 351 |
def openDirectory(self, path): |
|---|
| 352 |
return UnixSFTPDirectory(self, self._absPath(path)) |
|---|
| 353 |
|
|---|
| 354 |
def getAttrs(self, path, followLinks): |
|---|
| 355 |
path = self._absPath(path) |
|---|
| 356 |
if followLinks: |
|---|
| 357 |
s = self.avatar._runAsUser(os.stat, path) |
|---|
| 358 |
else: |
|---|
| 359 |
s = self.avatar._runAsUser(os.lstat, path) |
|---|
| 360 |
return self._getAttrs(s) |
|---|
| 361 |
|
|---|
| 362 |
def setAttrs(self, path, attrs): |
|---|
| 363 |
path = self._absPath(path) |
|---|
| 364 |
self.avatar._runAsUser(self._setAttrs, path, attrs) |
|---|
| 365 |
|
|---|
| 366 |
def readLink(self, path): |
|---|
| 367 |
path = self._absPath(path) |
|---|
| 368 |
return self.avatar._runAsUser(os.readlink, path) |
|---|
| 369 |
|
|---|
| 370 |
def makeLink(self, linkPath, targetPath): |
|---|
| 371 |
linkPath = self._absPath(linkPath) |
|---|
| 372 |
targetPath = self._absPath(targetPath) |
|---|
| 373 |
return self.avatar._runAsUser(os.symlink, targetPath, linkPath) |
|---|
| 374 |
|
|---|
| 375 |
def realPath(self, path): |
|---|
| 376 |
return os.path.realpath(self._absPath(path)) |
|---|
| 377 |
|
|---|
| 378 |
def extendedRequest(self, extName, extData): |
|---|
| 379 |
raise NotImplementedError |
|---|
| 380 |
|
|---|
| 381 |
class UnixSFTPFile: |
|---|
| 382 |
|
|---|
| 383 |
interface.implements(ISFTPFile) |
|---|
| 384 |
|
|---|
| 385 |
def __init__(self, server, filename, flags, attrs): |
|---|
| 386 |
self.server = server |
|---|
| 387 |
openFlags = 0 |
|---|
| 388 |
if flags & FXF_READ == FXF_READ and flags & FXF_WRITE == 0: |
|---|
| 389 |
openFlags = os.O_RDONLY |
|---|
| 390 |
if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == 0: |
|---|
| 391 |
openFlags = os.O_WRONLY |
|---|
| 392 |
if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == FXF_READ: |
|---|
| 393 |
openFlags = os.O_RDWR |
|---|
| 394 |
if flags & FXF_APPEND == FXF_APPEND: |
|---|
| 395 |
openFlags |= os.O_APPEND |
|---|
| 396 |
if flags & FXF_CREAT == FXF_CREAT: |
|---|
| 397 |
openFlags |= os.O_CREAT |
|---|
| 398 |
if flags & FXF_TRUNC == FXF_TRUNC: |
|---|
| 399 |
openFlags |= os.O_TRUNC |
|---|
| 400 |
if flags & FXF_EXCL == FXF_EXCL: |
|---|
| 401 |
openFlags |= os.O_EXCL |
|---|
| 402 |
if attrs.has_key("permissions"): |
|---|
| 403 |
mode = attrs["permissions"] |
|---|
| 404 |
del attrs["permissions"] |
|---|
| 405 |
else: |
|---|
| 406 |
mode = 0777 |
|---|
| 407 |
fd = server.avatar._runAsUser(os.open, filename, openFlags, mode) |
|---|
| 408 |
if attrs: |
|---|
| 409 |
server.avatar._runAsUser(server._setAttrs, filename, attrs) |
|---|
| 410 |
self.fd = fd |
|---|
| 411 |
|
|---|
| 412 |
def close(self): |
|---|
| 413 |
return self.server.avatar._runAsUser(os.close, self.fd) |
|---|
| 414 |
|
|---|
| 415 |
def readChunk(self, offset, length): |
|---|
| 416 |
return self.server.avatar._runAsUser([ (os.lseek, (self.fd, offset, 0)), |
|---|
| 417 |
(os.read, (self.fd, length)) ]) |
|---|
| 418 |
|
|---|
| 419 |
def writeChunk(self, offset, data): |
|---|
| 420 |
return self.server.avatar._runAsUser([(os.lseek, (self.fd, offset, 0)), |
|---|
| 421 |
(os.write, (self.fd, data))]) |
|---|
| 422 |
|
|---|
| 423 |
def getAttrs(self): |
|---|
| 424 |
s = self.server.avatar._runAsUser(os.fstat, self.fd) |
|---|
| 425 |
return self.server._getAttrs(s) |
|---|
| 426 |
|
|---|
| 427 |
def setAttrs(self, attrs): |
|---|
| 428 |
raise NotImplementedError |
|---|
| 429 |
|
|---|
| 430 |
|
|---|
| 431 |
class UnixSFTPDirectory: |
|---|
| 432 |
|
|---|
| 433 |
def __init__(self, server, directory): |
|---|
| 434 |
self.server = server |
|---|
| 435 |
self.files = server.avatar._runAsUser(os.listdir, directory) |
|---|
| 436 |
self.dir = directory |
|---|
| 437 |
|
|---|
| 438 |
def __iter__(self): |
|---|
| 439 |
return self |
|---|
| 440 |
|
|---|
| 441 |
def next(self): |
|---|
| 442 |
try: |
|---|
| 443 |
f = self.files.pop(0) |
|---|
| 444 |
except IndexError: |
|---|
| 445 |
raise StopIteration |
|---|
| 446 |
else: |
|---|
| 447 |
s = self.server.avatar._runAsUser(os.lstat, os.path.join(self.dir, f)) |
|---|
| 448 |
longname = lsLine(f, s) |
|---|
| 449 |
attrs = self.server._getAttrs(s) |
|---|
| 450 |
return (f, longname, attrs) |
|---|
| 451 |
|
|---|
| 452 |
def close(self): |
|---|
| 453 |
self.files = [] |
|---|
| 454 |
|
|---|
| 455 |
|
|---|
| 456 |
components.registerAdapter(SFTPServerForUnixConchUser, UnixConchUser, filetransfer.ISFTPServer) |
|---|
| 457 |
components.registerAdapter(SSHSessionForUnixConchUser, UnixConchUser, session.ISession) |
|---|