pyftpd_sink.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import argparse
  2. import hashlib
  3. import logging
  4. import os
  5. import pyftpdlib.authorizers
  6. import pyftpdlib.handlers
  7. import pyftpdlib.servers
  8. LOG_LEVELS = {
  9. 'critical': logging.CRITICAL,
  10. 'error': logging.ERROR,
  11. 'warning': logging.WARNING,
  12. 'info': logging.INFO,
  13. 'debug': logging.DEBUG,
  14. }
  15. class SHA256Authorizer(pyftpdlib.authorizers.DummyAuthorizer):
  16. def validate_authentication(self, username, password, handler):
  17. # pyftpdlib/authorizers.py", line 110, in add_user
  18. # dic = {'pwd': str(password),
  19. # so we can not compare against .digest()
  20. password_hash = hashlib.sha256(password.encode()).hexdigest()
  21. if self.user_table[username]['pwd'] != password_hash.lower():
  22. raise pyftpdlib.authorizers.AuthenticationFailed()
  23. def serve(root_dir_path, username, password_sha256_hexdigest,
  24. control_port, passive_port, passive_address, umask, log_level):
  25. logging.basicConfig(level=log_level)
  26. if umask:
  27. os.umask(umask)
  28. logging.info('umask=0o%o', umask)
  29. assert os.path.isdir(root_dir_path), root_dir_path
  30. authorizer = SHA256Authorizer()
  31. authorizer.add_user(
  32. username,
  33. password_sha256_hexdigest.lower(),
  34. homedir=root_dir_path,
  35. # https://pyftpdlib.readthedocs.io/en/latest/api.html#pyftpdlib.authorizers.DummyAuthorizer.add_user
  36. # e: change dir
  37. # m: mkdir
  38. # w: write
  39. perm='emw',
  40. msg_login='renshi ni hen gaoxing',
  41. msg_quit='zaijian',
  42. )
  43. handler = pyftpdlib.handlers.FTPHandler
  44. handler.authorizer = authorizer
  45. handler.banner = 'ni hao'
  46. handler.passive_ports = (passive_port,)
  47. handler.masquerade_address = passive_address
  48. server = pyftpdlib.servers.FTPServer((None, control_port), handler)
  49. # apparently requires +1 for unknown reasons
  50. server.max_cons = 1 + 1
  51. server.serve_forever()
  52. class EnvDefaultArgparser(argparse.ArgumentParser):
  53. def add_argument(self, *args, envvar=None, **kwargs):
  54. if envvar:
  55. envvar_value = os.environ.get(envvar, None)
  56. if envvar_value:
  57. kwargs['required'] = False
  58. kwargs['default'] = envvar_value
  59. super().add_argument(*args, **kwargs)
  60. def _init_argparser():
  61. argparser = EnvDefaultArgparser()
  62. argparser.add_argument(
  63. '--root', '--root-dir',
  64. metavar='path',
  65. dest='root_dir_path',
  66. default=os.getcwd(),
  67. help='default: current working directory',
  68. )
  69. argparser.add_argument(
  70. '--user', '--username',
  71. metavar='username',
  72. dest='username',
  73. required=True,
  74. envvar='FTP_USERNAME',
  75. help='default: env var $FTP_USERNAME',
  76. )
  77. argparser.add_argument(
  78. '--pwd-hash', '--password-hash',
  79. metavar='sha256_hexdigest',
  80. dest='password_sha256_hexdigest',
  81. required=True,
  82. envvar='FTP_PASSWORD_SHA256',
  83. help='default: env var $FTP_PASSWORD_SHA256',
  84. )
  85. argparser.add_argument(
  86. '--ctrl-port', '--control-port',
  87. metavar='port',
  88. type=int,
  89. dest='control_port',
  90. envvar='FTP_CONTROL_PORT',
  91. default=2121,
  92. help='default: env var $FTP_CONTROL_PORT or 2121',
  93. )
  94. argparser.add_argument(
  95. '--pasv-port', '--passive-port',
  96. metavar='port',
  97. type=int,
  98. dest='passive_port',
  99. envvar='FTP_PASSIVE_PORT',
  100. default=62121,
  101. help='port for passive (PASV) & extended passive (EPSV) mode;'
  102. + ' default: env var $FTP_PASSIVE_PORT or 62121',
  103. )
  104. argparser.add_argument(
  105. '--pasv-addr', '--passive-address', '--masquerade-address',
  106. metavar='ip_address',
  107. dest='passive_address',
  108. envvar='FTP_PASSIVE_ADDRESS',
  109. default=None,
  110. help='address returned to client (227)'
  111. + ' after opening port for passive mode (PASV)'
  112. + ' default: socket\'s own address',
  113. )
  114. argparser.add_argument(
  115. '--umask',
  116. type=int,
  117. envvar='UMASK',
  118. default=' default: env var $UMASK',
  119. )
  120. argparser.add_argument(
  121. '--log-level',
  122. metavar='level_name',
  123. dest='log_level_name',
  124. default='info',
  125. choices=LOG_LEVELS.keys(),
  126. help='default: %(default)s',
  127. )
  128. return argparser
  129. def main():
  130. args = _init_argparser().parse_args()
  131. args.log_level = LOG_LEVELS[args.log_level_name]
  132. del args.log_level_name
  133. serve(**vars(args))