""" Pytella is a low-level Gnutella protocol library. Copyright (C) 2000 Tom Goulet This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. 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 I can be reached at tomg@nova.yi.org. """ # Stuff we need. import sys,whrandom,pickle,string,signal,select # Not currently used: time, select #from socket import * # Socket stuff import socket # Defining exceptions. ListenError = "PytellaListenError" AcceptError = "PytellaAcceptError" ConnectError = "PytellaConnectError" SendError = "PytellaSendError" RecvError = "PytellaRecvError" LostSync = "PytellaLostSync" Timeout = "PytellaTimeout" def ready(s): try: if select.select([s], [], [], 0) == ([s], [], []): return(1) else: return(0) except ValueError: return(0) print "I shouldn't get to the bottom of ready()!" return() def savehosts(hosts): f = open('pytella.hosts', 'w') pickle.dump(hosts, f) f.close() return() def loaddefaulthosts(defaulthost): try: f = open('pytella.hosts.default', 'r') except IOError: hosts = [defaulthost] return(hosts) try: hosts = pickle.load(f) except EOFError: hosts = [defaulthost] f.close() return(hosts) f.close() return(hosts) def loadhosts(): defaulthost = (socket.gethostbyname('gnet.ath.cx'), 6346) print "In loadhosts(), default host is:", defaulthost try: f = open('pytella.hosts', 'r') except IOError: hosts = loaddefaulthosts(defaulthost) return(hosts) try: hosts = pickle.load(f) except EOFError: f.close() hosts = loaddefaulthosts(defaulthost) return(hosts) f.close() return(hosts) def sendpong(s, guid, ttl, hops, host):#foo if len(guid) != 16: raise SendError, 'Guid is not 16 bytes!' if ttl < 0: raise SendError, 'TTL is less than zero!' if ttl > 6: print "TTL is greater than six, changing to five." ttl = 5 if hops < 0: raise SendError, 'Hops are less than zero!' if hops > 255: raise SendError, "Over 255 hops? You've got to be kidding me." rawfunc = '\001' # It's a pong. rawttl = chr(ttl) # Convert integer into a char. rawhops = chr(hops) # Convert integer into a char. rawlength = '\016\000\000\000' # 14 byte payload for pongs. rawipaddress = '' for i in string.split(host[0], '.'): rawipaddress = rawipaddress + chr(int(i)) rawport = chr(host[1]%256)+chr(host[1]/256)# It seems to work. sheader = guid+rawfunc+rawttl+rawhops+rawlength# make the header spayload = rawport+rawipaddress+8*'\000'# make payload spacket = sheader + spayload # Make packet. try: sendraw(s, spacket, 2) # Send packet. except Timeout: raise SendError, 'Send timeout' return() # Go home. def recv(s): # Receive a Gnutella packet. try: rheader = recvraw(s, 23, 1) except Timeout: raise RecvError, 'Receive timeout' #print "Received header:", `rheader` if len(rheader) != 23: raise RecvError, 'Bad header' length = ord(rheader[19])+ord(rheader[20])*256+ord(rheader[21])*65536+ord(rheader[22])*16777216 if length > 256: raise RecvError, 'Bad header' elif length > 0: try: rpayload = recvraw(s, length, 1) except Timeout: raise RecvError, 'Receive timeout' #print "Received payload:", `rpayload` if len(rpayload) != length: raise RecvError, 'Bad payload' else: rpayload = '' return(rheader, rpayload) def parseheader(header): guid = header[0:16] # first 16 bytes are the guid if header[16] == '\000': func = 'ping' elif header[16] == '\001': func = 'pong' elif header[16] == '\100': func = 'push' elif header[16] == '\200': func = 'query' elif header[16] == '\201': func = 'queryhits' else: func = 'unknown' ttl = ord(header[17]) # Time to live is byte 17 hops = ord(header[18]) # Hops taken is byte 18. length = ord(header[19])+ord(header[20])*256+ord(header[21])*65536+ord(header[22])*16777216 return(guid, func, ttl, hops, length) def listen(host): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Create socket. try: s.bind(host) # Bind to specified port. except socket.error: raise ListenError, 'Failed to bind.' s.listen(1) # Listen for connections return(s) def accept(s): conn, addr = s.accept() try: handshake = recvraw(conn, 22, 5) except Timeout: raise AcceptError, 'Receive timeout' if handshake == 'GNUTELLA CONNECT/0.4\n\n': try: sendraw(conn, 'GNUTELLA OK\n\n', 5) except Timeout: raise AcceptError, 'Send timeout' else: raise AcceptError, 'Bad handshake' return(conn, addr) # Return the socket that people can use. def handler(signum, frame): print "A socket timeout was reached." raise Timeout, "Timeout reached." return() def recvraw(s, length, timeout): signal.signal(signal.SIGALRM, handler) signal.alarm(timeout) try: recvstring = s.recv(length) except socket.error: signal.alarm(0) raise RecvError, 'Socket error' signal.alarm(0) return(recvstring) def sendraw(s, sendstring, timeout): signal.signal(signal.SIGALRM, handler) signal.alarm(timeout) try: length = s.send(sendstring) except socket.error: signal.alarm(0) raise SendError, 'Socket error' signal.alarm(0) return(length) def makeguid(): # Create a random message identifier. f = open('/dev/urandom', 'r')#Open pseudo random device file. guid = f.read(16) # Read 16 bytes into 'guid' variable. f.close() # Close file. return(guid) # Return the random message identifier. def sendping(s, guid, ttl, hops):# Send a ping out if len(guid) != 16: raise SendError, 'Guid is not 16 bytes!' if ttl < 0: raise SendError, 'TTL is less than zero!' if ttl > 7: print "TTL is greater than seven, changing to five." ttl = 5 if hops < 0: raise SendError, 'Hops are less than zero!' if hops > 255: raise SendError, "Over 255 hops? You've got to be kidding me." rawfunc = '\000' # ping rawttl = chr(ttl) rawhops = chr(hops) rawlength = '\000\000\000\000'# pings have precisely 0 payload payload = '' spacket = guid+rawfunc+rawttl+rawhops+rawlength+payload#make sendstring try: sendraw(s, spacket, 1) # Send it except: raise SendError, 'Timeout in sending ping.' #print "Sent ping:", `spacket`#diagnostic return() # Done, now. def addhost(hosts, host): print "In beginning of addhost(), host is:", host # Example host: ('127.0.0.1', 5346) # We check for the validity of a host. if host[1] == 0: return(hosts)# Port is 0 if host[0][0:3] == '10.': return(hosts)# Private IP if host[0][0:3] == '192': return(hosts)# Private IP if host[0][0:3] == '172': # Must do more testing... #print "In addhost(), host is:", host#diagnostic tmp = int(string.split(host[0], '.')[1])# Make the if smaller if (tmp > 15) & (tmp < 32): return(hosts)# Private IP if host[0][0:2] == '0.': return(hosts)# Reserved IP if host[0][0:2] == '1.': return(hosts)# Reserved IP if host[0][0:2] == '2.': return(hosts)# Reserved IP if host[0][0:3] == '127': return(hosts)# Loopback IP if string.split(host[0], '.')[3] == '0': return(hosts)# Invalid IP if 255 in string.split(host[0], '.'): return(hosts)# Broadcast IP # Host seems valid. if len(hosts) > 2000: # Too many hosts hosts.remove(hosts[0])# Remove host on bottom of stack. if host in hosts: # If we already have this host... hosts.remove(host)#remove the old entry # Finally, add the host to the hosts list. print "Adding host:", host hosts.append(host) # Yay. return(hosts) # Return happily. def processpong(payload): # Process a pong port = 0 # port is an integer ipaddress = '' # ipaddress is a string port = ord(payload[0])+ord(payload[1])*256#sucking port out of payload ipaddress = str(ord(payload[2]))+"."+str(ord(payload[3]))+"."+str(ord(payload[4]))+"."+str(ord(payload[5]))# sucking the ip address out of the payload print "Pong processed."#diagnostic host = (ipaddress, port) return(host) # bring home the bacon def connect(host): # connect to a gnutellanet node s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Create socket. try: s.connect(host) # connect socket except socket.error, (rn, message): raise ConnectError, message handshake = 'GNUTELLA CONNECT/0.4\n\n' try: sendraw(s, handshake, 5) except Timeout: raise ConnectError, 'Send timeout.' try: countersign = recvraw(s, 13, 5) except Timeout: raise ConnectError, 'Receive timeout.' if countersign != 'GNUTELLA OK\n\n': # countersign raise ConnectError, 'Countersign: '+`countersign`+' bad!' print "Connected to "+host[0]+':'+str(host[1])+"."# diagnostic return(s) # done, go home def close(s): s.close() return() # EOF