File: gmailftpd.py

package info (click to toggle)
python-libgmail 0.0.8%2Bcvs20050208-2
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 172 kB
  • ctags: 412
  • sloc: python: 1,503; sh: 37; makefile: 13
file content (292 lines) | stat: -rwxr-xr-x 7,755 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#!/usr/bin/python2.3
#
# gmailftpd.py -- Demo to allow retrieval of attachments via FTP.
#
# $Revision: 1.3 $ ($Date: 2004/11/03 13:10:22 $)
#
# Author: follower@myrealbox.com
#
# License: Dual GPL 2.0 and PSF (This file only.)
#
#
# Based on smtpd.py by Barry Warsaw <barry@python.org> (Thanks Barry!)
#
# Note: Requires messages to be marked with a label named "ftp".
#       (This requirement can be removed.)
#
# TODO: Handle duplicate file names...
#

import sys
import os
import time
import socket
import asyncore
import asynchat
import logging

program = sys.argv[0]
__version__ = 'Python Gmail FTP proxy version 0.0.2'

# Allow us to run using installed `libgmail` or the one in parent directory.
try:
    import libgmail
    logging.warn("Note: Using currently installed `libgmail` version.")
except ImportError:
    # Urghhh...
    sys.path.insert(1,
                    os.path.realpath(os.path.join(os.path.dirname(__file__),
                                                  os.path.pardir)))

    import libgmail


class Devnull:
    def write(self, msg): pass
    def flush(self): pass


DEBUGSTREAM = Devnull()
NEWLINE = '\n'
EMPTYSTRING = ''

# TODO: Don't make these globals..
nextPort = 9021
my_cwd = ""
my_user = ""
my_type = "A"
ga = None

class FTPChannel(asynchat.async_chat):

    def __init__(self, server, conn, addr):
        asynchat.async_chat.__init__(self, conn)
        self.__server = server
        self.__conn = conn
        self.__addr = addr
        self.__line = []
        self.__fqdn = socket.getfqdn()
        self.__peer = conn.getpeername()
        print >> DEBUGSTREAM, 'Peer:', repr(self.__peer)
        self.push('220 %s %s' % (self.__fqdn, __version__))
        self.set_terminator('\r\n')

        self._activeDataChannel = None
        

    # Overrides base class for convenience
    def push(self, msg):
        asynchat.async_chat.push(self, msg + '\r\n')

    # Implementation of base class abstract method
    def collect_incoming_data(self, data):
        self.__line.append(data)

    # Implementation of base class abstract method
    def found_terminator(self):
        line = EMPTYSTRING.join(self.__line)
        print >> DEBUGSTREAM, 'Data:', repr(line)
        self.__line = []
        if not line:
            self.push('500 Error: bad syntax')
            return
        method = None
        i = line.find(' ')
        if i < 0:
            command = line.upper()
            arg = None
        else:
            command = line[:i].upper()
            arg = line[i+1:].strip()
        method = getattr(self, 'ftp_' + command, None)
        if not method:
            self.push('502 Error: command "%s" not implemented' % command)
            return
        method(arg)
        return

    def ftp_USER(self, arg):
        if not arg:
            self.push('501 Syntax: USER username')
        else:
            global my_user
            my_user = arg
            self.push('331 Password required')

    def ftp_PASS(self, arg = ''):
        global ga
        ga = libgmail.GmailAccount(my_user, arg)

        try:
            ga.login()
        except libgmail.GmailLoginFailure:
            self.push('530 Login failed. (Wrong username/password?)')
        else:
            self.push('230 User logged in')

    def ftp_LIST(self, arg):
        self._activeDataChannel.cmd = "LIST " + str(arg)
        self.push('226 ')


    def ftp_RETR(self, arg):
        """
        """
        if my_type != "I":
            self.push('426 Only binary transfer mode is supported')
        else:
            self._activeDataChannel.cmd = "RETR " + str(arg)
            self.push('226 ')


    def ftp_STOR(self, arg):
        """
        """
        if my_type != "I":
            self.push('426 Only binary transfer mode is supported')
        else:
            # TODO: Check this is legit, don't just copy & paste from RETR...
            self._activeDataChannel.cmd = "STOR " + str(arg)
            self.push('226 ')


    def ftp_PASV(self, arg):
        """
        """
        # *** TODO: Don't allow non-binary file transfers here?
        global nextPort
        PORT = nextPort
        nextPort += 1
        ADDR = ('127.0.0.1', PORT)
        self._activeDataChannel = DataChannel(ADDR)
        self.push('227 =127,0,0,1,%d,%d' % (PORT / 256, PORT % 256))
        self.push('150 ')


    def ftp_QUIT(self, arg):
        # args is ignored
        self.push('221 Bye')
        self.close_when_done()


    def ftp_CWD(self, arg):
        # TODO: Attach CWD (and other items) to channel...
        global my_cwd
        my_cwd = arg
        self.push('250 OK')


    def ftp_TYPE(self, arg):
        """
        """
        global my_type

        response = '200 OK'
        
        if arg in ["A", "A N"]:    
            my_type = "A"
        elif arg in ["I", "L 8"]:
            my_type = "I"
        else:
            response = "504 Unsupported TYPE parameter"
            
        self.push(response)


import tempfile

files = {}

class DataChannel(asyncore.dispatcher):
    """
    """
    def __init__(self, localaddr):
        self._localaddr = localaddr
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        # try to re-use a server port if possible
        self.set_reuse_addr()
        self.bind(localaddr)
        self.listen(5)
        print >> DEBUGSTREAM, \
              '%s started at %s\n\tLocal addr: %s\n' % (
            self.__class__.__name__, time.ctime(time.time()),
            localaddr)

        self.cmd = ""

    def handle_accept(self):
        """
        """

        if self.cmd[:4] in ["RETR", "STOR"] and my_type != "I":
            return
                    
        conn, addr = self.accept()

        self.conn = conn # Remove this?

        if self.cmd.startswith('LIST'):
            r = ga.getMessagesByLabel('ftp')
            filenames = []
            for th in r:
                for m in th:
                    for a in m.attachments:
                        files[a.filename] = a

            conn.sendall("\r\n".join(files.keys()) + "\r\n")
        elif self.cmd.startswith('RETR'):
            name_req = self.cmd[5:]
            conn.sendall(files[name_req].content)
        elif self.cmd.startswith('STOR'):
            buffer = ""
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                buffer += data

            filename = self.cmd[5:]
            tempDir = tempfile.mkdtemp()
            tempFilePath = os.path.join(tempDir, filename)
            print "Writing `%s` to `%s`." % (filename, tempFilePath)
            open(tempFilePath, "wb").write(buffer)

            ga.storeFile(tempFilePath, "ftp")

            os.remove(tempFilePath)
            os.rmdir(tempDir)

        conn.close()


class FTPServer(asyncore.dispatcher):
    def __init__(self, localaddr):
        self._localaddr = localaddr
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        # try to re-use a server port if possible
        self.set_reuse_addr()
        self.bind(localaddr)
        self.listen(5)
        print >> DEBUGSTREAM, \
              '%s started at %s\n\tLocal addr: %s\n' % (
            self.__class__.__name__, time.ctime(time.time()),
            localaddr)

    def handle_accept(self):
        conn, addr = self.accept()
        print >> DEBUGSTREAM, 'Incoming connection from %s' % repr(addr)
        channel = FTPChannel(self, conn, addr)

        

if __name__ == '__main__':
    DEBUGSTREAM = sys.stderr
    
    proxy = FTPServer(('127.0.0.1', 8021))

    try:
        asyncore.loop()
    except KeyboardInterrupt:
        pass