PageRenderTime 84ms CodeModel.GetById 53ms RepoModel.GetById 1ms app.codeStats 0ms

/sagator-1.2.3/srv/proxy.py

#
Python | 317 lines | 302 code | 4 blank | 11 comment | 0 complexity | e8915c8c6e095d18c432b2f6ae45bef1 MD5 | raw file
Possible License(s): GPL-2.0
  1. '''
  2. proxy.py - HTTP proxy daemon service for sagator.
  3. (c) 2004-2009 Jan ONDREJ (SAL) <ondrejj(at)salstar.sk>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. '''
  9. import socket,re,os,sys
  10. from cStringIO import StringIO
  11. from aglib import *
  12. __all__=['http_proxy']
  13. BUF_SIZE=16386
  14. HTTP_REPORT='''\
  15. HTTP/1.1 450 Virus Found
  16. Server: SAGATOR
  17. Connection: close
  18. Content-Type: text/html; charset=iso-8859-2
  19. <html><head>
  20. <title>450 Virus Found</title>
  21. </head><body style="background-color: red">
  22. <h1 style="color: yellow">Virus Found !!!</h1>
  23. <p style="color: white">
  24. URL: %s<br/>
  25. Virus name: %s [%f, %s]<br/>
  26. Antivir output: %s<br/>
  27. </p>
  28. </body></html>
  29. '''
  30. HTTP_ERROR='''\
  31. HTTP/1.1 %03d %s
  32. Server: SAGATOR
  33. Connection: close
  34. Content-Type: text/html; charset=iso-8859-2
  35. <html><head>
  36. <title>%03d %s</title>
  37. </head><body>
  38. <h1>%s</h1>
  39. <p>%s</p>
  40. </body></html>
  41. '''
  42. METHODS='(GET|HEAD|POST|PUT|DELETE)'
  43. HOST_PORT='([a-zA-Z0-9.-]+)(:[0-9]+|)'
  44. URI='(http|ftp)://'+HOST_PORT+'/([^ ]*)'
  45. DIR='(/[^ ]*)'
  46. HTTP_VER='HTTP/([0-9]+\.[0-9]+)'
  47. reg_http_whost=re.compile('^'+METHODS+' +'+URI+' +'+HTTP_VER+'\r?$').search
  48. reg_http_nohost=re.compile('^'+METHODS+' +'+DIR+' +'+HTTP_VER+'\r?$').search
  49. reg_hostport=re.compile(HOST_PORT).search
  50. reg_hdr_line=re.compile('^([!-9;-~]+): *(.+?)\r?$',re.M).search
  51. reg_chunk=re.compile('^([0-9A-Fa-f]+)(;.*?|)\r?$').search
  52. reg_response=re.compile('^'+HTTP_VER+' +([0-9]{3}) +(.*)$').search
  53. class http_proxy(service):
  54. '''
  55. HTTP proxy service (experimental).
  56. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  57. !!! WARNING! This service is experimental! Use at your risk! !!!
  58. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  59. This service can be used as filtering HTTP proxy.
  60. Usage: http_proxy(scanners, host, port, prefork=2)
  61. Where: scanners is an array of scanners (see README.scanners for more info)
  62. host is a an ip address to bind
  63. port is a port to bind
  64. prefork is a number, which defines preforked process count
  65. Example: http_proxy(SCANNERS, '127.0.0.1', 3128)
  66. Warning! Do not forget to block access to this port for non-local users.
  67. '''
  68. name='http_proxy()'
  69. MAX_SIZE=2*1024*1024
  70. def http_error(self,code=500,s='Internal Server Error',desc=''):
  71. self.conn.sendall(HTTP_ERROR % (code,s,code,s,s,desc))
  72. self.conn.shutdown(socket.SHUT_RDWR)
  73. def scandata(self):
  74. globals.reset()
  75. for scnr in self.SCANNERS:
  76. level,virname,ret=scnr.scanbuffer(mail.data)
  77. scnr.destroy()
  78. if is_infected(level):
  79. debug.echo(3,'%s: STATUS: %s [%f,%s]' % \
  80. (self.name,virname,level,globals.found_by.name))
  81. break
  82. if is_infected(level):
  83. self.conn.sendall(HTTP_REPORT % \
  84. (mail.sender,virname,level,globals.found_by.name,str(ret)))
  85. return False
  86. else:
  87. return True
  88. def accept(self,connects=0):
  89. if connects==0:
  90. self.conn,self.addr=self.s.accept()
  91. self.conn.settimeout(300)
  92. self.connf=self.conn.makefile('rw',0)
  93. else:
  94. debug.echo(7,"Persistent connection: ",connects)
  95. #self.conn.shutdown(socket.SHUT_RDWR) # PERSISTENT CONNECTIONS ARE NOT WORKING ???
  96. req=self.connf.readline()
  97. # Connection closed? Last request?
  98. if not req:
  99. return
  100. debug.echo(3,self.name+": "+req.strip())
  101. r_http=reg_http_whost(req)
  102. if r_http:
  103. method,proto,host,port,path,version_s=r_http.groups()
  104. if port:
  105. port=int(port[1:])
  106. else:
  107. port=80
  108. else:
  109. r_http=reg_http_nohost(req)
  110. if r_http:
  111. method,path,version_s=r_http.groups()
  112. proto='http'
  113. proto,host,port=('http',None,80)
  114. else:
  115. debug.echo(1,self.name+": BAD REQUEST! [%s]" % req.strip())
  116. self.http_error(desc="Bad Request")
  117. return
  118. # method,proto,host,port,path,version defined now
  119. version=float(version_s)
  120. # Receive request header
  121. hdr = ''
  122. phdr = {'connection': '', 'proxy-connection': ''}
  123. while True:
  124. l=self.connf.readline()
  125. if not l:
  126. debug.echo(1,self.name+": client connection closed by peer")
  127. self.conn.shutdown(socket.SHUT_RDWR)
  128. return
  129. if (l=='\r') or (l=='\r\n'):
  130. # end of request header
  131. break
  132. hdr+=l
  133. r=reg_hdr_line(l)
  134. if r:
  135. phdr[r.group(1).lower()] = r.group(2)
  136. # is there an Host: in header
  137. add_into_header=''
  138. if 'host' in phdr:
  139. r=reg_hostport(phdr['host'])
  140. if r:
  141. host,port=r.groups()
  142. if port:
  143. port=int(port)
  144. else:
  145. port=80
  146. else:
  147. if port==80:
  148. add_into_header+='Host: %s\r\n' % host
  149. else:
  150. add_into_header+='Host: %s:%d\r\n' % (host,port)
  151. # Is host defined?
  152. if not host:
  153. debug.echo(1,self.name+": BAD REQUEST! [%s]" % req.strip())
  154. self.http_error(desc="Bad Request, host not defined!")
  155. return
  156. # check for content length and transfer encoding
  157. content_length=0
  158. transfer_encoding=None
  159. if method.upper()=='POST':
  160. if 'content-length' in phdr:
  161. content_length=int(phdr['content-length'])
  162. if 'transfer-encoding' in phdr:
  163. transfer_encoding=phdr['transfer-encoding'].strip()
  164. # download this page
  165. newreq='%s /%s HTTP/%s\r\n%s%s\r\n' % \
  166. (method,path,version_s,hdr,add_into_header)
  167. debug.echo(6,[req,newreq])
  168. try:
  169. cli=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  170. cli.settimeout(180)
  171. cli.connect((host,port))
  172. except socket.error,(ec,es):
  173. self.http_error(desc=es)
  174. return
  175. if content_length>0:
  176. post_data=self.conn.recv(content_length)
  177. debug.echo(4,self.name+": POST DATA: "+str(post_data))
  178. else:
  179. post_data=''
  180. cli.send(newreq+post_data)
  181. cf=cli.makefile('rw',0)
  182. # read reply
  183. while True:
  184. rhdr=cf.readline()
  185. r_response=reg_response(rhdr.strip())
  186. if not r_response:
  187. debug.echo(1,self.name+": Wrong response: %s " % rhdr.strip())
  188. resp_code=r_response.group(2)
  189. debug.echo(4,"Response: ",rhdr.strip())
  190. # read reply header
  191. preply_hdr={'connection':'', 'content-length':'0',
  192. 'transfer-encoding':'' }
  193. while True:
  194. l=cf.readline()
  195. rhdr+=l
  196. if (l=='\r') or (l=='\r\n'):
  197. # end of reply header
  198. break
  199. r=reg_hdr_line(l)
  200. if r:
  201. preply_hdr[r.group(1).lower()]=r.group(2)
  202. if resp_code=="100": # 100 Continue
  203. self.conn.sendall(rhdr)
  204. else:
  205. break
  206. content_length=int(preply_hdr['content-length'])
  207. oversized=0L
  208. debug.echo(6,"reply_header: ",rhdr)
  209. # it is encoded as chunked?
  210. if preply_hdr['transfer-encoding'][:7]=='chunked':
  211. debug.echo(5,"Transfer-encoding: chunked")
  212. data=StringIO()
  213. while True:
  214. l=cf.readline()
  215. # connection closed?
  216. if not l: break
  217. # next chunk?
  218. r_chunk=reg_chunk(l.strip())
  219. if r_chunk:
  220. chunk_size=int(r_chunk.group(1),16)
  221. debug.echo(7,'chunk: ',chunk_size)
  222. if chunk_size==0:
  223. cli.shutdown(socket.SHUT_RDWR)
  224. break
  225. data.write(cf.read(chunk_size))
  226. # read a plain CRLF
  227. cf.readline()
  228. # MAX_SIZE reached?
  229. if (oversized+data.tell())>self.MAX_SIZE:
  230. oversized+=data.tell()
  231. self.conn.sendall("%s%X\r\n" % (rhdr,len(mail.data)))
  232. self.conn.sendall(data.getvalue())
  233. data=StringIO()
  234. else:
  235. debug.echo(1,self.name+": ERROR! Chunk expected! [%s]" % l.strip())
  236. break
  237. if oversized==0:
  238. mail.data=data.getvalue()
  239. mail.sender="%s://%s:%d/%s" % (proto,host,port,path)
  240. mail.recip=[self.addr[0],str(self.addr[1])]
  241. del data
  242. debug.echo(9,'DATA:',mail.data)
  243. if self.scandata(): # clean?
  244. self.conn.sendall("%s%X\r\n" % (rhdr,len(mail.data)))
  245. self.conn.sendall(mail.data)
  246. else:
  247. debug.echo(4,self.name+": scanning skipped, oversized data!")
  248. # accept next REQUEST if connection was not closed
  249. if l:
  250. if preply_hdr['connection']!="close":
  251. if phdr['proxy-connection']!="close":
  252. self.accept(connects+1)
  253. else:
  254. mail.sender="%s://%s:%d/%s" % (proto,host,port,path)
  255. mail.recip=[self.addr[0],str(self.addr[1])]
  256. data=StringIO()
  257. if preply_hdr['connection']=="close":
  258. debug.echo(5,"Connection: close")
  259. while True:
  260. d=cli.recv(BUF_SIZE)
  261. if not d: # connection closed
  262. break
  263. if (oversized+data.tell())<=self.MAX_SIZE:
  264. data.write(d)
  265. else:
  266. if oversized==0:
  267. self.conn.sendall(rhdr+data.getvalue())
  268. oversized=data.tell()
  269. data=StringIO() # clear buffer
  270. self.conn.sendall(d)
  271. oversized+=len(d)
  272. else:
  273. debug.echo(5,"Connection: length based")
  274. while data.tell()<content_length:
  275. if data.tell()<=self.MAX_SIZE:
  276. data.write(cli.recv(content_length-data.tell()))
  277. else:
  278. oversized=data.tell()
  279. self.conn.sendall(rhdr+data.getvalue())
  280. while oversized<content_length:
  281. data=cli.recv(content_length-oversized)
  282. self.conn.sendall(data)
  283. oversized+=len(data)
  284. break
  285. if oversized==0:
  286. mail.data=data.getvalue()
  287. del data
  288. debug.echo(9,'DATA:',mail.data)
  289. if self.scandata(): # clean?
  290. self.conn.sendall(rhdr+mail.data)
  291. else:
  292. debug.echo(4,self.name+": scanning skipped, oversized data!")
  293. if preply_hdr['connection']!="close":
  294. if phdr['proxy-connection']!="close":
  295. # persistent connection
  296. self.accept(connects+1)
  297. debug.echo(6,"closing connection")
  298. self.conn.shutdown(socket.SHUT_RDWR)