~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/conch/unix.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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_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 = {}  # 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.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: # 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.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
#        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, filetransfer.ISFTPServer)
 
457
components.registerAdapter(SSHSessionForUnixConchUser, UnixConchUser, session.ISession)