#!/bin/bash
-exec -a PyMSNt python src/main.py $*
+exec -a PyMSNt python main.py $*
# Comment out the above line and use the below for twistd
#PPATH="/usr/local/PyMSNt"
<!-- The JabberID of the transport -->
<jid>msn</jid>
+<!-- The public IP of the machine the transport is running on -->
+<ip>129.129.129.129</ip>
<!-- The component JID of the transport. Unless you're doing clustering, leave this alone -->
<!-- <compjid>msn1</compjid> -->
<!-- Use Jabber.com's XCP component protocol extensions. -->
<!-- <useXCP/> -->
+
+<!-- File transfer settings -->
+<!-- The lowest and highest ports to give to MSN clients when they are sending files to us. These should be open in the firewall -->
+<ftLowPort>6891</ftLowPort>
+<ftHighPort>6899</ftHighPort>
+<!-- Please give the port to listen for HTTP GETs here (Used in OOB file transfers). -->
+<ftOOBPort>8080</ftOOBPort>
+<!-- Please give the root URL the transport should send to clients. (You can use an Apache reverse proxy to put this on your ordinary website) -->
+<ftOOBRoot>http://jabber.org/msn/files/</ftOOBRoot>
+
<!-- You can choose which users you wish to have as administrators. These users can perform some tasks with Ad-Hoc commands that others cannot -->
<!--<admins>
<jid>admin@host.com</jid>
# Please edit config.xml instead of this file
jid = "msn"
+ip = "127.0.0.1"
compjid = ""
spooldir = ""
getAllAvatars = False
useXCP = False
+ftLowPort = "6891"
+ftHighPort = "6899"
+ftOOBPort = ""
+ftOOBRoot = "http://" + ip + "/"
+
admins = []
SUBSYNC = "http://jabber.org/protocol/roster-subsync"
MUC = "http://jabber.org/protocol/muc"
MUC_USER = MUC + "#user"
+SI = "http://jabber.org/protocol/si"
+FT = "http://jabber.org/protocol/si/profile/file-transfer"
+S5B = "http://jabber.org/protocol/bytestreams"
+IBB = "http://jabber.org/protocol/ibb"
IQGATEWAY = "jabber:iq:gateway"
IQVERSION = "jabber:iq:version"
IQREGISTER = "jabber:iq:register"
IQROSTER = "jabber:iq:roster"
IQAVATAR = "jabber:iq:avatar"
+IQOOB = "jabber:iq:oob"
+XOOB = "jabber:x:oob"
XCONFERENCE = "jabber:x:conference"
XEVENT = "jabber:x:event"
XDELAY = "jabber:x:delay"
self.pytrans.send(el)
+class DiscoRequest:
+ def __init__(self, pytrans, jid):
+ LogEvent(INFO)
+ self.doDisco()
+
+ def doDisco(self):
+ ID = self.pytrans.makeMessageID()
+ iq = Eleemnt((None, "iq"))
+ iq.attributes["to"] = jid
+ iq.attributes["from"] = config.jid
+ iq.attributes["type"] = "get"
+ query = iq.addElement("query")
+ query.attributes["xmlns"] = DISCO_INFO
+
+ d = self.pytrans.discovery.sendIq(iq)
+ d.addCallback(self.discoResponse)
+ d.addErrback(self.discoFail)
+ return d
+
+ def discoResponse(self, el):
+ iqType = el.getAttribute("type")
+ if iqType != "result":
+ return []
+
+ fro = el.getAttribute("from")
+
+ features = []
+
+ query = el.getElement("query")
+ if not query:
+ return []
+
+ for child in query.elements():
+ if child.name == "feature":
+ features.append(child.getAttribute("var"))
+
+ return features
+
+ def discoFail(self):
+ return []
+
+
+
--- /dev/null
+# Copyright 2005 James Bunton <james@delx.cjb.net>
+# Licensed for distribution under the GPL version 2, check COPYING for details
+
+import disco
+from debug import LogEvent, INFO, WARN, ERROR
+
+if utils.checkTwisted():
+ from twisted.xish.domish import Element
+else:
+ from tlib.domish import Element
+
+import random
+import sys
+
+class FTReceive:
+ """ Manager for file transfers going from MSN to Jabber. """
+
+ """
+ Plan of action for this class:
+ * Determine the FT support of the Jabber client.
+ * If we support a common protocol with them, create an
+ FTReceive_Invite object of that type. Either OOB (JEP0066) or SI(JEP0095)
+ * Call doInvite() and wait on the Deferred to send an affirmative or
+ negative to the MSN contact.
+ * The InvitationReceive sends IQ packets to the Jabber user to see if they
+ accept. If they do it creates an appropriate FTReceive_Transport to send
+ the file. Returning a Deferred for success or failure.
+
+ """
+
+ def __init__(self, session, senderJID, legacyftp):
+ self.session = session
+ self.senderJID = senderJID
+ self.legacyftp = legacyftp
+ LogEvent(INFO)
+ self.checkSupport()
+
+ def checkSupport(self):
+ def discoDone(features):
+ c1 = features.count(disco.FT)
+ c2 = features.count(disco.S5)
+ c3 = features.count(disco.IBB)
+ c4 = features.count(disco.IQOOB)
+ #if c1 > 0 and c2 > 0:
+ # socksMode()
+ #elif c1 > 0 and c3 > 0:
+ # ibbMode()
+ if c4 > 0:
+ oobMode()
+ else:
+ messageOobMode()
+
+ def discoFail(ignored=None):
+ oobMode()
+
+ d = disco.DiscoRequest(self.session.pytrans, self.session.jabberID)
+ d.addCallback(discoDone)
+ d.addErrback(discoFail)
+
+ def socksMode(self):
+ LogEvent(ERROR)
+
+ def ibbMode(self):
+ LogEvent(ERROR)
+
+ def oobMode(self):
+ LogEvent(ERROR)
+
+ def messageOobMode(self):
+ global oobSite
+ self.legacyftp.accept()
+ filename = str(random.randint(0, sys.maxint))
+ oobSite.putFile(self, filename)
+ m = Element((None, "message"))
+ m.attributes["to"] = self.session.jabberID
+ m.attributes["from"] = self.senderJID
+ m.addElement("body").addContent(config.ftOOBRoot + "/" + filename)
+ x = m.addElement("x")
+ x.attributes["xmlns"] = disco.XOOB
+ x.addElement("url").addContent(config.ftOOBRoot + "/" + filename)
+ self.session.pytrans.send(m)
+
+ def error(self):
+ #FIXME
+
+
+
+
+# Put the files up for OOB download
+
+from twisted.web import server, resource, error
+from twisted.internet import reactor
+
+from debug import LogEvent, INFO, WARN, ERROR
+
+class Connector:
+ def __init__(self, ftReceive, ftHttpPush):
+ self.ftReceive, self.ftHttpPush = ftReceive, ftHttpPush
+ self.ftReceive.legacyftp.writeTo(self)
+
+ def write(self, data):
+ self.ftHttpPush.write(data)
+
+ def close(self):
+ self.ftHttpPush.finish()
+
+ def error(self):
+ self.ftHttpPush.finish()
+ self.ftReceive.error()
+
+class FileTransfer(resource.Resource):
+ def __init__(self):
+ self.isLeaf = True
+ self.files = {}
+
+ def putFile(self, file, filename):
+ self.files[filename] = file
+
+ def render_GET(self, request):
+ filename = "/" + request.path
+ if self.files.has_key(filename):
+ file = self.files[filename]
+ Connector(file, request)
+ del self.files[filename]
+ else:
+ page = error.NoResource(message="404 File Not Found")
+ return page.render(request)
+
+oobSite = server.Site(FileTransfer())
+reactor.listenTCP(8080, site)
+
+
from debug import LogEvent, INFO, WARN, ERROR
import sha
import groupchat
+import ft
import avatar
import msnw
import config
name = "MSN Transport" # The name of the transport
url = "http://msn-transport.jabberstudio.org"
-version = "0.10.1" # The transport version
+version = "0.11-dev" # The transport version
mangle = True # XDB '@' -> '%' mangling
id = "msn" # The transport identifier
av = self.session.pytrans.avatarCache.setAvatar(imageData)
c.updateAvatar(av)
+ def gotSendRequest(self, fileReceive):
+ if not self.session: return
+ LogEvent(INFO, self.session.jabberID)
+ ft.FTReceive(self.session, msn2jid(fileReceive).userHandle, fileReceive)
+
def loggedIn(self):
if not self.session: return
LogEvent(INFO, self.session.jabberID)
def gotAvatarImage(self, to, image):
pass
+ def gotSendRequest(self, fileReceive):
+ pass
+
def listSynchronized(self):
pass
def gotAvatarImage(self, to, image):
self.msncon.gotAvatarImage(to, image)
+
+ def gotSendRequest(self, fileReceive):
+ self.msncon.gotSendRequest(fileReceive)
def switchboardReady(self, switchboard):
LogEvent(INFO, self.ident)
def gotAvatarImage(self, to, image):
if self.badConditions(): return
self.switchboardSession.gotAvatarImage(to, image)
+
+ def gotSendRequest(self, fileReceive):
+ if self.badConditions():
+ fileReceive.accept(False)
+ return
+ self.switchboardSession.gotSendRequest(fileReceive)
else:
from twisted.protocols.http import HTTPClient
import msnp11chl
+import msnft
import msnp2p
# Twisted imports
def handle_CHL(self, params):
checkParamLen(len(params), 2, 'CHL')
- response = msnp11chl.doChallenge(params[1])
+ response = msnp11chl.doChallenge(params[1])
self.sendLine("QRY %s %s %s" % (self._nextTransactionID(), msnp11chl.MSNP11_PRODUCT_ID, len(response)))
self.transport.write(response)
def __init__(self, msnobj=None):
MSNEventBase.__init__(self)
self.pendingUsers = {}
- self.cookies = {'iCookies' : {}, 'external' : {}} # will maybe be moved to a factory in the future
+ self.cookies = {'iCookies' : {}} # will maybe be moved to a factory in the future
self.p2pHandlers = []
self.msnobj = msnobj
def connectionLost(self, reason):
self.cookies['iCookies'] = {}
- self.cookies['external'] = {}
MSNEventBase.connectionLost(self, reason)
def _sendInit(self):
except KeyError:
log.msg('Received munged file transfer request ... ignoring.')
return 0
- self.gotSendRequest(fileName, fileSize, cookie, message)
- return 1
-
- def _checkFileResponse(self, message, info):
- """ helper method for checkMessage """
- try:
- cmd = info['Invitation-Command'].upper()
- cookie = info['Invitation-Cookie']
- except KeyError: return 0
- accept = (cmd == 'ACCEPT') and 1 or 0
- requested = self.cookies['iCookies'].get(cookie)
- if not requested: return 1
- requested[0].callback((accept, cookie, info))
- del self.cookies['iCookies'][cookie]
- return 1
-
- def _checkFileInfo(self, message, info):
- """ helper method for checkMessage """
- try:
- ip = info['IP-Address']
- iCookie = info['Invitation-Cookie']
- aCookie = info['AuthCookie']
- cmd = info['Invitation-Command'].upper()
- port = int(info['Port'])
- except KeyError: return 0
- accept = (cmd == 'ACCEPT') and 1 or 0
- requested = self.cookies['external'].get(iCookie)
- if not requested: return 1 # we didn't ask for this
- requested[0].callback((accept, ip, port, aCookie, info))
- del self.cookies['external'][iCookie]
+ self.gotSendRequest(msnft.MSNFTP_Receive(fileName, fileSize, message, message.userHandle, cookie, self))
return 1
def _checkP2PMessage(self, message, ctypes):
key, val = line.split(':')
info[key] = val.lstrip()
except ValueError: continue
- if self._checkFileInvitation(message, info) or self._checkFileInfo(message, info) or self._checkFileResponse(message, info): return 0
+ if self._checkFileInvitation(message, info): return 0
if self._checkP2PMessage(message, cTypes): return 0
return 1
def gotAvatarImage(self, userHandle, image):
"""
- called when we receive an avatar from a user
+ called when we receive an avatar from a contact
@param userHandle: the person who's avatar we have got
@param image: the avatar image
"""
pass
- def userTyping(self, message):
+ def gotSendRequest(self, fileReceive):
"""
- called when we receive the special type of message notifying
- us that a user is typing a message.
+ called when we receive a file send request from a contact
- @param message: the associated MSNMessage object
+ @param fileReceive: msnft.MSNFTReceive_Base instance
"""
pass
- def gotSendRequest(self, fileName, fileSize, iCookie, message):
+ def userTyping(self, message):
"""
- called when a contact is trying to send us a file.
- To accept or reject this transfer see the
- fileInvitationReply method.
+ called when we receive the special type of message notifying
+ us that a user is typing a message.
- @param fileName: the name of the file
- @param fileSize: the size of the file
- @param iCookie: the invitation cookie, used so the client can
- match up your reply with this request.
- @param message: the MSNMessage object which brought about this
- invitation (it may contain more information)
+ @param message: the associated MSNMessage object
"""
pass
def sendAvatarRequest(self, userHandle, msnobj):
handler = msnp2p.MSNP2P_Avatar_Receive(to=userHandle, fro=self.userHandle, msnobj=msnobj)
- self.p2pHandlers.append(handler)
+ self.p2pHandlers.append(handler)
msnmessage = MSNMessage(message=handler.getNextPacket())
msnmessage.setHeader("Content-Type", "application/x-msnmsgrp2p")
msnmessage.setHeader("P2P-Dest", handler.to)
self.cookies['iCookies'][cookie] = (d, m)
return d
- def fileInvitationReply(self, iCookie, accept=1):
- """
- used to reply to a file transfer invitation.
-
- @param iCookie: the invitation cookie of the initial invitation
- @param accept: whether or not you accept this transfer,
- 1 = yes, 0 = no, default = 1.
-
- @return: A Deferred, the callback for which will be fired when
- the user responds with the transfer information.
- The callback argument will be a tuple with 5 elements,
- whether or not they wish to proceed with the transfer
- (1=yes, 0=no), their ip, the port, the authentication
- cookie (see FileReceive/FileSend) and the message
- info (dict) (in case they send extra header-like info
- like Internal-IP, this doesn't necessarily need to be
- used). If you wish to proceed with the transfer see
- FileReceive.
- """
- d = Deferred()
- m = MSNMessage()
- m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
- m.message += 'Invitation-Command: %s\r\n' % (accept and 'ACCEPT' or 'CANCEL')
- m.message += 'Invitation-Cookie: %s\r\n' % str(iCookie)
- if not accept: m.message += 'Cancel-Code: REJECT\r\n'
- m.message += 'Launch-Application: FALSE\r\n'
- m.message += 'Request-Data: IP-Address:\r\n'
- m.message += '\r\n'
- m.ack = m.MESSAGE_ACK_NONE
- self.sendMessage(m)
- self.cookies['external'][iCookie] = (d, m)
- return d
-
def sendTransferInfo(self, accept, iCookie, authCookie, ip, port):
"""
send information relating to a file transfer session.
m.ack = m.MESSAGE_NACK
self.sendMessage(m)
-class FileReceive(LineReceiver):
- """
- This class provides support for receiving files from contacts.
-
- @ivar fileSize: the size of the receiving file. (you will have to set this)
- @ivar connected: true if a connection has been established.
- @ivar completed: true if the transfer is complete.
- @ivar bytesReceived: number of bytes (of the file) received.
- This does not include header data.
- """
-
- def __init__(self, auth, myUserHandle, file, directory="", overwrite=0):
- """
- @param auth: auth string received in the file invitation.
- @param myUserHandle: your userhandle.
- @param file: A string or file object represnting the file
- to save data to.
- @param directory: optional parameter specifiying the directory.
- Defaults to the current directory.
- @param overwrite: if true and a file of the same name exists on
- your system, it will be overwritten. (0 by default)
- """
- self.auth = auth
- self.myUserHandle = myUserHandle
- self.fileSize = 0
- self.connected = 0
- self.completed = 0
- self.directory = directory
- self.bytesReceived = 0
- self.overwrite = overwrite
-
- # used for handling current received state
- self.state = 'CONNECTING'
- self.segmentLength = 0
- self.buffer = ''
-
- if isinstance(file, types.StringType):
- path = os.path.join(directory, file)
- if os.path.exists(path) and not self.overwrite:
- log.msg('File already exists...')
- raise IOError, "File Exists" # is this all we should do here?
- self.file = open(os.path.join(directory, file), 'wb')
- else:
- self.file = file
-
- def connectionMade(self):
- self.connected = 1
- self.state = 'INHEADER'
- self.sendLine('VER MSNFTP')
-
- def connectionLost(self, reason):
- self.connected = 0
- self.file.close()
-
- def parseHeader(self, header):
- """ parse the header of each 'message' to obtain the segment length """
-
- if ord(header[0]) != 0: # they requested that we close the connection
- self.transport.loseConnection()
- return
- try:
- extra, factor = header[1:]
- except ValueError:
- # munged header, ending transfer
- self.transport.loseConnection()
- raise
- extra = ord(extra)
- factor = ord(factor)
- return factor * 256 + extra
-
- def lineReceived(self, line):
- temp = line.split(' ')
- if len(temp) == 1: params = []
- else: params = temp[1:]
- cmd = temp[0]
- handler = getattr(self, "handle_%s" % cmd.upper(), None)
- if handler: handler(params) # try/except
- else: self.handle_UNKNOWN(cmd, params)
-
- def rawDataReceived(self, data):
- bufferLen = len(self.buffer)
- if self.state == 'INHEADER':
- delim = 3-bufferLen
- self.buffer += data[:delim]
- if len(self.buffer) == 3:
- self.segmentLength = self.parseHeader(self.buffer)
- if not self.segmentLength: return # hrm
- self.buffer = ""
- self.state = 'INSEGMENT'
- extra = data[delim:]
- if len(extra) > 0: self.rawDataReceived(extra)
- return
-
- elif self.state == 'INSEGMENT':
- dataSeg = data[:(self.segmentLength-bufferLen)]
- self.buffer += dataSeg
- self.bytesReceived += len(dataSeg)
- if len(self.buffer) == self.segmentLength:
- self.gotSegment(self.buffer)
- self.buffer = ""
- if self.bytesReceived == self.fileSize:
- self.completed = 1
- self.buffer = ""
- self.file.close()
- self.sendLine("BYE 16777989")
- return
- self.state = 'INHEADER'
- extra = data[(self.segmentLength-bufferLen):]
- if len(extra) > 0: self.rawDataReceived(extra)
- return
-
- def handle_VER(self, params):
- checkParamLen(len(params), 1, 'VER')
- if params[0].upper() == "MSNFTP":
- self.sendLine("USR %s %s" % (self.myUserHandle, self.auth))
- else:
- log.msg('they sent the wrong version, time to quit this transfer')
- self.transport.loseConnection()
-
- def handle_FIL(self, params):
- checkParamLen(len(params), 1, 'FIL')
- try:
- self.fileSize = int(params[0])
- except ValueError: # they sent the wrong file size - probably want to log this
- self.transport.loseConnection()
- return
- self.setRawMode()
- self.sendLine("TFR")
-
- def handle_UNKNOWN(self, cmd, params):
- log.msg('received unknown command (%s), params: %s' % (cmd, params))
-
- def gotSegment(self, data):
- """ called when a segment (block) of data arrives. """
- self.file.write(data)
-
class FileSend(LineReceiver):
"""
This class provides support for sending files to other contacts.
--- /dev/null
+# Copyright 2005 James Bunton <james@delx.cjb.net>
+# Licensed for distribution under the GPL version 2, check COPYING for details
+
+from twisted.internet import reactor
+from twisted.protocols.basic import LineReceiver
+from twisted.internet.protocol import ClientFactory
+
+from msn import MSNMessage, MSNEventBase
+
+from debug import LogEvent, INFO, WARN, ERROR
+import config
+
+import random
+import sys
+
+
+class MSNFTReceive_Base:
+ # Public
+ def __init__(self, filename, filesize, message, userHandle):
+ self.connector = None
+ self.finished = False
+ self.error = False
+ self.buffer = []
+ self.filename, self.filesize, self.message, self.userHandle = filename, filesize, message, userHandle
+
+ def removeMe(self):
+ self.connector = None
+
+ def accept(self, yes):
+ pass
+
+ def writeTo(self, obj):
+ self.connector = obj
+ for data in self.buffer:
+ self.connector.write(data)
+ self.buffer = []
+ if self.finished:
+ self.connector.close()
+ if self.error:
+ self.connector.error()
+
+
+ # Private
+ def write(self, data):
+ if self.connector:
+ self.connector.write(data)
+ else:
+ self.buffer.append(data)
+
+ def close(self):
+ self.removeMe()
+ self.finished = True
+ if self.connector:
+ self.connector.close()
+
+ def gotError(self, ignored=None):
+ self.removeMe()
+ self.error = True
+ if self.connector:
+ self.connector.error()
+
+
+class MSNFTP_Ports:
+ def __init__(self):
+ try:
+ lowPort = int(config.ftLowPort)
+ highPort = int(config.ftHighPort)
+ except ValueError:
+ LogEvent(ERROR, "", "Invalid values for ftLowPort & ftHighPort. Using 6891 & 6899 respectively")
+ lowPort = 6891
+ highPort = 6899
+ self.ports = [lowPort+x for x in xrange(highPort-lowPort)]
+ self.portFree = [False] * len(self.ports)
+
+ def requestPort(self):
+ for i in xrange(len(self.ports)):
+ if self.portFree[i]:
+ self.portFree[i] = False
+ LogEvent(INFO, "", "Reserved a port")
+ return self.port[i]
+ LogEvent(INFO, "", "Out of ports")
+
+ def freePort(self, port):
+ if self.ports.count(port) > 0:
+ self.portFree[self.ports.index(port)] = True;
+ LogEvent(INFO)
+
+msnports = MSNFTP_Ports()
+
+class MSNFTP_Receive(ClientFactory):
+ def __init__(self. filename, filesize, userHandle, message, iCookie, switchboard):
+ MSNFTReceive_Base(filename, filesize, message, userHandle)
+ self.iCookie = iCookie
+ self.switchboard = switchboard
+ self.serverSocket = None
+ self.timeout = None
+ self.authCookie = str(random.randint(0, sys.maxint))
+ LogEvent(INFO, self.switchboard.userHandle)
+
+ def removeMe(self):
+ if self.serverSocket:
+ self.serverSocket.stopListening()
+ if self.timeout:
+ self.timeout.cancel()
+ LogEvent(INFO, self.switchboard.userHandle)
+
+ def accept(self, yes=True):
+ LogEvent(INFO, self.switchboard.userHandle)
+ global msnports
+ port = msnports.requestPort()
+ if not port:
+ yes = False
+ self.gotError()
+
+ m = MSNMessage()
+ m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
+ if yes:
+ m.message += 'IP-Address: %s\r\n' % str(config.ip)
+ m.message += 'Port: %s\r\n' % str(port)
+ m.message += 'AuthCookie %s\r\n' % authCookie
+ m.message += 'Sender-Connect: TRUE\r\n'
+ m.message += 'Invitation-Command: ACCEPT\r\n'
+ m.message += 'Invitation-Cookie: %s\r\n' % str(self.iCookie)
+ else:
+ m.message += 'Invitation-Command: CANCEL\r\n'
+ m.message += 'Cancel-Code: REJECT\r\n'
+ m.message += 'Launch-Application: FALSE\r\n'
+ m.message += 'Request-Data: IP-Address:\r\n'
+ m.message += '\r\n'
+ m.ack = m.MESSAGE_ACK_NONE
+ self.switchboard.sendMessage(m)
+
+ if yes:
+ self.serverSocket = reactor.listenTCP(port, self)
+ self.timeout = reactor.callLater(20, self.gotError)
+
+ def buildProtocol(self, addr):
+ LogEvent(INFO, self.switchboard.userHandle)
+ self.serverSocket.stopListening()
+ self.serverSocket = None
+ self.timeout.cancel()
+ self.timeout = None
+ return MSNFTP_FileReceive(authCookie, self.switchboard.userHandle, self)
+
+
+class MSNFTP_FileReceive(LineReceiver):
+ """
+ This class provides support for receiving files from contacts.
+
+ @ivar fileSize: the size of the receiving file. (you will have to set this)
+ @ivar connected: true if a connection has been established.
+ @ivar completed: true if the transfer is complete.
+ @ivar bytesReceived: number of bytes (of the file) received.
+ This does not include header data.
+ """
+
+ def __init__(self, auth, myUserHandle, file, directory="", overwrite=0):
+ """
+ @param auth: auth string received in the file invitation.
+ @param myUserHandle: your userhandle.
+ @param file: A string or file object represnting the file
+ to save data to.
+ @param directory: optional parameter specifiying the directory.
+ Defaults to the current directory.
+ @param overwrite: if true and a file of the same name exists on
+ your system, it will be overwritten. (0 by default)
+ """
+ self.auth = auth
+ self.myUserHandle = myUserHandle
+ self.fileSize = 0
+ self.connected = 0
+ self.completed = 0
+ self.directory = directory
+ self.bytesReceived = 0
+ self.overwrite = overwrite
+
+ # used for handling current received state
+ self.state = 'CONNECTING'
+ self.segmentLength = 0
+ self.buffer = ''
+
+ if isinstance(file, types.StringType):
+ path = os.path.join(directory, file)
+ if os.path.exists(path) and not self.overwrite:
+ log.msg('File already exists...')
+ raise IOError, "File Exists" # is this all we should do here?
+ self.file = open(os.path.join(directory, file), 'wb')
+ else:
+ self.file = file
+
+ def connectionMade(self):
+ self.connected = 1
+ self.state = 'INHEADER'
+ self.sendLine('VER MSNFTP')
+
+ def connectionLost(self, reason):
+ self.connected = 0
+ self.file.close()
+
+ def parseHeader(self, header):
+ """ parse the header of each 'message' to obtain the segment length """
+
+ if ord(header[0]) != 0: # they requested that we close the connection
+ self.transport.loseConnection()
+ return
+ try:
+ extra, factor = header[1:]
+ except ValueError:
+ # munged header, ending transfer
+ self.transport.loseConnection()
+ raise
+ extra = ord(extra)
+ factor = ord(factor)
+ return factor * 256 + extra
+
+ def lineReceived(self, line):
+ temp = line.split(' ')
+ if len(temp) == 1: params = []
+ else: params = temp[1:]
+ cmd = temp[0]
+ handler = getattr(self, "handle_%s" % cmd.upper(), None)
+ if handler: handler(params) # try/except
+ else: self.handle_UNKNOWN(cmd, params)
+
+ def rawDataReceived(self, data):
+ bufferLen = len(self.buffer)
+ if self.state == 'INHEADER':
+ delim = 3-bufferLen
+ self.buffer += data[:delim]
+ if len(self.buffer) == 3:
+ self.segmentLength = self.parseHeader(self.buffer)
+ if not self.segmentLength: return # hrm
+ self.buffer = ""
+ self.state = 'INSEGMENT'
+ extra = data[delim:]
+ if len(extra) > 0: self.rawDataReceived(extra)
+ return
+
+ elif self.state == 'INSEGMENT':
+ dataSeg = data[:(self.segmentLength-bufferLen)]
+ self.buffer += dataSeg
+ self.bytesReceived += len(dataSeg)
+ if len(self.buffer) == self.segmentLength:
+ self.gotSegment(self.buffer)
+ self.buffer = ""
+ if self.bytesReceived == self.fileSize:
+ self.completed = 1
+ self.buffer = ""
+ self.file.close()
+ self.sendLine("BYE 16777989")
+ return
+ self.state = 'INHEADER'
+ extra = data[(self.segmentLength-bufferLen):]
+ if len(extra) > 0: self.rawDataReceived(extra)
+ return
+
+ def handle_VER(self, params):
+ checkParamLen(len(params), 1, 'VER')
+ if params[0].upper() == "MSNFTP":
+ self.sendLine("USR %s %s" % (self.myUserHandle, self.auth))
+ else:
+ log.msg('they sent the wrong version, time to quit this transfer')
+ self.transport.loseConnection()
+
+ def handle_FIL(self, params):
+ checkParamLen(len(params), 1, 'FIL')
+ try:
+ self.fileSize = int(params[0])
+ except ValueError: # they sent the wrong file size - probably want to log this
+ self.transport.loseConnection()
+ return
+ self.setRawMode()
+ self.sendLine("TFR")
+
+ def handle_UNKNOWN(self, cmd, params):
+ log.msg('received unknown command (%s), params: %s' % (cmd, params))
+
+ def gotSegment(self, data):
+ """ called when a segment (block) of data arrives. """
+ self.file.write(data)
+
+