├── PA1 for CS305 2023 Fall_SMTP Server.pdf ├── PA1 for CS305 2023 Fall_SMTP Server ├── POP_Capture.pcapng ├── SMTP_Capture.pcapng ├── pics │ ├── .DS_Store │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── assignment1-Page-2.drawio.svg │ ├── assignment1.drawio │ ├── assignment1.drawio.svg │ ├── image-20230926214029881.png │ ├── image-20230926214145709.png │ ├── image-20230926214511289.png │ ├── image-20230926214549022.png │ ├── image-20230926214750392.png │ ├── image-20230926214915332.png │ ├── image-20230926215731684.png │ ├── image-20230926220341029.png │ ├── image-20231015230121769.png │ ├── image-20231015230522151.png │ ├── image-20231015230602494.png │ └── image-20231015231255884.png ├── references │ ├── List of SMTP server return codes - Wikipedia --- SMTP服务器返回代码列表 - 维基百科.pdf │ ├── Post Office Protocol - Wikipedia --- 邮局协议 - 维基百科.pdf │ └── Simple Mail Transfer Protocol - Wikipedia --- 简单邮件传输协议 - 维基百科.pdf └── src │ ├── .DS_Store │ ├── agent.py │ ├── data │ ├── config.toml │ └── fdns.toml │ ├── requirements.txt │ └── server.py ├── README.md └── as1-benchmark-release ├── .DS_Store ├── data ├── config.toml └── fdns.toml ├── fixtures ├── 1.yml ├── 2.yml ├── 3.yml ├── 4.yml ├── 5.yml ├── 6.yml └── 7.yml └── test.py /PA1 for CS305 2023 Fall_SMTP Server.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server.pdf -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/POP_Capture.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/POP_Capture.pcapng -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/SMTP_Capture.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/SMTP_Capture.pcapng -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/.DS_Store -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/1.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/2.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/3.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/4.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/5.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/6.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/7.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/assignment1-Page-2.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
SMTP Server 1
(your implementation)
SMTP Server 1...
Agent
(provided testbench)
Agent...
SMTP
Protocol
SMTP...
POP3
Protocol
POP3...
SMTP
Protocol
SMTP...
Pseudo DNS Server
(provided)
Pseudo DNS Server...
Pseudo
DNS
query
Pseudo...
SMTP Server 2
(your implementation)
SMTP Server 2...
Text is not SVG - cannot display
-------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/assignment1.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/assignment1.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
SMTP Server 1
(requires implementation)
SMTP Server 1...
SMTP Server 2
(evaluation server)
SMTP Server 2...
Agent
(provided testbench)
Agent...
SMTP
Protocol
SMTP...
POP3
Protocol
POP3...
SMTP
Protocol
SMTP...
Pseudo DNS Server
(provided)
Pseudo DNS Server...
Pseudo
DNS
query
Pseudo...
Text is not SVG - cannot display
-------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926214029881.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926214029881.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926214145709.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926214145709.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926214511289.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926214511289.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926214549022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926214549022.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926214750392.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926214750392.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926214915332.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926214915332.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926215731684.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926215731684.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926220341029.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/image-20230926220341029.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/image-20231015230121769.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/image-20231015230121769.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/image-20231015230522151.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/image-20231015230522151.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/image-20231015230602494.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/image-20231015230602494.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/pics/image-20231015231255884.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/pics/image-20231015231255884.png -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/references/List of SMTP server return codes - Wikipedia --- SMTP服务器返回代码列表 - 维基百科.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/references/List of SMTP server return codes - Wikipedia --- SMTP服务器返回代码列表 - 维基百科.pdf -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/references/Post Office Protocol - Wikipedia --- 邮局协议 - 维基百科.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/references/Post Office Protocol - Wikipedia --- 邮局协议 - 维基百科.pdf -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/references/Simple Mail Transfer Protocol - Wikipedia --- 简单邮件传输协议 - 维基百科.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/references/Simple Mail Transfer Protocol - Wikipedia --- 简单邮件传输协议 - 维基百科.pdf -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/PA1 for CS305 2023 Fall_SMTP Server/src/.DS_Store -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/src/agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from argparse import ArgumentParser 4 | from email.mime.text import MIMEText 5 | from poplib import POP3 6 | from smtplib import SMTP 7 | 8 | import tomli 9 | 10 | parser = ArgumentParser() 11 | parser.add_argument('--email', '-e', type=str, required=True) 12 | parser.add_argument('--password', '-p', type=str, required=True) 13 | parser.add_argument('--smtp', '-s', type=str) 14 | parser.add_argument('--pop', '-P', type=str) 15 | 16 | args = parser.parse_args() 17 | 18 | with open('data/config.toml', 'rb') as f: 19 | _config = tomli.load(f) 20 | _domain = args.email.split('@')[-1] 21 | SMTP_SERVER = args.smtp or _config['agent'][_domain]['smtp'] 22 | POP_SERVER = args.pop or _config['agent'][_domain]['pop'] 23 | 24 | with open('data/fdns.toml', 'rb') as f: 25 | FDNS = tomli.load(f) 26 | 27 | 28 | def fdns_query(domain: str, type_: str) -> str | None: 29 | domain = domain.rstrip('.') + '.' 30 | return FDNS[type_][domain] 31 | 32 | 33 | def smtp(): 34 | conn = SMTP('localhost', int(fdns_query(SMTP_SERVER, 'P'))) 35 | to = [] 36 | while True: 37 | _to = input('To: ') 38 | if _to == '': 39 | break 40 | to.append(_to) 41 | subject = input('Subject: ') 42 | content = input('Content: ') 43 | msg = MIMEText(content, 'plain', 'utf-8') 44 | msg['Subject'] = subject 45 | msg['From'] = args.email 46 | conn.sendmail(args.email, to, msg.as_string()) 47 | conn.quit() 48 | 49 | 50 | def pop(): 51 | conn = POP3('localhost', int(fdns_query(POP_SERVER, 'P'))) 52 | print(conn.getwelcome()) 53 | print(conn.user(args.email)) 54 | print(conn.pass_(args.password)) 55 | while True: 56 | try: 57 | cmd = input('[pop]>>> ') 58 | if cmd == 'stat': 59 | msg, bts = conn.stat()[0:2] 60 | print(f'{msg} messages ({bts} bytes)') 61 | elif cmd == 'list': 62 | print(f'{conn.list()[1]}') 63 | elif cmd.startswith('retr '): 64 | msg = list(map(str, conn.retr(int(cmd[5:]))[1])) 65 | print('\r\n'.join(msg)) 66 | elif cmd.startswith('dele '): 67 | print(conn.dele(int(cmd[5:]))) 68 | elif cmd == 'rset': 69 | print(conn.rset()) 70 | elif cmd == 'noop': 71 | print(conn.noop()) 72 | elif cmd == 'quit': 73 | print(conn.quit()) 74 | break 75 | else: 76 | print('Invalid command') 77 | except KeyboardInterrupt: 78 | conn.rset() 79 | raise 80 | except Exception as e: 81 | print('-ERR!!') 82 | print(repr(e)) 83 | 84 | 85 | if __name__ == '__main__': 86 | while True: 87 | try: 88 | cmd = input('[smtp|pop|exit]>>> ') 89 | if cmd == 'smtp': 90 | smtp() 91 | elif cmd == 'pop': 92 | pop() 93 | elif cmd == 'exit': 94 | break 95 | else: 96 | print('Invalid command') 97 | except KeyboardInterrupt: 98 | break 99 | except Exception as e: 100 | print('-ERR!!') 101 | print(repr(e)) 102 | -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/src/data/config.toml: -------------------------------------------------------------------------------- 1 | [agent] 2 | "mail.sustech.edu.cn" = { smtp = "smtp.exmail.qq.com", pop = "pop.exmail.qq.com" } 3 | "gmail.com" = { smtp = "smtp.gmail.com", pop = "pop.gmail.com" } 4 | 5 | [server] 6 | "exmail.qq.com" = { smtp = 1025, pop = 3110 } 7 | "gmail.com" = { smtp = 2025, pop = 2110 } 8 | 9 | [accounts."exmail.qq.com"] 10 | "usr1@mail.sustech.edu.cn" = "pass1" 11 | "usr2@mail.sustech.edu.cn" = "pass2" 12 | 13 | [accounts."gmail.com"] 14 | "usr@gmail.com" = "pass" 15 | -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/src/data/fdns.toml: -------------------------------------------------------------------------------- 1 | [MX] 2 | "mail.sustech.edu.cn." = "mxbiz1.qq.com." 3 | "gmail.com." = "gmail-smtp-in.l.google.com." 4 | 5 | [P] 6 | "smtp.exmail.qq.com." = "1025" 7 | "smtp.gmail.com." = "2025" 8 | "pop.exmail.qq.com." = "3110" 9 | "pop.gmail.com." = "2110" 10 | "mxbiz1.qq.com." = "1025" 11 | "gmail-smtp-in.l.google.com." = "2025" 12 | -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/src/requirements.txt: -------------------------------------------------------------------------------- 1 | tomli == 2.0.1 2 | timeout_decorator == 0.5.0 3 | PyYAML == 6.0.1 -------------------------------------------------------------------------------- /PA1 for CS305 2023 Fall_SMTP Server/src/server.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from argparse import ArgumentParser 4 | # from email.mime.text import MIMEText 5 | from queue import Queue 6 | import socket 7 | from socketserver import ThreadingTCPServer, BaseRequestHandler 8 | from threading import Thread 9 | 10 | import tomli 11 | 12 | 13 | def student_id() -> int: 14 | return 12210000 # TODO: replace with your SID 15 | 16 | 17 | parser = ArgumentParser() 18 | parser.add_argument('--name', '-n', type=str, required=True) 19 | parser.add_argument('--smtp', '-s', type=int) 20 | parser.add_argument('--pop', '-p', type=int) 21 | 22 | args = parser.parse_args() 23 | 24 | with open('data/config.toml', 'rb') as f: 25 | _config = tomli.load(f) 26 | SMTP_PORT = args.smtp or int(_config['server'][args.name]['smtp']) 27 | POP_PORT = args.pop or int(_config['server'][args.name]['pop']) 28 | ACCOUNTS = _config['accounts'][args.name] 29 | MAILBOXES = {account: [] for account in ACCOUNTS.keys()} 30 | 31 | with open('data/fdns.toml', 'rb') as f: 32 | FDNS = tomli.load(f) 33 | 34 | ThreadingTCPServer.allow_reuse_address = True 35 | 36 | 37 | def fdns_query(domain: str, type_: str) -> str | None: 38 | domain = domain.rstrip('.') + '.' 39 | return FDNS[type_][domain] 40 | 41 | 42 | class POP3Server(BaseRequestHandler): 43 | def handle(self): 44 | conn = self.request 45 | ... 46 | 47 | 48 | class SMTPServer(BaseRequestHandler): 49 | def handle(self): 50 | conn = self.request 51 | ... 52 | 53 | 54 | if __name__ == '__main__': 55 | if student_id() % 10000 == 0: 56 | raise ValueError('Invalid student ID') 57 | 58 | smtp_server = ThreadingTCPServer(('', SMTP_PORT), SMTPServer) 59 | pop_server = ThreadingTCPServer(('', POP_PORT), POP3Server) 60 | Thread(target=smtp_server.serve_forever).start() 61 | Thread(target=pop_server.serve_forever).start() 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS305-23F-Assignment-1 2 | This is a repository for Programming Assignment 1 in SUSTech CS305, Computer Network course. 3 | 4 | If you have any problem about this assignment, you are welcomed to raise an issue anytime! 5 | 6 | Hope you can ENJOY your programming! <3 7 | 8 | 9 | 10 | #### Update: 11 | 12 | 2023.10.19 13 | 14 | Modify some requires in POP3 server: For command RETR, it requies a specific parameter as the message number to retrieve. (e.g. RETR 1) 15 | 16 | 17 | 18 | 2023.10.26 19 | 20 | Simple test scripts released for students to do self-checking. Here is a tutorial for usage: 21 | 22 | ① Use cmd under the **project directory** (default: .../assign1/src) and type command: 23 | 24 | ``` 25 | pip install -r requirements.txt 26 | ``` 27 | 28 | ② Move your "server.py" to same directory as "test.py"(.../as1-benchmark-release). 29 | 30 | ③ Use you cmd to run: 31 | 32 | ``` 33 | python test.py 34 | ``` 35 | 36 | for running all the test cases together, and 37 | 38 | ``` 39 | python test.py -f X 40 | ``` 41 | 42 | '**X**' here should be replaced by the index of the specific test case you want to run individually. There is no need for you to additionally run servers. The test script will pull up the server it needs itself, and shut it down when finish testing. 43 | 44 | ④ All the test cases are put in the 'fixtures' folder. Feel free to inspect them or add your own cases for test imitating the cases given. These cases cover a lot of pts, but may not be completely consistent with the sample we will ultimately use for testing. (May be 90% similar?/thinking) 45 | 46 | Again, we will **NOT cover any (corner) case** that are not mentioned in the Assignment Document. Its quite easy for you to get through the test as if the MOST basic work is done. 47 | 48 | Hope you have fun programming in CS305 class! 49 | 50 | 51 | 52 | 2023.10.26 53 | 54 | Test scrpits fixed. What is worth mentioning is that: 55 | 56 | * All the tests about POP3 are based on SMTP tests. That is to say, only if you have passed all the tests related to SMTP can you possibly pass the POP3 test. If you failed with finishing some implementations of SMTP which causes you failed to pass all POP3 tests, but you think you have implemented some functions of POP3, **prove it** (with screen shot etc.) in your final report. 57 | * For the screenshot of Wireshark, you just need to show your result of capturing the **SMTP AND POP3** packets when running test case "**4.yml**". 58 | 59 | * You are suggested to run the tests under **Linux** environment. 60 | 61 | 62 | 63 | 2023.10.27 64 | 65 | Fix test case 7.yml 66 | -------------------------------------------------------------------------------- /as1-benchmark-release/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ounisangw/CS305-23F-Assignment-1/da447b956d031cfd3291d319e7840802d37c8d14/as1-benchmark-release/.DS_Store -------------------------------------------------------------------------------- /as1-benchmark-release/data/config.toml: -------------------------------------------------------------------------------- 1 | [agent] 2 | "mail.sustech.edu.cn" = { smtp = "smtp.exmail.qq.com", pop = "pop.exmail.qq.com" } 3 | "gmail.com" = { smtp = "smtp.gmail.com", pop = "pop.gmail.com" } 4 | 5 | [server] 6 | "exmail.qq.com" = { smtp = 1125, pop = 1210 } 7 | "gmail.com" = { smtp = 2125, pop = 2210 } 8 | 9 | [accounts."exmail.qq.com"] 10 | "usr1@mail.sustech.edu.cn" = "password1" 11 | "usr2@mail.sustech.edu.cn" = "password2" 12 | 13 | [accounts."gmail.com"] 14 | "usr1@gmail.com" = "password1" 15 | -------------------------------------------------------------------------------- /as1-benchmark-release/data/fdns.toml: -------------------------------------------------------------------------------- 1 | [MX] 2 | "mail.sustech.edu.cn." = "mxbiz1.qq.com." 3 | "gmail.com." = "gmail-smtp-in.l.google.com." 4 | 5 | [P] 6 | "smtp.exmail.qq.com." = "1125" 7 | "smtp.gmail.com." = "2125" 8 | "pop.exmail.qq.com." = "1210" 9 | "pop.gmail.com." = "2210" 10 | "mxbiz1.qq.com." = "1125" 11 | "gmail-smtp-in.l.google.com." = "2125" 12 | -------------------------------------------------------------------------------- /as1-benchmark-release/fixtures/1.yml: -------------------------------------------------------------------------------- 1 | scenario: POP3 USER, PASS and QUIT 2 | credit: 15 3 | 4 | servers: 5 | - domain: 'exmail.qq.com' 6 | type: student 7 | - domain: 'gmail.com' 8 | type: student 9 | 10 | steps: 11 | - name: Login the account in different domain 12 | uses: login 13 | args: 14 | user: 'usr2@mail.sustech.edu.cn' 15 | pass: 'password2' 16 | - uses: quit 17 | -------------------------------------------------------------------------------- /as1-benchmark-release/fixtures/2.yml: -------------------------------------------------------------------------------- 1 | scenario: Send an email to another user in the different domain and test LIST 2 | credit: 5 3 | 4 | servers: 5 | - domain: 'exmail.qq.com' 6 | type: student 7 | - domain: 'gmail.com' 8 | type: student 9 | 10 | steps: 11 | - name: Send email 12 | uses: sendmail 13 | args: 14 | from: 'usr1@mail.sustech.edu.cn' 15 | to: 16 | - 'usr1@gmail.com' 17 | subject: 'Test Subject' 18 | body: 'Test Body' 19 | - uses: wait 20 | args: 21 | seconds: 3 22 | - name: Login the account in different domain 23 | uses: login 24 | args: 25 | user: 'usr1@gmail.com' 26 | pass: 'password1' 27 | - name: List the email 28 | uses: list 29 | expect: 30 | - mesg_num: 1 31 | - uses: quit 32 | -------------------------------------------------------------------------------- /as1-benchmark-release/fixtures/3.yml: -------------------------------------------------------------------------------- 1 | scenario: Send an email to another user in the different domain and test STAT 2 | credit: 5 3 | 4 | servers: 5 | - domain: 'exmail.qq.com' 6 | type: student 7 | - domain: 'gmail.com' 8 | type: student 9 | 10 | steps: 11 | - name: Send email 12 | uses: sendmail 13 | args: 14 | from: 'usr1@mail.sustech.edu.cn' 15 | to: 16 | - 'usr1@gmail.com' 17 | subject: 'Test Subject' 18 | body: 'Test Body' 19 | - uses: wait 20 | args: 21 | seconds: 3 22 | - name: Login my account 23 | uses: login 24 | args: 25 | user: 'usr1@gmail.com' 26 | pass: 'password1' 27 | - name: Show the status 28 | uses: stat 29 | expect: 30 | - count: 1 31 | - uses: quit 32 | -------------------------------------------------------------------------------- /as1-benchmark-release/fixtures/4.yml: -------------------------------------------------------------------------------- 1 | scenario: Send an email to another user in the same domain and test RETR, DELE, RSET and NOOP 2 | credit: 5 3 | 4 | servers: 5 | - domain: 'exmail.qq.com' 6 | type: student 7 | - domain: 'gmail.com' 8 | type: student 9 | 10 | steps: 11 | - name: Send email 12 | uses: sendmail 13 | args: 14 | from: 'usr1@mail.sustech.edu.cn' 15 | to: 16 | - 'usr2@mail.sustech.edu.cn' 17 | subject: 'Test Subject2' 18 | body: 'Test Body2' 19 | - uses: wait 20 | args: 21 | seconds: 3 22 | - name: Login the account in different domain 23 | uses: login 24 | args: 25 | user: 'usr2@mail.sustech.edu.cn' 26 | pass: 'password2' 27 | - name: Retrieve the email 28 | uses: retr 29 | args: 30 | which: 1 31 | expect: 32 | from: 'usr1@mail.sustech.edu.cn' 33 | subject: 'Test Subject2' 34 | - name: Delete the email 35 | uses: dele 36 | args: 37 | which: 1 38 | expect: 39 | resp: "b'+OK" 40 | - name: Reset the deletion 41 | uses: rset 42 | expect: 43 | resp: "b'+OK" 44 | - name: Cmd Noop 45 | uses: noop 46 | expect: 47 | resp: "b'+OK" 48 | - uses: quit 49 | -------------------------------------------------------------------------------- /as1-benchmark-release/fixtures/5.yml: -------------------------------------------------------------------------------- 1 | scenario: Send an email to another user in the different domain and test LIST 2 | credit: 50 3 | 4 | servers: 5 | - domain: 'exmail.qq.com' 6 | type: student 7 | - domain: 'gmail.com' 8 | type: student 9 | 10 | steps: 11 | - name: Send email 12 | uses: sendmail 13 | args: 14 | from: 'usr1@mail.sustech.edu.cn' 15 | to: 16 | - 'usr1@gmail.com' 17 | subject: 'Test Subject' 18 | body: 'Test Body' 19 | excepted_error: 20 | 'SMTPHeloError': 0 21 | 'SMTPSenderRefused': 10 22 | 'SMTPRecipientsRefused': 20 23 | 'SMTPDataError': 30 24 | - uses: wait 25 | args: 26 | seconds: 3 27 | 28 | - name: Login the account in different domain 29 | uses: login 30 | args: 31 | user: 'usr1@gmail.com' 32 | pass: 'password1' 33 | - name: List the email 34 | uses: list 35 | expect: 36 | - mesg_num: 1 37 | - uses: quit 38 | -------------------------------------------------------------------------------- /as1-benchmark-release/fixtures/6.yml: -------------------------------------------------------------------------------- 1 | scenario: Send an email to another user non-exist in different domain 2 | credit: 5 3 | 4 | servers: 5 | - domain: 'exmail.qq.com' 6 | type: student 7 | - domain: 'gmail.com' 8 | type: student 9 | 10 | steps: 11 | - name: Send email 12 | uses: sendmail 13 | args: 14 | from: 'usr1@mail.sustech.edu.cn' 15 | to: 16 | - 'error@gmail.com' 17 | subject: 'Test Subject6' 18 | body: 'Test Body6' 19 | excepted_error: 20 | 'SMTPRecipientsRefused': 5 21 | 'SMTPDataError': 5 22 | - uses: wait 23 | args: 24 | seconds: 5 25 | - name: Login the account in different domain 26 | uses: login 27 | args: 28 | user: 'usr1@mail.sustech.edu.cn' 29 | pass: 'password1' 30 | - name: List the email 31 | uses: list 32 | expect: 33 | - mesg_num: 1 34 | - uses: quit 35 | -------------------------------------------------------------------------------- /as1-benchmark-release/fixtures/7.yml: -------------------------------------------------------------------------------- 1 | scenario: Send an email to another user from a non-existing email address 2 | credit: 5 3 | 4 | servers: 5 | - domain: 'exmail.qq.com' 6 | type: student 7 | - domain: 'gmail.com' 8 | type: student 9 | 10 | steps: 11 | - name: Send email 12 | uses: sendmail 13 | args: 14 | from: 'error@mail.sustech.edu.cn' 15 | to: 16 | - 'usr1@gmail.com' 17 | subject: 'Test Subject7' 18 | body: 'Test Body7' 19 | excepted_error: 20 | 'SMTPSenderRefused': 5 21 | - uses: wait 22 | args: 23 | seconds: 3 24 | - name: Login the account in different domain 25 | uses: login 26 | args: 27 | user: 'usr1@gmail.com' 28 | pass: 'password1' 29 | - name: Show the status 30 | uses: stat 31 | expect: 32 | - count: 0 33 | - uses: quit 34 | -------------------------------------------------------------------------------- /as1-benchmark-release/test.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import sys 5 | import subprocess as sp 6 | import time 7 | from argparse import ArgumentParser 8 | from collections import Counter 9 | from contextlib import suppress 10 | from dataclasses import dataclass 11 | from email.mime.text import MIMEText 12 | from keyword import iskeyword 13 | from poplib import POP3 14 | from smtplib import SMTP 15 | from socketserver import ThreadingTCPServer 16 | from typing import Literal 17 | from timeout_decorator import timeout, TimeoutError 18 | import tomli 19 | import yaml 20 | import psutil 21 | 22 | ThreadingTCPServer.allow_reuse_address = True 23 | 24 | with open('data/config.toml', 'rb') as f: 25 | CONFIG = tomli.load(f) 26 | 27 | with open('data/fdns.toml', 'rb') as f: 28 | FDNS = tomli.load(f) 29 | 30 | conn: POP3 31 | 32 | 33 | def safe_dict(d: dict[str, object]) -> dict[str, object]: 34 | d = d or {} 35 | return {f'{k}_' if iskeyword(k) else k: v for k, v in d.items()} 36 | 37 | 38 | def fdns_query(domain: str, type_: str) -> str | None: 39 | domain = domain.rstrip('.') + '.' 40 | return FDNS[type_][domain] 41 | 42 | 43 | class TestScenario: 44 | def __init__(self, filename: str): 45 | def to_runnable(server: dict[str, str]) -> sp.Popen: 46 | code = 'server.py' if server['type'] == 'student' else 'std_server.py' 47 | return sp.Popen(f'python3 {code} -n {server["domain"]}', shell=True, 48 | stdout=sp.DEVNULL, stderr=sp.DEVNULL 49 | ) 50 | 51 | with open(filename, 'r', encoding='utf-8') as f: 52 | desc = yaml.load(f, Loader=yaml.FullLoader) 53 | 54 | self.scenario = desc['scenario'] 55 | self.credit = int(desc['credit']) 56 | self.servers = desc['servers'] 57 | self.subprocesses = list(map(to_runnable, self.servers)) 58 | self.steps = [TestStep(**safe_dict(step)) for step in desc['steps']] 59 | 60 | def __enter__(self) -> TestScenario: 61 | print(f'Setup scenario: {self.scenario}') 62 | time.sleep(2) 63 | return self 64 | 65 | def __exit__(self, exc_type, exc_val, exc_tb): 66 | print(f'Teardown scenario: {self.scenario}') 67 | for proc in self.subprocesses: 68 | proccccc = psutil.Process(proc.pid) 69 | for c in proccccc.children(recursive=True): 70 | c.kill() 71 | proc.kill() 72 | time.sleep(3) 73 | 74 | def run(self) -> TestResult: 75 | for step in self.steps: 76 | try: 77 | try: 78 | step.run() 79 | except Exception as e: 80 | if step.excepted_error is None: 81 | raise e 82 | ee = e 83 | while type(ee) is not Exception: 84 | ee_type = type(ee).__name__ 85 | if ee_type in step.excepted_error: 86 | return TestResult( 87 | scenario=self.scenario, 88 | possible_credit=self.credit, 89 | actual_credit=step.excepted_error[ee_type], 90 | stats='PARTIALLY PASSED', 91 | message=f'Raised error in step {step.name}: {ee_type} -> {step.excepted_error[ee_type]} pts', 92 | ) 93 | ee = ee.__class__.__bases__[0] 94 | raise e 95 | except TimeoutError: 96 | return TestResult( 97 | scenario=self.scenario, 98 | possible_credit=self.credit, 99 | actual_credit=0, 100 | stats='ERROR', 101 | message=f'Timeout in step {step.name}', 102 | ) 103 | except AssertionError: 104 | return TestResult( 105 | scenario=self.scenario, 106 | possible_credit=self.credit, 107 | actual_credit=0, 108 | stats='FAILED', 109 | message=f'Result mismatch in step {step.name}', 110 | ) 111 | except Exception as e: 112 | return TestResult( 113 | scenario=self.scenario, 114 | possible_credit=self.credit, 115 | actual_credit=0, 116 | stats='ERROR', 117 | message=f'Error in step {step.name}: {repr(e)}', 118 | ) 119 | return TestResult( 120 | scenario=self.scenario, 121 | possible_credit=self.credit, 122 | actual_credit=self.credit, 123 | stats='PASSED', 124 | message=None, 125 | ) 126 | 127 | 128 | def WAIT(seconds: float): 129 | time.sleep(seconds) 130 | 131 | 132 | @timeout(10) 133 | def SENDMAIL(from_: str, to: list[str] | str, subject: str, body: str): 134 | domain = from_.split('@')[-1] 135 | smtp_server = CONFIG['agent'][domain]['smtp'] 136 | conn = SMTP('localhost', int(fdns_query(smtp_server, 'P'))) 137 | msg = MIMEText(body, 'plain', 'utf-8') 138 | msg['Subject'] = subject 139 | msg['From'] = from_ 140 | conn.sendmail(from_, to, msg.as_string()) 141 | WAIT(3) 142 | conn.quit() 143 | 144 | 145 | @timeout(8) 146 | def LOGIN(user: str, pass_: str): 147 | global conn 148 | domain = user.split('@')[-1] 149 | pop3_server = CONFIG['agent'][domain]['pop'] 150 | conn = POP3('localhost', int(fdns_query(pop3_server, 'P'))) 151 | conn.user(user) 152 | conn.pass_(pass_) 153 | 154 | 155 | @timeout(8) 156 | def STAT() -> dict[str, int]: 157 | return [dict(zip(('count', 'size'), conn.stat()))] 158 | 159 | 160 | @timeout(8) 161 | def LIST() -> list[dict[dict, int]]: 162 | res = conn.list()[1] 163 | return [dict(zip(('mesg_num', 'octets'), map(int, r.split()))) for r in res] 164 | 165 | 166 | @timeout(8) 167 | def RETR(which: int) -> dict[str, str]: 168 | data0 = conn.retr(which) 169 | data = data0[1] 170 | sub, from_ = None, None 171 | for i, line in enumerate(data): 172 | if line.startswith(b'Subject:'): 173 | sub = line.decode().split('Subject: ', 1)[1] 174 | for i, line in enumerate(data): 175 | if line.startswith(b'From:'): 176 | from_ = line.decode().split('From: ', 1)[1] 177 | return {'from': from_, 'subject': sub} 178 | 179 | 180 | @timeout(8) 181 | def DELE(which: int): 182 | resp = str(conn.dele(which)) 183 | return {'resp': resp} 184 | 185 | 186 | @timeout(8) 187 | def RSET(): 188 | resp = str(conn.rset()) 189 | return {'resp': resp} 190 | 191 | 192 | @timeout(8) 193 | def QUIT(): 194 | conn.quit() 195 | 196 | @timeout(8) 197 | def NOOP(): 198 | resp = str(conn.noop()) 199 | return {'resp': resp} 200 | 201 | 202 | @dataclass 203 | class TestStep: 204 | uses: str 205 | name: str | None = None 206 | args: dict[str, object] | None = None 207 | expect: dict[str, object] | None = None 208 | excepted_error: dict[str, int] | None = None 209 | 210 | def run(self): 211 | self.name = self.name or self.uses 212 | 213 | func = globals()[self.uses.upper()] 214 | actual = func(**safe_dict(self.args)) 215 | print('TEST>>> Step:', self.name) 216 | if self.expect is not None: 217 | print(' >>> Expect:', self.expect) 218 | print(' >>> Actual:', actual) 219 | 220 | if type(self.expect) is dict: 221 | for k, v in self.expect.items(): 222 | if type(v) is str: 223 | # assert v in actual[k] 224 | assert actual[k][:len(v)] == v 225 | else: 226 | assert actual[k] == v 227 | elif type(self.expect) is list: 228 | if len(self.expect) == 0: 229 | assert len(actual) == 0 230 | for i, item in enumerate(self.expect): 231 | if type(item) is dict: 232 | for k, v in item.items(): 233 | assert actual[i][k] == v 234 | else: 235 | assert actual[i] == item 236 | 237 | 238 | @dataclass 239 | class TestResult: 240 | scenario: str 241 | possible_credit: int 242 | actual_credit: int 243 | stats: Literal['PASSED', 'FAILED', 'ERROR', 'PARTIALLY PASSED'] 244 | message: str | None 245 | 246 | def __str__(self): 247 | return f'[{self.stats}] {self.scenario}:\n\tCredit: {self.actual_credit}/{self.possible_credit}\n\tMessage: {self.message}' 248 | 249 | 250 | if __name__ == '__main__': 251 | parser = ArgumentParser() 252 | parser.add_argument('--output', '-o', type=str) 253 | parser.add_argument('--fixtures', '-f', type=str, default=None) 254 | args = parser.parse_args() 255 | FILE = open(args.output, 'a+') if args.output else None 256 | 257 | fixtures_to_run = args.fixtures.split(',') if args.fixtures else None 258 | 259 | res = [] 260 | for filename in sorted(os.listdir('fixtures')): 261 | if not fixtures_to_run or filename.rstrip('.yml') in fixtures_to_run: 262 | with TestScenario(os.path.join('fixtures', filename)) as scenario: 263 | res.append(scenario.run()) 264 | 265 | cnt = Counter(r.stats for r in res) 266 | total_credit = sum(r.possible_credit for r in res) 267 | actual_credit = sum(r.actual_credit for r in res) 268 | 269 | print('\n\n') 270 | 271 | sys.argv[1:] = ["--name", "gmail.com"] 272 | from server import student_id 273 | 274 | print('***** TEST SUMMARY *****', file=FILE) 275 | print(f'StudentID: {student_id()}', file=FILE) 276 | print(f'Score: {actual_credit}/{total_credit}', file=FILE) 277 | print('\t'.join(f'{k}: {v}' for k, v in cnt.items()), file=FILE) 278 | 279 | print('\n***** TEST DETAILS *****', file=FILE) 280 | for r in res: 281 | print(r, file=FILE) 282 | 283 | if FILE: 284 | print('***** TEST SUMMARY *****') 285 | print(f'StudentID: {student_id()}') 286 | print(f'Score: {actual_credit}/{total_credit}') 287 | print('\t'.join(f'{k}: {v}' for k, v in cnt.items())) 288 | 289 | print('\n***** TEST DETAILS *****') 290 | for r in res: 291 | print(r) 292 | 293 | with suppress(Exception): 294 | if FILE: 295 | FILE.close() 296 | --------------------------------------------------------------------------------