Advertisement
Guest User

Untitled

a guest
May 13th, 2019
150
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.81 KB | None | 0 0
  1. import socket
  2. import base64
  3. import select
  4. import ssl
  5. import json
  6. import argparse
  7. import os
  8. import shutil
  9.  
  10. from email.parser import Parser
  11. from email.policy import default
  12.  
  13. CRLF = b'\r\n'
  14.  
  15.  
  16. class Config:
  17. def __init__(self, user, password, server, port, letters_dir, use_ssl):
  18. self.user = user
  19. self.password = password
  20. self.server = server
  21. self.port = port
  22. self.letters_dir = letters_dir
  23. self.use_ssl = use_ssl
  24.  
  25.  
  26. def load_config(filename):
  27. with open(filename, 'r') as config_file:
  28. d = json.load(config_file)
  29.  
  30. return Config(d['user'], d['password'], d['server'], d['port'], d['letters_dir'], d['use_ssl'])
  31.  
  32.  
  33. def parse_args():
  34. parser = argparse.ArgumentParser(description='simple pop3 client')
  35. parser.add_argument('--config', required=False, default='mail_config.json', help='config file', type=str)
  36. parser.add_argument('--count', required=False, default=1, help='amount of letters to download', type=int)
  37.  
  38. return parser.parse_args()
  39.  
  40.  
  41. class Attachment:
  42. def __init__(self, name, data):
  43. self.name = name
  44. self.data = data
  45.  
  46.  
  47. class LetterInfo:
  48. def __init__(self, text, subject, from_, to, date, attachments):
  49. self.text = text
  50. self.subject = subject
  51. self.from_ = from_
  52. self.to = to
  53. self.date = date
  54. self.attachments = attachments
  55.  
  56. def save_at(self, path):
  57. if os.path.isdir(path):
  58. shutil.rmtree(path)
  59.  
  60. os.mkdir(path)
  61.  
  62. with open(os.path.join(path, 'letter.txt'), 'w') as letter_file:
  63. letter_file.write(f'From: {self.from_}\n')
  64. letter_file.write(f'To: {self.to}\n')
  65. letter_file.write(f'Date: {self.date}\n')
  66. letter_file.write(f'Subject: {self.subject}\n')
  67. letter_file.write(f'Attachments: {" ".join(map(lambda x: x.name, self.attachments))}\n')
  68. letter_file.write(f'Text:\n{self.text}\n')
  69.  
  70. os.mkdir(os.path.join(path, 'attachments'))
  71.  
  72. for attachment in self.attachments:
  73. with open(os.path.join(path, 'attachments', attachment.name), 'wb') as attachment_file:
  74. attachment_file.write(attachment.data)
  75.  
  76.  
  77. class Pop3Response:
  78. def __init__(self, successful, data):
  79. self.successful = successful
  80. self.data = data
  81.  
  82. def __str__(self):
  83. if self.successful:
  84. return f'[SUCCESSFUL RESPONSE]\n{self.data}'
  85. return f'[FAILED RESPONSE]\n{self.data}'
  86.  
  87.  
  88. class Pop3Client:
  89. def __init__(self, host, port, user, password, use_ssl):
  90. self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  91. if use_ssl:
  92. self.__sock = ssl.wrap_socket(self.__sock, ssl_version=ssl.PROTOCOL_SSLv23)
  93.  
  94. self.__sock.connect((host, port))
  95. self.__sock.settimeout(10)
  96.  
  97. self._login(user, password)
  98.  
  99. def _login(self, user, password):
  100. print(f'Login for {user}...')
  101.  
  102. _ = self._read_single_line()
  103.  
  104. user_response = self.execute_raw('USER', [user])
  105.  
  106. if not user_response.successful:
  107. raise ValueError(f'User command failed: {user_response.data}')
  108.  
  109. pass_response = self.execute_raw('PASS', [password])
  110.  
  111. if not pass_response.successful:
  112. raise ValueError(f'Password command failed: {pass_response.data}')
  113.  
  114. print(f'Successfully login for: {user}')
  115.  
  116. def __enter__(self):
  117. return self
  118.  
  119. def __exit__(self, e, et, tb):
  120. self.__sock.close()
  121. return False
  122.  
  123. def retr(self, i):
  124. resp = self.execute_raw('RETR', [i], is_long_cmd=True)
  125.  
  126. if not resp.successful:
  127. print(resp.data)
  128. return
  129.  
  130. headers = Parser(policy=default).parsestr(resp.data)
  131.  
  132. from_ = headers['From']
  133. to = headers['To']
  134. date = headers['Date']
  135. subject = headers['Subject']
  136.  
  137. parts = list(headers.iter_parts())
  138.  
  139. text = parts[0].get_payload()
  140. attachments = []
  141.  
  142. for i in range(1, len(parts)):
  143. attachment = self._read_attachment(parts[i])
  144. if attachment:
  145. attachments.append(attachment)
  146.  
  147. return LetterInfo(text, subject, from_, to, date, attachments)
  148.  
  149. def _read_attachment(self, h):
  150. name = ''
  151.  
  152. if h['Content-Type'].maintype != 'image' or h['Content-Transfer-Encoding'].cte != 'base64':
  153. print(f'Cant read attachment of type: {h["Content-Type"].content_type} and cte {h["Content-Transfer-Encoding"].cte}')
  154. return None
  155.  
  156. for p in h['Content-Type'].split(';'):
  157. if p.find('name=') == -1:
  158. continue
  159.  
  160. pp = p.split('=')
  161. name = pp[1].strip('"')
  162. break
  163.  
  164. data = base64.b64decode(''.join(h.get_payload().split()))
  165.  
  166. return Attachment(name, data)
  167.  
  168.  
  169. def execute_raw(self, command, args=None, is_long_cmd=False):
  170. cmd = command.encode()
  171. if args:
  172. cmd += b' ' + b' '.join(map(lambda x: str(x).encode(), args))
  173.  
  174. if not cmd.endswith(CRLF):
  175. cmd += CRLF
  176.  
  177. self.__sock.sendall(cmd)
  178.  
  179. if not is_long_cmd:
  180. return self._read_response_with_single_line()
  181.  
  182. return self._read_long_response()
  183.  
  184. def _read_long_response(self):
  185. resp = self._read_response_with_single_line()
  186. lines = []
  187.  
  188. if not resp.successful:
  189. return resp
  190.  
  191. line = self._read_single_line()
  192.  
  193. while line != b'.':
  194. if not line.startswith(b'..'):
  195. lines.append(line)
  196. else:
  197. lines.append(b'.')
  198. line = self._read_single_line()
  199.  
  200. return Pop3Response(True, '\n'.join(map(lambda x: x.decode(), lines)))
  201.  
  202. def _read_response_with_single_line(self):
  203. line = self._read_single_line().decode()
  204. successful = line.startswith('+OK')
  205. data = (line[4:] if len(line) > 3 else line[3:]) if successful else (line[5:] if len(line) > 4 else line[4:])
  206.  
  207. return Pop3Response(successful, data)
  208.  
  209. def _read_single_line(self):
  210. line = bytearray()
  211.  
  212. while True:
  213. line.extend(self.__sock.recv(1))
  214.  
  215. if line.endswith(CRLF):
  216. break
  217.  
  218. return line[:-2]
  219.  
  220.  
  221. def main():
  222. try:
  223. args = parse_args()
  224. config = load_config(args.config)
  225.  
  226. if not os.path.isdir(config.letters_dir):
  227. os.mkdir(config.letters_dir)
  228.  
  229. with Pop3Client(config.server, config.port, config.user, config.password, config.use_ssl) as client:
  230. for i in range(args.count):
  231. letter = client.retr(i + 1)
  232.  
  233. letter.save_at(os.path.join(config.letters_dir, str(i + 1)))
  234. except (FileNotFoundError, KeyError):
  235. print('Invalid config file')
  236. except socket.timeout:
  237. print('Network operation timeout')
  238.  
  239.  
  240. if __name__ == "__main__":
  241. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement