| OLD | NEW | 
 | (Empty) | 
|    1 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. |  | 
|    2 # See LICENSE for details. |  | 
|    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_TRU
     NC, 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 = {}  # dict mapping (interface, port) -> listener |  | 
|   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] # the port |  | 
|   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         # remove all listeners |  | 
|   96         for listener in self.listeners.itervalues(): |  | 
|   97             self._runAsUser(listener.stopListening) |  | 
|   98         log.msg('avatar %s logging out (%i)' % (self.username, len(self.listener
     s))) |  | 
|   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: # we didn't get a pty-req |  | 
|  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.po
     rt) |  | 
|  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.po
     rt) |  | 
|  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 #        else: |  | 
|  221 #            tty.setraw(self.pty.pipes[0].fileno(), tty.TCSANOW) |  | 
|  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: # flag |  | 
|  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: # no echo |  | 
|  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, filetransf
     er.ISFTPServer) |  | 
|  457 components.registerAdapter(SSHSessionForUnixConchUser, UnixConchUser, session.IS
     ession) |  | 
| OLD | NEW |