diff --git a/mailoney/core.py b/mailoney/core.py index 3e62ccb..9680128 100644 --- a/mailoney/core.py +++ b/mailoney/core.py @@ -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, @@ -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: @@ -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: """ diff --git a/mailoney/db.py b/mailoney/db.py index 2e286a1..824354e 100644 --- a/mailoney/db.py +++ b/mailoney/db.py @@ -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""" @@ -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 """ @@ -136,11 +147,13 @@ 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, @@ -148,7 +161,9 @@ def create_session(ip_address: str, port: int, server_name: str) -> SMTPSession: 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 diff --git a/mailoney/migrations/script.py.mako b/mailoney/migrations/script.py.mako new file mode 100644 index 0000000..fbc4b07 --- /dev/null +++ b/mailoney/migrations/script.py.mako @@ -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"} diff --git a/mailoney/migrations/versions/b519325d1f2b_add_destination_ip_and_port_to_sessions.py b/mailoney/migrations/versions/b519325d1f2b_add_destination_ip_and_port_to_sessions.py new file mode 100644 index 0000000..e1792d5 --- /dev/null +++ b/mailoney/migrations/versions/b519325d1f2b_add_destination_ip_and_port_to_sessions.py @@ -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 ### diff --git a/tests/test_db.py b/tests/test_db.py index 551cd85..bd03005 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -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() @@ -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