SHOW:
|
|
- or go back to the newest paste.
| 1 | #!/usr/bin/python | |
| 2 | # | |
| 3 | # This is the relay script mentioned in http://blog.zorinaq.com/?e=81 | |
| 4 | # | |
| 5 | # Listens on the address and port specified by --local-ip and --local-port, and | |
| 6 | # relay all connections to the endpoint specified by --remote-hosts and | |
| 7 | # --remote-port. Multiple remote hosts can be specified: one will be selected | |
| 8 | # randomly for each connection. | |
| 9 | # | |
| 10 | # Optionally, if --mode 1:<secret> is specified, insert the secret key as the | |
| 11 | # first bytes of data transmitted through each relayed connection, and if | |
| 12 | # --mode 2:<secret> is specified, verify and remove the secret key (ignore | |
| 13 | # the connection by discarding all data if the key does not match). | |
| 14 | # | |
| 15 | # I recommend a long hex string for the secret, for example: | |
| 16 | # $ secret=`ps aux | md5sum | cut -c 1-32` | |
| 17 | # $ ./tcprelay-secret-exp.py [...] -m 1:"$secret" | |
| 18 | ||
| 19 | #import asyncake | |
| 20 | import asyncore | |
| 21 | import socket, random, struct | |
| 22 | import re | |
| 23 | ||
| 24 | class forwarder(asyncore.dispatcher): | |
| 25 | def __init__(self, ip, port, remoteip, remoteport, mode, secret, backlog=600): | |
| 26 | asyncore.dispatcher.__init__(self) | |
| 27 | self.remoteip=remoteip | |
| 28 | self.remoteport=remoteport | |
| 29 | self.mode=mode | |
| 30 | self.secret=secret | |
| 31 | self.create_socket(socket.AF_INET,socket.SOCK_STREAM) | |
| 32 | self.set_reuse_addr() | |
| 33 | self.bind((ip,port)) | |
| 34 | self.listen(backlog) | |
| 35 | ||
| 36 | def handle_accept(self): | |
| 37 | conn, addr = self.accept() | |
| 38 | # print '--- Connect --- ' | |
| 39 | sender(receiver(conn, addr[0], self.mode, self.secret),self.remoteip,self.remoteport, addr[0]) | |
| 40 | ||
| 41 | class receiver(asyncore.dispatcher): | |
| 42 | def __init__(self, conn, client_ip, mode, secret): | |
| 43 | asyncore.dispatcher.__init__(self,conn) | |
| 44 | self.mode=mode | |
| 45 | self.secret=secret | |
| 46 | self.from_remote_buffer='' | |
| 47 | self.to_remote_buffer='' | |
| 48 | self.sender=None | |
| 49 | self.client_ip = client_ip | |
| 50 | self.zero_bytes_forwarded = True | |
| 51 | # for framing | |
| 52 | self.look_for_type = True | |
| 53 | self.field_type = None | |
| 54 | self.look_for_len_byte_nr = None | |
| 55 | self.field_len = None | |
| 56 | self.bytes_left_to_extract = None | |
| 57 | ||
| 58 | def handle_connect(self): | |
| 59 | pass | |
| 60 | ||
| 61 | def detect_and_remove_framing(self, read): | |
| 62 | processed_read = '' | |
| 63 | for b in read: | |
| 64 | if self.look_for_type: | |
| 65 | self.field_type = b | |
| 66 | self.look_for_type = False | |
| 67 | self.look_for_len_byte_nr = 0 | |
| 68 | self.field_len = 0 | |
| 69 | elif self.look_for_len_byte_nr != None: | |
| 70 | self.field_len <<= 8 | |
| 71 | self.field_len += ord(b) | |
| 72 | self.look_for_len_byte_nr += 1 | |
| 73 | if self.look_for_len_byte_nr >= 2: | |
| 74 | self.look_for_len_byte_nr = None | |
| 75 | self.bytes_left_to_extract = self.field_len | |
| 76 | elif self.bytes_left_to_extract != None: | |
| 77 | if self.field_type == 'd': | |
| 78 | processed_read += b | |
| 79 | else: | |
| 80 | pass | |
| 81 | self.bytes_left_to_extract -= 1 | |
| 82 | if self.bytes_left_to_extract == 0: | |
| 83 | self.bytes_left_to_extract = None | |
| 84 | self.look_for_type = True | |
| 85 | return processed_read | |
| 86 | ||
| 87 | def handle_read(self): | |
| 88 | """Read from TCP client.""" | |
| 89 | read = self.recv(4096) | |
| 90 | if self.mode == '1': # insert the secret key | |
| 91 | # Implement simple framing ('d' for data packets, 'p' for padding packets)
| |
| 92 | read = 'd' + struct.pack('>h', len(read)) + read
| |
| 93 | rlen = 0 | |
| 94 | padding = 1 | |
| 95 | if padding == 0: # no padding | |
| 96 | rlen = 0 | |
| 97 | elif padding == 1 and len(read) < 1500: # padding | |
| 98 | if len(read) < 1000: | |
| 99 | rlen = random.randint(1000 - len(read), 1500 - len(read)) | |
| 100 | else: | |
| 101 | rlen = random.randint(0, 1500 - len(read)) | |
| 102 | if rlen: | |
| 103 | read += 'p' + struct.pack('>h', rlen) + ('_' * rlen)
| |
| 104 | if self.zero_bytes_forwarded: | |
| 105 | read = self.secret + read | |
| 106 | self.zero_bytes_forwarded = False | |
| 107 | elif self.mode == '2': # verify and remove the secret key | |
| 108 | if self.zero_bytes_forwarded: | |
| 109 | if read.startswith(self.secret): | |
| 110 | read = read[len(self.secret):] | |
| 111 | self.zero_bytes_forwarded = False | |
| 112 | else: | |
| 113 | read = '' | |
| 114 | read = self.detect_and_remove_framing(read) | |
| 115 | # print '%04i -->'%len(read) | |
| 116 | self.from_remote_buffer += read | |
| 117 | ||
| 118 | def writable(self): | |
| 119 | return (len(self.to_remote_buffer) > 0) | |
| 120 | ||
| 121 | def handle_write(self): | |
| 122 | sent = self.send(self.to_remote_buffer) | |
| 123 | # print '%04i <--'%sent | |
| 124 | self.to_remote_buffer = self.to_remote_buffer[sent:] | |
| 125 | ||
| 126 | def handle_close(self): | |
| 127 | self.close() | |
| 128 | if self.sender: | |
| 129 | self.sender.close() | |
| 130 | ||
| 131 | class sender(asyncore.dispatcher): | |
| 132 | def __init__(self, receiver, remoteaddr, remoteport, client_ip): | |
| 133 | asyncore.dispatcher.__init__(self) | |
| 134 | self.receiver=receiver | |
| 135 | receiver.sender=self | |
| 136 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) | |
| 137 | self.connect((random.choice(remoteaddr), remoteport)) | |
| 138 | ||
| 139 | def handle_connect(self): | |
| 140 | pass | |
| 141 | ||
| 142 | def handle_read(self): | |
| 143 | """Read from TCP server.""" | |
| 144 | read = self.recv(4096) | |
| 145 | # print '<-- %04i'%len(read) | |
| 146 | self.receiver.to_remote_buffer += read | |
| 147 | ||
| 148 | def writable(self): | |
| 149 | return (len(self.receiver.from_remote_buffer) > 0) | |
| 150 | ||
| 151 | def handle_write(self): | |
| 152 | sent = self.send(self.receiver.from_remote_buffer) | |
| 153 | # print '--> %04i'%sent | |
| 154 | self.receiver.from_remote_buffer = self.receiver.from_remote_buffer[sent:] | |
| 155 | ||
| 156 | def handle_close(self): | |
| 157 | # when the buffer has not yet fully been written to the client, don't close quite yet. | |
| 158 | # handle_close() will be automatically called again by asyncore | |
| 159 | if not self.receiver.to_remote_buffer: | |
| 160 | self.close() | |
| 161 | self.receiver.close() | |
| 162 | ||
| 163 | if __name__=='__main__': | |
| 164 | import optparse | |
| 165 | parser = optparse.OptionParser() | |
| 166 | ||
| 167 | parser.add_option( | |
| 168 | '-l','--local-ip', | |
| 169 | dest='local_ip',default='127.0.0.1', | |
| 170 | help='Local IP address to bind to (for listening socket)') | |
| 171 | parser.add_option( | |
| 172 | '-p','--local-port', | |
| 173 | type='int',dest='local_port', | |
| 174 | help='Local port to bind to') | |
| 175 | parser.add_option( | |
| 176 | '-P','--remote-port', | |
| 177 | type='int',dest='remote_port', | |
| 178 | help='Remote port to connect to') | |
| 179 | parser.add_option( | |
| 180 | '-r','--remote-hosts', | |
| 181 | type='string',dest='remote_hosts',default='127.0.0.1', | |
| 182 | help='Remote host(s) to connect to, comma-separated') | |
| 183 | parser.add_option( | |
| 184 | '-m','--mode', | |
| 185 | type='string',dest='mode', | |
| 186 | help='Operating mode ("0" for not using a secret key, ' + \
| |
| 187 | '"1:<secret>" for using/inserting the specified secret key, ' + \ | |
| 188 | '"2:<secret>" for verifying/stripping the specified secret key') | |
| 189 | options, args = parser.parse_args() | |
| 190 | alladdresses = {}
| |
| 191 | for h in options.remote_hosts.split(','):
| |
| 192 | (name, aliaslist, addresslist) = socket.gethostbyname_ex(h) | |
| 193 | for a in addresslist: | |
| 194 | alladdresses[a] = None | |
| 195 | if options.mode is None: | |
| 196 | (mode, secret) = (None, None) | |
| 197 | else: | |
| 198 | (mode, secret) = options.mode.split(':', 1)
| |
| 199 | if len(secret) < 32: | |
| 200 | raise Exception('secret specified in -m option needs to be at least 32 characters long')
| |
| 201 | #x = asyncake.AsynCake() | |
| 202 | forwarder(options.local_ip, options.local_port, alladdresses.keys(), options.remote_port, mode, secret) | |
| 203 | #x.loop() | |
| 204 | asyncore.loop() |