#!/usr/bin/python -u """ Pytecache is a GnutellaNet hosts cachter cacher servant. Copyright (C) 2000 Tom Goulet This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; 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. """ # User changeable defines. PORT = 6346 # Port Pytecache will listen on. MAXCONNS = 16 # Maximum number of connections. MAXPONGS = 64 # Maximum number of pongs to be sent. # You shouldn't need to change anything else. # Modules import socket # It's a TCP/IP program...figure it out import select # To tell if a socket is ready. import time # For sleep and timing stuff. import string # For hostname string manipulation import os # To get the user's home directory. import signal # To make timeouts possible. # Exceptions Timeout = 'Timeout' # Defines # Keys for a connection entry. SOCK = 0 # The socket object. FLAG = 1 # Connection flag. GUID = 2 # GUID of the ping. ADDR = 3 # Address of the host. TIME = 4 # A timestamp for whatever use. # Possible flags for the connection FLAG key. CLOSE = -1 # Close and remove. DUPE = 0 # Check for duplicates. HAND = 1 # Receive handshake. WELC = 2 # Send welcome. PING = 3 # Receive ping. PONG = 4 # Send pong. def main(s): hostsfile = os.environ['HOME']+'/.gnut_hosts' f = open(hostsfile, 'r') conns = [] print "Pytecache running on localhost:6346" while 1: # Connections FLAGed CLOSE are close()d and remove()d. i = 0 while i < len(conns): if conns[i][FLAG] == CLOSE: print "Closing connection:", conns[i][ADDR][0] conns[i][SOCK].close() conns.remove(conns[i]) else: i = i + 1 # If there is a new connection on the listening socket... if select.select([s], [], [], 0)[0] == [s]: conns = accept(s, conns) # deal with every connection in conns. map(handleconn, conns, [f]*len(conns)) # So I don't max the cpu. time.sleep(0.05) def accept(s, conns): if len(conns) > MAXCONNS: print conns[0][ADDR][0], time.strftime('%a %b %d %H:%M:%S %Z %Y',time.localtime(time.time())), 'Maximum connections reached.' conns[0][SOCK].close() conns.remove(conns[0]) try: sock, addr = s.accept() except: return conns print "Incoming connection:", addr[0] for i in conns: if addr[0] == i[ADDR][0]: sock.close() print "Duplicate address, closing:", addr[0] return conns conns.append({SOCK: sock, ADDR: addr, FLAG: HAND}) return conns def handler(signum, frame): signal.alarm(0) raise Timeout return def recv(conn, timeout, length): signal.signal(signal.SIGALRM, handler) signal.alarm(timeout) try: data = conn.recv(length) finally: signal.alarm(0) return data def send(conn, timeout, data): signal.signal(signal.SIGALRM, handler) signal.alarm(timeout) conn.send(data) signal.alarm(0) return def handleconn(conn, f): if conn[FLAG] == CLOSE: # This shouldn't happen. print "connection with flag of CLOSE sent to handleconn" pass elif conn[FLAG] == HAND: if select.select([conn[SOCK]], [], [], 0)[0] == [conn[SOCK]]: try: handshake = recv(conn[SOCK], 1, 22) if handshake == 'GNUTELLA CONNECT/0.4\n\n': conn[FLAG] = WELC else: conn[FLAG] = CLOSE except: signal.alarm(0) conn[FLAG] = CLOSE elif conn[FLAG] == WELC: countersign = 'GNUTELLA OK\n\n' try: send(conn[SOCK], 1, countersign) conn[FLAG] = PING except: signal.alarm(0) conn[FLAG] = CLOSE elif conn[FLAG] == PING: if select.select([conn[SOCK]], [], [], 0)[0] == [conn[SOCK]]: try: rheader = recv(conn[SOCK], 1, 23) guid = rheader[0:16] func = ord(rheader[16]) hops = ord(rheader[18]) # Packet must be a ping with 0 hops. if func == 0 and hops == 0: conn[FLAG] = PONG conn[GUID] = guid else: conn[FLAG] = CLOSE except: signal.alarm(0) conn[FLAG] = CLOSE elif conn[FLAG] in range(PONG, PONG+MAXPONGS): host = string.strip(f.readline()) if host == '': f.seek(0) host = string.strip(f.readline()) host = string.split(host) if validhost(host): host = (host[0], int(host[1])) s = conn[SOCK] rawipaddress = '' for i in string.split(host[0], '.'): rawipaddress = rawipaddress + chr(int(i)) rawport = chr(host[1]%256)+chr(host[1]/256) sheader = conn[GUID] + '\001\000\000\016\000\000\000' spayload = rawport + rawipaddress + 8*'\000' spacket = sheader + spayload try: send(s, 1, spacket) except: signal.alarm(0) conn[FLAG] = CLOSE conn[FLAG] = conn[FLAG] + 1 elif conn[FLAG] == PONG+MAXPONGS: print "Pong flood sent:", conn[ADDR][0] conn[FLAG] = CLOSE else: print "Unknown flag:", conn return(conn) def validhost(host): # We check for the validity of a host. addy = string.split(host[0], '.') if host[1] == 0: return 0 # Port is 0 elif addy[0] == '10': return 0 # Private IP elif addy[0] == '192': return 0 # Private IP elif addy[0] == '172' and int(addy[1]) in range(16, 32): return 0 # Private IP elif addy[0] == '0': return 0 # Reserved IP elif addy[0] == '1': return 0 # Reserved IP elif addy[0] == '2': return 0 # Reserved IP elif addy[0] == '127': return 0 # Loopback IP elif addy[3] == '0': return 0 # Invalid IP elif '255' in addy: return 0 # Broadcast IP # Host appears to be valid. else: return 1 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind('', PORT) s.listen(1) try: main(s) except: s.close() raise