Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions mailoney/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ def _accept_connections(self) -> None:
while True:
try:
client, addr = self.socket.accept()
logger.info(f"Connection from {addr[0]}:{addr[1]}")
print(f"[*] Connection from {addr[0]}:{addr[1]}")
logger.info(f"Connection from {addr[0]}:{addr[1]} to {self.bind_ip}:{self.bind_port}")
print(f"[*] Connection from {addr[0]}:{addr[1]} to {self.bind_ip}:{self.bind_port}")

client_handler = threading.Thread(
target=self._handle_client,
Expand All @@ -94,7 +94,11 @@ def _handle_client(self, client_socket: socket.socket, addr: Tuple[str, int]) ->
client_socket: Client socket
addr: Client address tuple (ip, port)
"""
session_record = create_session(addr[0], addr[1], self.server_name)
session_record = create_session(
addr[0], addr[1], self.server_name,
dest_ip=self.bind_ip,
dest_port=self.bind_port
)
session_log = []

try:
Expand Down Expand Up @@ -165,7 +169,7 @@ def _handle_client(self, client_socket: socket.socket, addr: Tuple[str, int]) ->
logger.error(f"Error in client handler: {e}")
finally:
client_socket.close()
logger.info(f"Connection closed for {addr[0]}:{addr[1]}")
logger.info(f"Connection closed for {addr[0]}:{addr[1]} (dest: {self.bind_ip}:{self.bind_port})")

def stop(self) -> None:
"""
Expand Down
31 changes: 23 additions & 8 deletions mailoney/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class SMTPSession(Base):
port = Column(Integer, nullable=False)
server_name = Column(String(255))
session_data = Column(Text)
# Destination (server) IP and port - the honeypot's listening address
dest_ip = Column(String(255), nullable=True)
dest_port = Column(Integer, nullable=True)

class Credential(Base):
"""Model for captured credentials"""
Expand Down Expand Up @@ -106,15 +109,23 @@ def init_db(db_url: Optional[str] = None) -> None:
except Exception as e:
logger.warning(f"Error applying migrations: {e}")

def create_session(ip_address: str, port: int, server_name: str) -> SMTPSession:
def create_session(
ip_address: str,
port: int,
server_name: str,
dest_ip: Optional[str] = None,
dest_port: Optional[int] = None
) -> SMTPSession:
"""
Create a new session record in the database.

Args:
ip_address: Client IP address
port: Client port
ip_address: Client IP address (source)
port: Client port (source)
server_name: Server name used

dest_ip: Server's bound IP address (destination)
dest_port: Server's bound port (destination)

Returns:
The created SMTPSession instance
"""
Expand All @@ -136,19 +147,23 @@ def create_session(ip_address: str, port: int, server_name: str) -> SMTPSession:
smtp_session = SMTPSession(
ip_address=ip_address,
port=port,
server_name=server_name
server_name=server_name,
dest_ip=dest_ip,
dest_port=dest_port
)
db_session.add(smtp_session)
db_session.commit()

# This is key: Make a copy of all the attributes we need before closing session
session_copy = SMTPSession(
id=smtp_session.id,
timestamp=smtp_session.timestamp,
ip_address=smtp_session.ip_address,
port=smtp_session.port,
server_name=smtp_session.server_name,
session_data=smtp_session.session_data
session_data=smtp_session.session_data,
dest_ip=smtp_session.dest_ip,
dest_port=smtp_session.dest_port
)

return session_copy
Expand Down
26 changes: 26 additions & 0 deletions mailoney/migrations/script.py.mako
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
${imports if imports else ""}

# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}


def upgrade() -> None:
${upgrades if upgrades else "pass"}


def downgrade() -> None:
${downgrades if downgrades else "pass"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Add destination IP and port to sessions

Revision ID: b519325d1f2b
Revises: 1a5b9822e49c
Create Date: 2025-12-29 21:11:45.297350

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = 'b519325d1f2b'
down_revision: Union[str, None] = '1a5b9822e49c'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('smtp_sessions', sa.Column('dest_ip', sa.String(length=255), nullable=True))
op.add_column('smtp_sessions', sa.Column('dest_port', sa.Integer(), nullable=True))
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('smtp_sessions', 'dest_port')
op.drop_column('smtp_sessions', 'dest_ip')
# ### end Alembic commands ###
36 changes: 35 additions & 1 deletion tests/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def test_create_session(test_db):
assert session.ip_address == "192.168.1.1"
assert session.port == 12345
assert session.server_name == "test.example.com"

# Verify the session was created in the database
from mailoney.db import Session, SMTPSession
db_session = Session()
Expand All @@ -49,6 +49,40 @@ def test_create_session(test_db):
finally:
db_session.close()


def test_create_session_with_destination(test_db):
"""Test creating a session record with destination IP and port"""
session = create_session(
"192.168.1.100", 54321, "mail.test.com",
dest_ip="10.0.0.1",
dest_port=25
)
assert session.id is not None
assert session.ip_address == "192.168.1.100"
assert session.port == 54321
assert session.server_name == "mail.test.com"
assert session.dest_ip == "10.0.0.1"
assert session.dest_port == 25

# Verify the session was created in the database with destination info
from mailoney.db import Session, SMTPSession
db_session = Session()
try:
stmt = sa.select(SMTPSession).filter_by(id=session.id)
db_result = db_session.execute(stmt).scalar_one_or_none()
assert db_result is not None
assert db_result.dest_ip == "10.0.0.1"
assert db_result.dest_port == 25
finally:
db_session.close()


def test_create_session_without_destination(test_db):
"""Test that sessions without destination info have None values"""
session = create_session("192.168.1.50", 9999, "legacy.test.com")
assert session.dest_ip is None
assert session.dest_port is None

def test_update_session_data(test_db):
"""Test updating session data"""
# Create a new session
Expand Down