# Twisted, the Framework of Your Internet
# Copyright (C) 2001-2004 Matthew W. Lefkowitz
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
from twisted.cred import portal
from twisted.python import components, log
from ssh import session, forwarding, filetransfer
from ssh.filetransfer import FXF_READ, FXF_WRITE, FXF_APPEND, FXF_CREAT, FXF_TRUNC, FXF_EXCL
from ssh.connection import OPEN_UNKNOWN_CHANNEL_TYPE
from avatar import ConchUser
from error import ConchError
from interfaces import ISession, ISFTPServer, ISFTPFile
import struct, array, os, stat, time
class UnixSSHRealm:
__implements__ = portal.IRealm
def requestAvatar(self, username, mind, *interfaces):
user = UnixConchUser(username)
return interfaces[0], user, user.logout
class UnixConchUser(ConchUser):
def __init__(self, username):
ConchUser.__init__(self)
self.username = username
import pwd
self.pwdData = pwd.getpwnam(self.username)
self.listeners = {} # dict mapping (interface, port) -> listener
self.channelLookup.update(
{"session": session.SSHSession,
"direct-tcpip": forwarding.openConnectForwardingClient})
self.subsystemLookup.update(
{"sftp": filetransfer.FileTransferServer})
def getUserGroupId(self):
return self.pwdData[2:4]
def getHomeDir(self):
return self.pwdData[5]
def getShell(self):
return self.pwdData[6]
def global_tcpip_forward(self, data):
hostToBind, portToBind = forwarding.unpackGlobal_tcpip_forward(data)
from twisted.internet import reactor
try: listener = self._runAsUser(
reactor.listenTCP, portToBind,
forwarding.SSHListenForwardingFactory(self.conn,
(hostToBind, portToBind),
forwarding.SSHListenServerForwardingChannel),
interface = hostToBind)
except:
return 0
else:
self.listeners[(hostToBind, portToBind)] = listener
if portToBind == 0:
portToBind = listener.getHost()[2] # the port
return 1, struct.pack('>L', portToBind)
else:
return 1
def global_cancel_tcpip_forward(self, data):
hostToBind, portToBind = forwarding.unpackGlobal_tcpip_forward(data)
listener = self.listeners.get((hostToBind, portToBind), None)
if not listener:
return 0
del self.listeners[(hostToBind, portToBind)]
self._runAsUser(listener.stopListening)
return 1
def logout(self):
# remove all listeners
for listener in self.listeners.itervalues():
self._runAsUser(listener.stopListening)
log.msg('avatar %s logging out (%i)' % (self.username, len(self.listeners)))
def _runAsUser(self, f, *args, **kw):
euid = os.geteuid()
egid = os.getegid()
uid, gid = self.getUserGroupId()
os.setegid(0)
os.seteuid(0)
os.setegid(gid)
os.seteuid(uid)
try:
f = iter(f)
except TypeError:
f = [(f, args, kw)]
try:
for i in f:
func = i[0]
args = len(i)>1 and i[1] or ()
kw = len(i)>2 and i[2] or {}
r = func(*args, **kw)
finally:
os.setegid(0)
os.seteuid(0)
os.setegid(egid)
os.seteuid(euid)
return r
class SSHSessionForUnixConchUser:
__implements__ = ISession
def __init__(self, avatar):
self.avatar = avatar
self. environ = {'PATH':'/bin:/usr/bin:/usr/local/bin'}
self.pty = None
self.ptyTuple = 0
def getPty(self, term, windowSize, modes):
import pty
self.environ['TERM'] = term
self.winSize = windowSize
self.modes = modes
master, slave = pty.openpty()
ttyname = os.ttyname(slave)
self.environ['SSH_TTY'] = ttyname
self.ptyTuple = (master, slave, ttyname)
def openShell(self, proto):
import fcntl, tty
from twisted.internet import reactor
if not self.ptyTuple: # we didn't get a pty-req
log.msg('tried to get shell without pty, failing')
raise ConchError("no pty")
uid, gid = self.avatar.getUserGroupId()
homeDir = self.avatar.getHomeDir()
shell = self.avatar.getShell()
self.environ['USER'] = self.avatar.username
self.environ['HOME'] = homeDir
self.environ['SHELL'] = shell
peer = self.avatar.conn.transport.transport.getPeer()
host = self.avatar.conn.transport.transport.getHost()
self.environ['SSH_CLIENT'] = '%s %s %s' % (peer.host, peer.port, host.port)
self.getPtyOwnership()
self.pty = reactor.spawnProcess(proto, \
shell, ['-', '-i'], self.environ, homeDir, uid, gid,
usePTY = self.ptyTuple)
fcntl.ioctl(self.pty.fileno(), tty.TIOCSWINSZ,
struct.pack('4H', *self.winSize))
if self.modes:
self.setModes()
self.oldWrite = proto.transport.write
proto.transport.write = self._writeHack
self.avatar.conn.transport.transport.setTcpNoDelay(1)
def execCommand(self, proto, cmd):
from twisted.internet import reactor
uid, gid = self.avatar.getUserGroupId()
homeDir = self.avatar.getHomeDir()
shell = self.avatar.getShell() or '/bin/sh'
command = (shell, '-c', cmd)
peer = self.avatar.conn.transport.transport.getPeer()
host = self.avatar.conn.transport.transport.getHost()
self.environ['SSH_CLIENT'] = '%s %s %s' % (peer.host, peer.port, host.port)
if self.ptyTuple:
self.getPtyOwnership()
self.pty = reactor.spawnProcess(proto, \
shell, command, self.environ, homeDir,
uid, gid, usePTY = self.ptyTuple or 1)
if self.ptyTuple:
if self.modes:
self.setModes()
else:
import tty
tty.setraw(self.pty.fileno(), tty.TCSANOW)
self.avatar.conn.transport.transport.setTcpNoDelay(1)
def getPtyOwnership(self):
ttyGid = os.stat(self.ptyTuple[2])[5]
uid = self.avatar.getUserGroupId()[0]
euid, egid = os.geteuid(), os.getegid()
os.setegid(0)
os.seteuid(0)
try:
os.chown(self.ptyTuple[2], uid, ttyGid)
finally:
os.setegid(egid)
os.seteuid(euid)
def setModes(self):
import tty, ttymodes
pty = self.pty
attr = tty.tcgetattr(pty.fileno())
for mode, modeValue in self.modes:
if not ttymodes.TTYMODES.has_key(mode): continue
ttyMode = ttymodes.TTYMODES[mode]
if len(ttyMode) == 2: # flag
flag, ttyAttr = ttyMode
if not hasattr(tty, ttyAttr): continue
ttyval = getattr(tty, ttyAttr)
if modeValue:
attr[flag] = attr[flag]|ttyval
else:
attr[flag] = attr[flag]&~ttyval
elif ttyMode == 'OSPEED':
attr[tty.OSPEED] = getattr(tty, 'B%s'%modeValue)
elif ttyMode == 'ISPEED':
attr[tty.ISPEED] = getattr(tty, 'B%s'%modeValue)
else:
if not hasattr(tty, ttyMode): continue
ttyval = getattr(tty, ttyMode)
attr[tty.CC][ttyval] = chr(modeValue)
tty.tcsetattr(pty.fileno(), tty.TCSANOW, attr)
def closed(self):
if self.pty:
import os
self.pty.loseConnection()
self.pty.signalProcess('HUP')
if self.ptyTuple:
ttyGID = os.stat(self.ptyTuple[2])[5]
os.chown(self.ptyTuple[2], 0, ttyGID)
def _writeHack(self, data):
"""
Hack to send ignore messages when we aren't echoing.
"""
if self.pty is not None:
import tty
attr = tty.tcgetattr(self.pty.fileno())[3]
if not attr & tty.ECHO and attr & tty.ICANON: # no echo
self.avatar.conn.transport.sendIgnore('\x00'*(8+len(data)))
self.oldWrite(data)
class SFTPServerForUnixConchUser:
__implements__ = ISFTPServer
def __init__(self, avatar):
self.avatar = avatar
def _setAttrs(self, path, attrs):
"""
NOTE: this function assumes it runs as the logged-in user:
i.e. under _runAsUser()
"""
if attrs.has_key("uid") and attrs.has_key("gid"):
os.lchown(path, attrs["uid"], attrs["gid"])
if attrs.has_key("permissions"):
os.chmod(path, attrs["permissions"])
if attrs.has_key("atime") and attrs.has_key("mtime"):
os.utime(path, (attrs["atime"]. attrs["mtime"]))
def _getAttrs(self, s):
return {
"size" : s.st_size,
"uid" : s.st_uid,
"gid" : s.st_gid,
"permissions" : s.st_mode,
"atime" : s.st_atime,
"mtime" : s.st_mtime
}
def _absPath(self, path):
import pwd
uid, gid = self.avatar.getUserGroupId()
home = pwd.getpwuid(uid)[5]
return os.path.realpath(os.path.abspath(os.path.join(home, path)))
def gotVersion(self, otherVersion, extData):
return {}
def openFile(self, filename, flags, attrs):
return UnixSFTPFile(self, self._absPath(filename), flags, attrs)
def removeFile(self, filename):
filename = self._absPath(filename)
return self.avatar._runAsUser(os.remove, filename)
def renameFile(self, oldpath, newpath):
oldpath = self._absPath(oldpath)
newpath = self._absPath(newpath)
return self.avatar._runAsUser(os.rename, oldpath, newpath)
def makeDirectory(self, path, attrs):
path = self._absPath(path)
return self.avatar._runAsUser([(os.mkdir, (path,)),
(self._setAttrs, (path, attrs))])
def removeDirectory(self, path):
path = self._absPath(path)
self.avatar._runAsUser(os.rmdir, path)
def openDirectory(self, path):
return UnixSFTPDirectory(self, self._absPath(path))
def getAttrs(self, path, followLinks):
path = self._absPath(path)
if followLinks:
s = self.avatar._runAsUser(os.stat, path)
else:
s = self.avatar._runAsUser(os.lstat, path)
return self._getAttrs(s)
def setAttrs(self, path, attrs):
path = self._absPath(path)
self.avatar._runAsUser(self._setAttrs, path, attrs)
def readLink(self, path):
path = self._absPath(path)
return self.avatar._runAsUser(os.readlink, path)
def makeLink(self, linkPath, targetPath):
linkPath = self._absPath(linkPath)
targetPath = self._absPath(targetPath)
return self.avatar._runAsUser(os.symlink, targetPath, linkPath)
def realPath(self, path):
return self._absPath(path)
class UnixSFTPFile:
__implements__ = ISFTPFile
def __init__(self, server, filename, flags, attrs):
self.server = server
openFlags = 0
if flags & FXF_READ == FXF_READ and flags & FXF_WRITE == 0:
openFlags = os.O_RDONLY
if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == 0:
openFlags = os.O_WRONLY
if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == FXF_READ:
openFlags = os.O_RDWR
if flags & FXF_APPEND == FXF_APPEND:
openFlags |= os.O_APPEND
if flags & FXF_CREAT == FXF_CREAT:
openFlags |= os.O_CREAT
if flags & FXF_TRUNC == FXF_TRUNC:
openFlags |= os.O_TRUNC
if flags & FXF_EXCL == FXF_EXCL:
openFlags |= os.O_EXCL
if attrs.has_key("permissions"):
mode = attrs["permissions"]
del attrs["permissions"]
else:
mode = 0777
fd = server.avatar._runAsUser(os.open, filename, openFlags, mode)
if attrs:
server.avatar._runAsUser(server._setAttrs, filename, attrs)
self.fd = fd
def close(self):
return self.server.avatar._runAsUser(os.close, self.fd)
def readChunk(self, offset, length):
return self.server.avatar._runAsUser([ (os.lseek, (self.fd, offset, 0)),
(os.read, (self.fd, length)) ])
def writeChunk(self, offset, data):
return self.server.avatar._runAsUser([(os.lseek, (self.fd, offset, 0)),
(os.write, (self.fd, data))])
def getAttrs(self):
s = self.server.avatar._runAsUser(os.fstat, self.fd)
return self.server._getAttrs(s)
def setAttrs(self, attrs):
raise NotImplementedError
class UnixSFTPDirectory:
def __init__(self, server, directory):
self.server = server
self.files = server.avatar._runAsUser(os.listdir, directory)
self.dir = directory
def __iter__(self):
return self
def next(self):
try:
f = self.files.pop(0)
except IndexError:
raise StopIteration
else:
s = self.server.avatar._runAsUser(os.lstat, os.path.join(self.dir, f))
longname = _lsLine(f, s)
attrs = self.server._getAttrs(s)
return (f, longname, attrs)
def close(self):
self.files = []
def _lsLine(name, s):
mode = s.st_mode
perms = array.array('c', '-'*10)
ft = stat.S_IFMT(mode)
if stat.S_ISDIR(ft): perms[0] = 'd'
elif stat.S_ISCHR(ft): perms[0] = 'c'
elif stat.S_ISBLK(ft): perms[0] = 'b'
elif stat.S_ISREG(ft): perms[0] = '-'
elif stat.S_ISFIFO(ft): perms[0] = 'f'
elif stat.S_ISLNK(ft): perms[0] = 'l'
elif stat.S_ISSOCK(ft): perms[0] = 's'
else: perms[0] = '!'
# user
if mode&stat.S_IRUSR:perms[1] = 'r'
if mode&stat.S_IWUSR:perms[2] = 'w'
if mode&stat.S_IXUSR:perms[3] = 'x'
# group
if mode&stat.S_IRGRP:perms[4] = 'r'
if mode&stat.S_IWGRP:perms[5] = 'w'
if mode&stat.S_IXGRP:perms[6] = 'x'
# other
if mode&stat.S_IROTH:perms[7] = 'r'
if mode&stat.S_IWOTH:perms[8] = 'w'
if mode&stat.S_IXOTH:perms[9] = 'w'
# suid/sgid
if mode&stat.S_ISUID:
if perms[3] == 'x': perms[3] = 's'
else: perms[3] = 'S'
if mode&stat.S_ISGID:
if perms[6] == 'x': perms[6] = 's'
else: perms[6] = 'S'
l = perms.tostring()
l += str(s.st_nlink).rjust(5) + ' '
un = str(s.st_uid)
l += un.ljust(9)
gr = str(s.st_gid)
l += gr.ljust(9)
sz = str(s.st_size)
l += sz.rjust(8)
l += ' '
sixmo = 60 * 60 * 24 * 7 * 26
if s.st_mtime + sixmo < time.time(): # last edited more than 6mo ago
l += time.strftime("%b %2d %Y ", time.localtime(s.st_mtime))
else:
l += time.strftime("%b %2d %H:%S ", time.localtime(s.st_mtime))
l += name
return l
components.registerAdapter(SFTPServerForUnixConchUser, UnixConchUser, filetransfer.ISFTPServer)
components.registerAdapter(SSHSessionForUnixConchUser, UnixConchUser, session.ISession)
syntax highlighted by Code2HTML, v. 0.9.1