Skip to content

Commit b416953

Browse files
committed
added barely functioning pop3 and smtp servers for debugging and sending/receiving emails.
1 parent 4f35eda commit b416953

12 files changed

+461
-21
lines changed

Makefile

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
build_linux: clean
2-
docker run -v "$(pwd):/src/" cdrx/pyinstaller-linux "pyinstaller --clean --onedir --add-data ops:ops --add-data /home/shyft/stuff/miniconda3/lib/python3.9/site-packages/pyppeteer-0.2.5.dist-info:pyppeteer-0.2.5.dist-info casper_agent.py"
2+
docker run -v "$(pwd):/src/" cdrx/pyinstaller-linux "pyinstaller --clean --onedir --add-data "ops;ops" --add-data /home/shyft/stuff/miniconda3/lib/python3.9/site-packages/pyppeteer-0.2.5.dist-info:pyppeteer-0.2.5.dist-info casper_agent.py"
33

44
cd dist
55
tar cvzf casper_agent_linux.tar.gz linux/
66
cd ..
77

88
build_windows: clean
99
# https://github.com/cdrx/docker-pyinstaller
10-
docker run -v "$(pwd):/src/" cdrx/pyinstaller-windows 'pyinstaller -y --onedir --add-data "ops;ops" --add-data "C:/python37/Lib/site-packages/pyppeteer-0.2.5.dist-info;pyppeteer-0.2.5.dist-info" casper_agent.py'
11-
10+
# docker run -v "$(pwd):/src/" cdrx/pyinstaller-windows 'pyinstaller -y --onedir --add-data "ops;ops" --add-data "C:\python37\Lib\site-packages\pyppeteer-0.2.5.dist-info;pyppeteer-0.2.5.dist-info" --add-data "config.toml;." casper_agent.py'
11+
12+
13+
pyinstaller -y --onedir --add-data "ops;ops" --add-data "C:\Users\IEUser\miniconda3\Lib\site-packages\pyppeteer-0.2.5.dist-info;pyppeteer-0.2.5.dist-info" --add-data "config.toml;." casper_agent.py
1214
cd dist
1315
zip -r casper_agent_windows.zip windows/
1416
cd ..

__init__.py

Whitespace-only changes.

casper_agent.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import json
2626
import socket
2727
import smtplib
28+
import poplib
2829
import ssl
2930
import sys
3031
from typing import List
@@ -66,7 +67,7 @@
6667

6768
@app.callback()
6869
def load_config(
69-
config_file: Path = "config.toml",
70+
config_file: Path = Path("config.toml"),
7071
debug: bool = True,
7172
):
7273
global AGENT_UUID
@@ -330,12 +331,12 @@ def _send_email(**kwargs):
330331
if kwargs.get("encrypted", False):
331332
context = ssl.create_default_context()
332333

333-
with smtplib.SMTP(kwargs.get("smtp_server"), kwargs.get("smtp_port")) as s:
334+
with smtplib.SMTP(kwargs.get("smtp_server",'localhost'), kwargs.get("smtp_port",1025)) as s:
334335

335336
# s.login(kwargs.get('smtp_user'), kwargs.get('smtp_pass'))
336337

337338
s.sendmail(
338-
kwargs.get("sender"), kwargs.get("recipient_list"), kwargs.get("email_body")
339+
kwargs.get("sender",''), kwargs.get("recipient_list",[]), kwargs.get("email_body",'')
339340
)
340341

341342

@@ -444,7 +445,10 @@ def send_email(
444445
except BaseException as e:
445446
print(e)
446447
if smtp_server == 'localhost' and smtp_port == 1025:
447-
print('It looks like you are running the default smtp server settings. \ntry running "python smtp_debugging_server.py" or "python -m smtpd -c DebuggingServer -n localhost:1025"')
448+
print('''It looks like you are running the default smtp server settings.
449+
try running "cd email_server && python smtp_debugging_server.py" or "python -m smtpd -c DebuggingServer -n localhost:1025" both operate on 1025 for MTA features.
450+
You'll probably want to run the POP3 server pop3_debugging_server.py in the same dir. operates on port 1110 by default for MDA features
451+
''')
448452
return False
449453

450454

@@ -548,12 +552,12 @@ def wander(
548552
return False
549553

550554

551-
def load_ops(ops_dir=OPS_DIR) -> list:
555+
def load_ops(ops_dir=OPS_DIR, op_filter="*") -> list:
552556
print(f"loading operations from {ops_dir}")
553557

554558
ops = []
555559

556-
op_files = glob.glob(f"{ops_dir}/*.toml")
560+
op_files = glob.glob(f"{ops_dir}/{op_filter}.toml")
557561

558562
for op_file in op_files:
559563
with open(op_file) as f:
@@ -569,15 +573,17 @@ def load_ops(ops_dir=OPS_DIR) -> list:
569573
def operate(
570574
ops=[],
571575
no_ops: bool = False,
576+
op_filter="*",
572577
new_ops=False,
573-
ops_dir: Path = "./ops",
578+
ops_dir: Path = Path("./ops"),
574579
beacon_only: bool = False,
575580
tags: List[str] = [],
581+
576582
):
577583
"""actually fire off the operations defined in the ops.toml files"""
578584

579585
if ops == [] and new_ops == False:
580-
ops = load_ops(ops_dir=ops_dir)
586+
ops = load_ops(ops_dir=ops_dir, op_filter=op_filter)
581587
if no_ops == True:
582588
ops = []
583589

email_server/email_db.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from datetime import datetime
2+
from pony.orm import *
3+
4+
5+
db = Database()
6+
db.bind(provider='sqlite', filename='mail.sqlite', create_db=True)
7+
8+
9+
class Account(db.Entity):
10+
id = PrimaryKey(int, auto=True)
11+
username = Optional(str)
12+
password = Optional(str)
13+
domain = Optional(str)
14+
messages = Set('Message')
15+
16+
17+
class Message(db.Entity):
18+
id = PrimaryKey(int, auto=True)
19+
timestamp = Optional(datetime, default=lambda: datetime.now())
20+
msg_from = Optional(str)
21+
msg_to = Optional(str)
22+
msg_subj = Optional(str)
23+
msg_body = Optional(str)
24+
account = Required(Account)
25+
26+
27+
db.generate_mapping(create_tables=True)
28+
with db_session:
29+
if Account.get(username='shyft') == None:
30+
shyft = Account(username='shyft', password='pass')
31+
test = Account(username='test', password='pass')
32+
msg1 = Message(msg_to="shyft@mail.mil", msg_body='msg 1 3easdfasdf', account=shyft)
33+
msg2 = Message(msg_to="shyft@mail.com",msg_body='msg 2 asldfj9oiwjef', account=shyft)
34+
msg3 = Message(msg_to="test@mail.mil", msg_body='not for shyft asldfj9oiwjef', account=test)
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[[users]]
2+
username = "shyft"
3+
password = 'pass'
4+
5+
[[users]]
6+
username = 'test'
7+
password = 'test'

email_server/pop3_debugging_server.py

+247
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
#!/usr/bin/env python3
2+
3+
# https://www.vircom.com/blog/quick-guide-of-pop3-command-line-to-type-in-telnet/
4+
# https://electrictoolbox.com/pop3-commands/ for how pop is supposed to work...
5+
from pony.orm.core import db_decorator, get
6+
from rich import print
7+
import sys
8+
import socketserver
9+
# import glob
10+
# import toml
11+
12+
import os
13+
14+
import diskcache as dc
15+
16+
import email_db as db
17+
18+
19+
# EMAILS = dict()
20+
21+
# USERS = toml.load("email_user_accounts.toml")
22+
23+
# USERS = {
24+
# "shyft": "pass",
25+
# "test": "test"
26+
# }
27+
28+
# USERS = dc.Cache("maildb")
29+
30+
31+
# mail_store_path = './mail_store'
32+
# os.mkdir(mail_store_path)
33+
34+
# USERS.set('shyft', {
35+
# 'password': 'pass',
36+
# 'messages': [
37+
# 'json data here',
38+
# 'another json message'
39+
# ]
40+
# }
41+
# )
42+
43+
# keys are users
44+
# vals are lists of messages
45+
46+
@db.db_session
47+
def get_user(username):
48+
acct = db.Account.get(username=username)
49+
if acct != None:
50+
return acct
51+
return False
52+
53+
@db.db_session
54+
def auth_user(username, password):
55+
res= db.Account.get(username=username, password=password)
56+
if res != None:
57+
return True
58+
return False
59+
60+
@db.db_session
61+
def format_msg(m_id):
62+
m = db.Message.get(id=m_id)
63+
if m != None:
64+
return f'''
65+
msg id: {m.id}
66+
msg timestamp: {m.timestamp}
67+
msg from: {m.msg_from}
68+
msg to: {m.msg_to}
69+
msg subject: {m.msg_subj}
70+
msg body:
71+
{m.msg_body}
72+
'''
73+
return f'invalid id {m_id}'
74+
75+
@db.db_session
76+
def stat_msg(username):
77+
ret = []
78+
messages = db.select(m for m in db.Message if username in m.msg_to)
79+
for m in messages:
80+
ret.append((m.id, len(m.msg_body)))
81+
# return ''.join(ret)
82+
return ret
83+
84+
@db.db_session
85+
def summary_msg(m_id):
86+
m = db.Message.get(id=m_id)
87+
return f'\n---\n{m.id}|{m.timestamp}|{m.msg_from}|{m.msg_to}|{m.msg_subj}|{m.msg_body}'
88+
# return f'{m.id} {len(m.msg_body)}'
89+
90+
@db.db_session
91+
def get_messages(username):
92+
ret = []
93+
messages = db.select(m for m in db.Message if username in m.msg_to)
94+
for m in messages:
95+
ret.append(summary_msg(m.id))
96+
# return ''.join(ret)
97+
return ret
98+
# message_files = glob.glob(f'{mail_store_path}/{username}/*.mail')
99+
100+
# messages = []
101+
# for file in message_files:
102+
# with open(file) as f:
103+
# messages.append(f.read())
104+
105+
106+
class MailHandler(socketserver.BaseRequestHandler):
107+
"""
108+
The request handler class for our server.
109+
110+
It is instantiated once per connection to the server, and must
111+
override the handle() method to implement communication to the
112+
client.
113+
"""
114+
115+
def handle(self):
116+
# self.request is the TCP socket connected to the client
117+
self.request.sendall(f"+OK POP3 maily server v13.37 ready\n\n".encode())
118+
self.user = b""
119+
self.logged_in = False
120+
self.messages = list()
121+
122+
while True:
123+
try:
124+
raw = self.request.recv(1024).decode()
125+
126+
self.data = raw.strip().split(" ")
127+
128+
# breakpoint()
129+
# self.data = self.data.decode()
130+
print(f"{self.client_address[0]} sent: {self.data}")
131+
132+
cmd = self.data[0]
133+
payload = "".join(self.data[1:])
134+
# print(f"payload : {payload}")
135+
136+
if cmd.lower() == "user":
137+
self.logged_in == False # remove this line to be a ctf challenge. read other user emails...
138+
if get_user(payload):
139+
self.user = payload
140+
self.request.sendall(
141+
b"+OK Username accepted, password please\n"
142+
)
143+
else:
144+
self.request.sendall(b"-ERR invalid username\n")
145+
146+
elif cmd.lower() == "pass":
147+
print(f'login attempt for user {self.user} with password {payload}')
148+
if auth_user(self.user, payload):
149+
self.logged_in = True
150+
msg = b"+OK password accepted\n"
151+
print(f"user {self.user} {msg}")
152+
get_messages(self.user)
153+
self.request.sendall(msg)
154+
155+
elif cmd.lower() == "stat":
156+
msgs = stat_msg(self.user)
157+
self.messages = msgs
158+
status = f"logged in:{self.logged_in}\nuser: {self.user}\n"
159+
print(status)
160+
ret_msg = f"+OK {len(msgs)} {sum([len(m) for m in msgs])}\n".encode()
161+
# ret_msg = "+OK 1 1337\n".encode()
162+
print(ret_msg)
163+
self.request.sendall(ret_msg)
164+
165+
elif cmd.lower() == "list":
166+
if self.logged_in == True:
167+
self.messages = ''.join(get_messages(self.user))
168+
169+
resp = f"+OK {len(self.messages)} messages\n"
170+
for msg in stat_msg(self.user):
171+
resp += f"{msg[0]} {msg[1]}\n"
172+
# resp += '\n'.join(stat_msg(self.user))
173+
resp += ".\n"
174+
self.request.sendall(resp.encode())
175+
else:
176+
self.request.sendall(b"-ERR Must be logged in..\n")
177+
178+
elif cmd.lower() == "retr":
179+
if self.logged_in == True:
180+
payload = int(payload)
181+
msg = ''
182+
with db.db_session:
183+
msg = db.Message.get(id=payload)
184+
if msg != None:
185+
msg = format_msg(msg.id)
186+
if payload >= 0 and payload <= len(self.messages) and msg != None:
187+
188+
self.request.sendall(
189+
f"+OK\n---BEGIN_MSG---\n{msg}\n---END_MSG---\n.\n".encode()
190+
)
191+
else:
192+
pass # invalid index
193+
else:
194+
self.request.sendall(b"-ERR Must be logged in..\n")
195+
196+
elif cmd.lower() == "top":
197+
if self.logged_in == True:
198+
if int(payload) >= 0 and int(payload) <= len(self.messages):
199+
msg = self.messages[int(payload) - 1]
200+
self.request.sendall(
201+
f"+OK\n---BEGIN_MSG---\n{msg[:100]}\n---END_MSG---\n.\n".encode()
202+
)
203+
else:
204+
pass # invalid index
205+
else:
206+
self.request.sendall(b"-ERR Must be logged in..\n")
207+
208+
elif cmd.lower() == "dele":
209+
if self.logged_in == True:
210+
m_id = int(payload)
211+
with db.db_session:
212+
msg = db.Message.get(id=m_id)
213+
if msg != None:
214+
# msg = self.messages.pop(int(payload) - 1)
215+
msg_str = format_msg(m_id)
216+
self.request.sendall(f"+OK Deleted msg {m_id}\n---BEGIN_MSG---\n{msg_str}\n---END_MSG---\n.\n".encode())
217+
msg.delete()
218+
print(f'deleted msg id {m_id}')
219+
else:
220+
self.request.sendall(b"-ERR Must be logged in..\n")
221+
elif cmd.lower() == "quit":
222+
self.request.sendall(b"+OK BYE\n")
223+
break
224+
else:
225+
self.request.sendall(b"-ERR Unknown command... try again\n")
226+
227+
except BrokenPipeError as e:
228+
return
229+
except BaseException as e:
230+
print(e)
231+
232+
233+
if __name__ == "__main__":
234+
HOST, PORT = "localhost", 1110
235+
236+
# Create the server, binding to localhost on port 9999
237+
with socketserver.TCPServer((HOST, PORT), MailHandler) as server:
238+
# Activate the server; this will keep running until you
239+
# interrupt the program with Ctrl-C
240+
print("Running on", HOST, PORT)
241+
try:
242+
server.allow_reuse_address = True
243+
server.serve_forever()
244+
except KeyboardInterrupt as e:
245+
print("quitting... ")
246+
server.server_close()
247+
sys.exit(0)

0 commit comments

Comments
 (0)