PageRenderTime 94ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/demo/medusa054/ftps_server.py

https://gitlab.com/mitr/m2crypto
Python | 438 lines | 437 code | 1 blank | 0 comment | 0 complexity | 3d6e4585bb94ef95cc144fbdadf98fdf MD5 | raw file
  1. """An FTP/TLS server built on Medusa's ftp_server.
  2. Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
  3. # Python
  4. import socket, string, sys, time
  5. # Medusa
  6. from counter import counter
  7. import asynchat, asyncore, ftp_server, logger
  8. # M2Crypto
  9. from M2Crypto import SSL, version
  10. VERSION_STRING=version
  11. class ftp_tls_channel(ftp_server.ftp_channel):
  12. """FTP/TLS server channel for Medusa."""
  13. def __init__(self, server, ssl_ctx, conn, addr):
  14. """Initialise the channel."""
  15. self.ssl_ctx = ssl_ctx
  16. self.server = server
  17. self.current_mode = 'a'
  18. self.addr = addr
  19. asynchat.async_chat.__init__(self, conn)
  20. self.set_terminator('\r\n')
  21. self.client_addr = (addr[0], 21)
  22. self.client_dc = None
  23. self.in_buffer = ''
  24. self.closing = 0
  25. self.passive_acceptor = None
  26. self.passive_connection = None
  27. self.filesystem = None
  28. self.authorized = 0
  29. self._ssl_accepting = 0
  30. self._ssl_accepted = 0
  31. self._pbsz = None
  32. self._prot = None
  33. resp = '220 %s M2Crypto (Medusa) FTP/TLS server v%s ready.'
  34. self.respond(resp % (self.server.hostname, VERSION_STRING))
  35. def writable(self):
  36. return self._ssl_accepting or self._ssl_accepted
  37. def handle_read(self):
  38. """Handle a read event."""
  39. if self._ssl_accepting:
  40. self._ssl_accepted = self.socket.accept_ssl()
  41. if self._ssl_accepted:
  42. self._ssl_accepting = 0
  43. else:
  44. try:
  45. ftp_server.ftp_channel.handle_read(self)
  46. except SSL.SSLError as what:
  47. if str(what) == 'unexpected eof':
  48. self.close()
  49. else:
  50. raise
  51. def handle_write(self):
  52. """Handle a write event."""
  53. if self._ssl_accepting:
  54. self._ssl_accepted = self.socket.accept_ssl()
  55. if self._ssl_accepted:
  56. self._ssl_accepting = 0
  57. else:
  58. try:
  59. ftp_server.ftp_channel.handle_write(self)
  60. except SSL.SSLError as what:
  61. if str(what) == 'unexpected eof':
  62. self.close()
  63. else:
  64. raise
  65. def send(self, data):
  66. """Send data over SSL."""
  67. try:
  68. result = self.socket.send(data)
  69. if result <= 0:
  70. return 0
  71. else:
  72. return result
  73. except SSL.SSLError as what:
  74. self.close()
  75. self.log_info('send: closing channel %s %s' % (repr(self), what))
  76. return 0
  77. def recv(self, buffer_size):
  78. """Receive data over SSL."""
  79. try:
  80. result = self.socket.recv(buffer_size)
  81. if not result:
  82. return ''
  83. else:
  84. return result
  85. except SSL.SSLError as what:
  86. self.close()
  87. self.log_info('recv: closing channel %s %s' % (repr(self), what))
  88. return ''
  89. def found_terminator(self):
  90. """Dispatch the FTP command."""
  91. line = self.in_buffer
  92. if not len(line):
  93. return
  94. sp = string.find(line, ' ')
  95. if sp != -1:
  96. line = [line[:sp], line[sp+1:]]
  97. else:
  98. line = [line]
  99. command = string.lower(line[0])
  100. if string.find(command, 'stor') != -1:
  101. while command and command[0] not in string.letters:
  102. command = command[1:]
  103. func_name = 'cmd_%s' % command
  104. if command != 'pass':
  105. self.log('<== %s' % repr(self.in_buffer)[1:-1])
  106. else:
  107. self.log('<== %s' % line[0]+' <password>')
  108. self.in_buffer = ''
  109. if not hasattr(self, func_name):
  110. self.command_not_understood(line[0])
  111. return
  112. func = getattr(self, func_name)
  113. if not self.check_command_authorization(command):
  114. self.command_not_authorized(command)
  115. else:
  116. try:
  117. result = apply(func, (line,))
  118. except:
  119. self.server.total_exceptions.increment()
  120. (file, func, line), t, v, tbinfo = asyncore.compact_traceback()
  121. if self.client_dc:
  122. try:
  123. self.client_dc_close()
  124. except:
  125. pass
  126. resp = '451 Server error: %s, %s: file %s line: %s'
  127. self.respond(resp % (t, v, file, line))
  128. def make_xmit_channel(self):
  129. """Create a connection for sending data."""
  130. pa = self.passive_acceptor
  131. if pa:
  132. if pa.ready:
  133. conn, addr = pa.ready
  134. if self._prot:
  135. cdc = tls_xmit_channel(self, conn, self.ssl_ctx, addr)
  136. else:
  137. cdc = ftp_server.xmit_channel(self, addr)
  138. cdc.set_socket(conn)
  139. cdc.connected = 1
  140. self.passive_acceptor.close()
  141. self.passive_acceptor = None
  142. else:
  143. if self._prot:
  144. cdc = tls_xmit_channel(self, None, self.ssl_ctx, None)
  145. else:
  146. cdc = ftp_server.xmit_channel(self)
  147. else:
  148. if self._prot:
  149. cdc = tls_xmit_channel(self, None, self.ssl_ctx, self.client_addr)
  150. else:
  151. cdc = ftp_server.xmit_channel(self, self.client_addr)
  152. cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
  153. if self.bind_local_minus_one:
  154. cdc.bind(('', self.server.port - 1))
  155. try:
  156. cdc.connect(self.client_addr)
  157. except socket.error as what:
  158. self.respond('425 Cannot build data connection')
  159. self.client_dc = cdc
  160. def make_recv_channel(self, fd):
  161. """Create a connection for receiving data."""
  162. pa = self.passive_acceptor
  163. if pa:
  164. if pa.ready:
  165. conn, addr = pa.ready
  166. if self._prot:
  167. cdc = tls_recv_channel(self, conn, self.ssl_ctx, addr, fd)
  168. else:
  169. cdc = ftp_server.recv_channel(self, addr, fd)
  170. cdc.set_socket(conn)
  171. cdc.connected = 1
  172. self.passive_acceptor.close()
  173. self.passive_acceptor = None
  174. else:
  175. if self._prot:
  176. cdc = tls_recv_channel(self, None, self.ssl_ctx, None, fd)
  177. else:
  178. cdc = ftp_server.recv_channel(self, None, fd)
  179. else:
  180. if self._prot:
  181. cdc = tls_recv_channel(self, None, self.ssl_ctx, self._prot, self.client_addr, fd)
  182. else:
  183. cdc = ftp_server.recv_channel(self, self.client_addr, fd)
  184. cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
  185. try:
  186. cdc.connect(self.client_addr)
  187. except socket.error as what:
  188. self.respond('425 Cannot build data connection')
  189. self.client_dc = cdc
  190. def cmd_auth(self, line):
  191. """Prepare for TLS operation."""
  192. # XXX Handle variations.
  193. if line[1] != 'TLS':
  194. self.command_not_understood (string.join(line))
  195. else:
  196. self.respond('234 AUTH TLS successful')
  197. self._ssl_accepting = 1
  198. self.socket = SSL.Connection(self.ssl_ctx, self.socket)
  199. self.socket.setup_addr(self.addr)
  200. self.socket.setup_ssl()
  201. self.socket.set_accept_state()
  202. self._ssl_accepted = self.socket.accept_ssl()
  203. if self._ssl_accepted:
  204. self._ssl_accepting = 0
  205. def cmd_pbsz(self, line):
  206. """Negotiate size of buffer for secure data transfer. For
  207. FTP/TLS the only valid value for the parameter is '0'; any
  208. other value is accepted but ignored."""
  209. if not (self._ssl_accepting or self._ssl_accepted):
  210. return self.respond('503 AUTH TLS must be issued prior to PBSZ')
  211. self._pbsz = 1
  212. self.respond('200 PBSZ=0 successful.')
  213. def cmd_prot(self, line):
  214. """Negotiate the security level of the data connection."""
  215. if self._pbsz is None:
  216. return self.respond('503 PBSZ must be issued prior to PROT')
  217. if line[1] == 'C':
  218. self.respond('200 Protection set to Clear')
  219. self._pbsz = None
  220. self._prot = None
  221. elif line[1] == 'P':
  222. self.respond('200 Protection set to Private')
  223. self._prot = 1
  224. elif line[1] in ('S', 'E'):
  225. self.respond('536 PROT %s unsupported' % line[1])
  226. else:
  227. self.respond('504 PROT %s unsupported' % line[1])
  228. class ftp_tls_server(ftp_server.ftp_server):
  229. """FTP/TLS server for Medusa."""
  230. SERVER_IDENT = 'M2Crypto FTP/TLS Server (v%s)' % VERSION_STRING
  231. ftp_channel_class = ftp_tls_channel
  232. def __init__(self, authz, ssl_ctx, host=None, ip='', port=21, resolver=None, log_obj=None):
  233. """Initialise the server."""
  234. self.ssl_ctx = ssl_ctx
  235. self.ip = ip
  236. self.port = port
  237. self.authorizer = authz
  238. if host is None:
  239. self.hostname = socket.gethostname()
  240. else:
  241. self.hostname = host
  242. self.total_sessions = counter()
  243. self.closed_sessions = counter()
  244. self.total_files_out = counter()
  245. self.total_files_in = counter()
  246. self.total_bytes_out = counter()
  247. self.total_bytes_in = counter()
  248. self.total_exceptions = counter()
  249. asyncore.dispatcher.__init__(self)
  250. self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
  251. self.set_reuse_addr()
  252. self.bind((self.ip, self.port))
  253. self.listen(5)
  254. if log_obj is None:
  255. log_obj = sys.stdout
  256. if resolver:
  257. self.logger = logger.resolving_logger(resolver, log_obj)
  258. else:
  259. self.logger = logger.unresolving_logger(logger.file_logger(sys.stdout))
  260. l = 'M2Crypto (Medusa) FTP/TLS server started at %s\n\tAuthz: %s\n\tHostname: %s\n\tPort: %d'
  261. self.log_info(l % (time.ctime(time.time()), repr(self.authorizer), self.hostname, self.port))
  262. def handle_accept(self):
  263. """Accept a socket and dispatch a channel to handle it."""
  264. conn, addr = self.accept()
  265. self.total_sessions.increment()
  266. self.log_info('Connection from %s:%d' % addr)
  267. self.ftp_channel_class(self, self.ssl_ctx, conn, addr)
  268. class nbio_ftp_tls_actor:
  269. """TLS protocol negotiation mixin for FTP/TLS."""
  270. def tls_init(self, sock, ssl_ctx, client_addr):
  271. """Perform TLS protocol negotiation."""
  272. self.ssl_ctx = ssl_ctx
  273. self.client_addr = client_addr
  274. self._ssl_handshaking = 1
  275. self._ssl_handshake_ok = 0
  276. if sock:
  277. self.socket = SSL.Connection(self.ssl_ctx, sock)
  278. self.socket.setup_addr(self.client_addr)
  279. self.socket.setup_ssl()
  280. self._ssl_handshake_ok = self.socket.accept_ssl()
  281. if self._ssl_handshake_ok:
  282. self._ssl_handshaking = 0
  283. self.add_channel()
  284. # else the client hasn't connected yet; when that happens,
  285. # handle_connect() will be triggered.
  286. def tls_neg_ok(self):
  287. """Return status of TLS protocol negotiation."""
  288. if self._ssl_handshaking:
  289. self._ssl_handshake_ok = self.socket.accept_ssl()
  290. if self._ssl_handshake_ok:
  291. self._ssl_handshaking = 0
  292. return self._ssl_handshake_ok
  293. def handle_connect(self):
  294. """Handle a data connection that occurs after this instance came
  295. into being. When this handler is triggered, self.socket has been
  296. created and refers to the underlying connected socket."""
  297. self.socket = SSL.Connection(self.ssl_ctx, self.socket)
  298. self.socket.setup_addr(self.client_addr)
  299. self.socket.setup_ssl()
  300. self._ssl_handshake_ok = self.socket.accept_ssl()
  301. if self._ssl_handshake_ok:
  302. self._ssl_handshaking = 0
  303. self.add_channel()
  304. def send(self, data):
  305. """Send data over SSL."""
  306. try:
  307. result = self.socket.send(data)
  308. if result <= 0:
  309. return 0
  310. else:
  311. return result
  312. except SSL.SSLError as what:
  313. self.close()
  314. self.log_info('send: closing channel %s %s' % (repr(self), what))
  315. return 0
  316. def recv(self, buffer_size):
  317. """Receive data over SSL."""
  318. try:
  319. result = self.socket.recv(buffer_size)
  320. if not result:
  321. return ''
  322. else:
  323. return result
  324. except SSL.SSLError as what:
  325. self.close()
  326. self.log_info('recv: closing channel %s %s' % (repr(self), what))
  327. return ''
  328. class tls_xmit_channel(nbio_ftp_tls_actor, ftp_server.xmit_channel):
  329. """TLS driver for a send-only data connection."""
  330. def __init__(self, channel, conn, ssl_ctx, client_addr=None):
  331. """Initialise the driver."""
  332. ftp_server.xmit_channel.__init__(self, channel, client_addr)
  333. self.tls_init(conn, ssl_ctx, client_addr)
  334. def readable(self):
  335. """This channel is readable iff TLS negotiation is in progress.
  336. (Which implies a connected channel, of course.)"""
  337. if not self.connected:
  338. return 0
  339. else:
  340. return self._ssl_handshaking
  341. def writable(self):
  342. """This channel is writable iff TLS negotiation is in progress
  343. or the application has data to send."""
  344. if self._ssl_handshaking:
  345. return 1
  346. else:
  347. return ftp_server.xmit_channel.writable(self)
  348. def handle_read(self):
  349. """Handle a read event: either continue with TLS negotiation
  350. or let the application handle this event."""
  351. if self.tls_neg_ok():
  352. ftp_server.xmit_channel.handle_read(self)
  353. def handle_write(self):
  354. """Handle a write event: either continue with TLS negotiation
  355. or let the application handle this event."""
  356. if self.tls_neg_ok():
  357. ftp_server.xmit_channel.handle_write(self)
  358. class tls_recv_channel(nbio_ftp_tls_actor, ftp_server.recv_channel):
  359. """TLS driver for a receive-only data connection."""
  360. def __init__(self, channel, conn, ssl_ctx, client_addr, fd):
  361. """Initialise the driver."""
  362. ftp_server.recv_channel.__init__(self, channel, client_addr, fd)
  363. self.tls_init(conn, ssl_ctx, client_addr)
  364. def writable(self):
  365. """This channel is writable iff TLS negotiation is in progress."""
  366. return self._ssl_handshaking
  367. def handle_read(self):
  368. """Handle a read event: either continue with TLS negotiation
  369. or let the application handle this event."""
  370. if self.tls_neg_ok():
  371. ftp_server.recv_channel.handle_read(self)
  372. def handle_write(self):
  373. """Handle a write event: either continue with TLS negotiation
  374. or let the application handle this event."""
  375. if self.tls_neg_ok():
  376. ftp_server.recv_channel.handle_write(self)