Repository: fkie-cad/socbed

Issue:
Update IPFire from 2.25 to 2.29 and solve unbound issue
Dear Author:

I have found that you haven't the time to update the IPFire from 2.25 to 2.29. I have done this thing. Below is the boot command for internet router and company router:

iso_urls and iso_checksum:
```shell
      "iso_checksum": "sha256:ae9c1f9639e30e9e88b7363426d69297300cf15543498bef0b18e27f3dd66222",
      "iso_urls": [
        "https://downloads.ipfire.org/releases/ipfire-2.x/2.29-core189/ipfire-2.29-core189-x86_64.iso"
      ],
```

boot command for internet router:
```shell
"boot_command": [
        "<enter><wait10s>",
        "<tab><enter>",
        "<wait><enter>",
        "<wait><enter>",
        "<wait><tab><enter>",
        "<wait60s><enter>",
        "<wait30s><enter><wait><enter>",
        "<wait><spacebar>",
        "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>internetrouter<tab><spacebar>",
        "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>localdomain<tab><spacebar>",
        "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>breach<tab>breach<tab><spacebar>",
        "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>breach<tab>breach<tab><spacebar>",       
	      "<wait10s><tab><spacebar>",
        "<wait><down><down><tab><spacebar>",
        "<wait><down><tab><spacebar>",
        "<wait><tab><spacebar>",
        "<wait><down><tab><spacebar>",
        "<wait><down><tab><spacebar>",
        "<wait><tab><spacebar>",
        "<wait><down><tab><spacebar>",
        "<wait><tab><spacebar>",
        "<wait><tab><tab><tab><spacebar>",
        "<wait><down><tab><spacebar>",
        "<wait><tab><spacebar>",
        "<wait><spacebar>",
        "<wait>172.18.0.1<tab>",
        "<wait><left><left><bs><bs><bs>0<tab><spacebar>",
        "<wait><down><tab><spacebar>",
        "<wait>192.168.56.30<tab>",
        "<wait><tab><spacebar>",
        "<wait><down><tab><spacebar>",
        "<wait><tab><spacebar><tab><tab><tab><tab><spacebar>",
        "<wait><tab><tab><spacebar>",
        "<wait><tab><tab><spacebar>",
        "<wait><tab><spacebar>",
        "<wait><spacebar>",    
        "<wait60s>",
        "root<wait><enter>breach<wait><enter>",
        "<wait20s>iptables -I INPUT -p tcp --dport 444 -j ACCEPT<enter>",
        "<wait>iptables -I INPUT -p tcp --dport 222 -j ACCEPT<enter>",
        "<wait>sed -i 's/ENABLE_SSH=off/ENABLE_SSH=on/g' /var/ipfire/remote/settings<enter>",
        "<wait>sed -i 's/ENABLE_SSH_PORTFW=off/ENABLE_SSH_PORTFW=on/g' /var/ipfire/remote/settings<enter>",
        "<wait>sed -i 's/ENABLE_SSH_KEYS=off/ENABLE_SSH_KEYS=on/g' /var/ipfire/remote/settings<enter>",
        "<wait>touch /var/ipfire/remote/enablessh<enter>",
        "<wait>chown nobody:nobody /var/ipfire/remote/enablessh<enter>",
        "<wait>sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config<enter>",
        "<wait>sed -i 's/Port 22/Port 222/g' /etc/ssh/sshd_config<enter>",
        "<wait>/etc/rc.d/init.d/sshd restart<enter>",
        "<wait10s>/etc/init.d/sshd restart<enter>",
        "<wait10s>"
      ],
```

boot command for company router:
```shell
      "boot_command": [
        "<enter><wait10s>",
        "<tab><enter>",
        "<wait><enter>",
        "<wait><enter>",
        "<wait><tab><enter>",
        "<wait60s><enter>",
        "<wait30s><enter><wait><enter>",
        "<wait><spacebar>",
        "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>companyrouter<tab><spacebar>",
        "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>localdomain<tab><spacebar>",
        "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>breach<tab>breach<tab><spacebar>",
        "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>breach<tab>breach<tab><spacebar>",
        "<wait10s><tab><spacebar>",
        "<wait><down><down><down><tab><spacebar>",
        "<wait><down><tab><spacebar>",
        "<wait><tab><spacebar>",
        "<wait><tab><spacebar>",
        "<wait><down><down><tab><spacebar>",
        "<wait><tab><spacebar>",
        "<wait><up><tab><spacebar>",
        "<wait><tab><spacebar>",
        "<wait><down><down><tab><spacebar>",
        "<wait><tab><spacebar>",
        "<wait><tab><tab><tab><spacebar>",
        "<wait><down><tab><spacebar>",
        "<wait><tab><spacebar>",
        "<wait><spacebar>",
        "<wait>172.16.0.1<tab>",
        "<wait><left><left><bs><bs><bs>0<tab><spacebar>",
        "<wait><down><tab><spacebar>",
        "<wait>192.168.56.10<tab>",
        "<wait><tab><spacebar>",
        "<wait><down><tab><spacebar>",
        "<wait>172.17.0.1<tab>",
        "<wait><left><left><bs><bs><bs>0<tab><spacebar>",
        "<wait><down><tab><spacebar>",
        "<wait><tab><tab><tab>172.18.0.2<tab>",
        "<wait><left><left><bs><bs><bs>0<tab>",
        "<wait>172.18.0.1<tab><spacebar>",
        "<wait><tab><tab><spacebar>",
        "<wait><tab><tab><spacebar>",
        "<wait><spacebar>",
        "<wait><tab>172.16.1.1<tab>",
        "<wait>172.16.255.254<tab><tab><tab><tab><tab>",
        "<wait><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>breach.local<tab><spacebar>",
        "<wait><spacebar>",
        "<wait60s>",
        "root<wait><enter>breach<wait><enter>",
        "<wait20s>iptables -I INPUT -p tcp --dport 444 -j ACCEPT<enter>",
        "<wait>iptables -I INPUT -p tcp --dport 222 -j ACCEPT<enter>",
        "<wait>echo 'ENABLE_SSH_PORTFW=on' > /var/ipfire/remote/settings<enter>",
        "<wait>echo 'ENABLE_SSH=on' >> /var/ipfire/remote/settings<enter>",
        "<wait>echo 'ENABLE_SSH_PASSWORDS=on' >> /var/ipfire/remote/settings<enter>",
        "<wait>echo 'ENABLE_SSH_KEYS=on' >> /var/ipfire/remote/settings<enter>",
        "<wait>echo 'SSH_PORT=off' >> /var/ipfire/remote/settings<enter>",
        "<wait>touch /var/ipfire/remote/enablessh<enter>",
        "<wait>chown nobody:nobody /var/ipfire/remote/enablessh<enter>",
        "<wait>sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config<enter>",
        "<wait>sed -i 's/AllowTcpForwarding no/AllowTcpForwarding yes/g' /etc/ssh/sshd_config<enter>",
        "<wait>sed -i 's/Port 22/Port 222/g' /etc/ssh/sshd_config<enter>",
        "<wait>/etc/rc.d/init.d/sshd restart<enter>",
        "<wait10s> /etc/init.d/sshd restart<enter>",
        "<wait10s>"
      ],
```

after update IPFire, I think add manual dns server is recommend for stable dns lookup, so I add two dns server to internet router using ansible role named post_unbound (directory is ansible/roles/post_unbound/tasks/main.yml) as below:

```shell
---

- name: Add 8.8.8.8 to the DNS servers file
  lineinfile:
    path: /var/ipfire/dns/servers
    line: "3,8.8.8.8,,enabled,"
    create: yes  
    state: present 

- name: Add 114.114.114.114 to the DNS servers file
  lineinfile:
    path: /var/ipfire/dns/servers
    line: "4,114.114.114.114,,enabled,"
    create: yes 
    state: present 

- name: Restart unbound service
  service:
    name: unbound
    state: restarted 

```

and then add this role to internet router as below:
```shell
---
- hosts: internetrouter
  become: yes
  gather_facts: true

  roles:
    - configure_internet_router
    - rsyslog_install_ipfire
    - rsyslog_ISO8601
    - post_unbound
```

then unbound service in internet router can running stably!

Hints from issue comments:
@Maspital: Hi, thanks for your issue.
There are currently some fixes waiting to be merged in #94 , we'll test and implement your suggestions after these have been moved into main.@ru37z: Thanks again for reporting this issue!
- The unbound DNS problems have been fixed in v1.3.7.
- As for the IPFire version update, we currently do not deem it necessary. In case you see any advantages, feel free to create a pull request including a list of the potential benefits of the update.

Solution patch:
diff --git a/README.md b/README.md
index 7266ecc..af7cb5a 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ The numbers above are valid for small simulations with 1-3 clients.
 
 ## Installation
 
-The installation instructions below were tested on a fresh Ubuntu 20.04 LTS system.
+The installation instructions below were tested on a fresh Ubuntu 24.04 LTS system.
 Please adhere strictly to the instructions as different software versions might not work as expected.
 
 ```sh
diff --git a/provisioning/ansible/roles/external_mail_handler/files/external_mail_handler.py b/provisioning/ansible/roles/external_mail_handler/files/external_mail_handler.py
index 3137f44..ea44769 100644
--- a/provisioning/ansible/roles/external_mail_handler/files/external_mail_handler.py
+++ b/provisioning/ansible/roles/external_mail_handler/files/external_mail_handler.py
@@ -33,7 +33,7 @@ def setup_logging():
     logging.Formatter.converter = time.localtime
     logging.Formatter.default_time_format = "%Y-%m-%dT%H:%M:%S"
     is_dst = time.daylight and time.localtime().tm_isdst > 0
-    gmt_offset_secs = - (time.altzone if is_dst else time.timezone)
+    gmt_offset_secs = -(time.altzone if is_dst else time.timezone)
     gmt_offset_string = "{0:+03d}:00".format(gmt_offset_secs // 3600)
     log_handler = WatchedFileHandler(filename="/var/log/breach/external_mail_handler.log")
     logging.basicConfig(
@@ -53,13 +53,13 @@ class CustomHandler:
     async def handle_DATA(self, server, session, envelope):
         # peer, mail_from, mail_to, rcpt_tos and data are now all encapsulated in 'envelope'
         # keep in mind that envelope.data contains raw bytes which first have to be decoded
-        mail = mime_string_to_text_mail(envelope.data.decode("utf-8"))
+        mail = mime_string_to_text_mail(envelope.content.decode("utf-8"))
         logger.info("Received mail from " + str(mail.sender) + " addressed to " + str(mail.receiver))
         self.swap_sender_receiver(mail)
         self.modify_text(mail)
         self.send_mail(mail)
         # A return message is mandatory
-        return '250 OK'
+        return "250 OK"
 
     @staticmethod
     def swap_sender_receiver(mail):
@@ -84,19 +84,17 @@ class Responder:
     def __init__(self):
         self.smtp_out = Server("172.18.0.2", 25)
         self.smtp_in = Server("0.0.0.0", 25)
-        self.controller: Controller|None = None
+        self.controller: Controller | None = None
 
     def run(self):
-        logger.info("Starting Mail Responder listening at " +
-                    str(self.smtp_in.server_ip) +
-                    ":" + str(self.smtp_in.server_port))
-        logger.info("Sending responses to " +
-                    str(self.smtp_out.server_ip) +
-                    ":" + str(self.smtp_out.server_port))
+        logger.info(
+            "Starting Mail Responder listening at " + str(self.smtp_in.server_ip) + ":" + str(self.smtp_in.server_port)
+        )
+        logger.info("Sending responses to " + str(self.smtp_out.server_ip) + ":" + str(self.smtp_out.server_port))
         self.init_controller()
         self.controller.start()  # detaches from current thread
-        input('SMTP server running. Press Return to stop server and exit.')
-        self.controller.stop()
+        while True:
+            time.sleep(1)
 
     def init_controller(self):
         handler = CustomHandler(self.smtp_out)
@@ -146,4 +144,4 @@ class Server:
 if __name__ == "__main__":
     setup_logging()
     responder = Responder()
-    responder.run()
\ No newline at end of file
+    responder.run()
diff --git a/provisioning/ansible/roles/external_mail_handler/tasks/main.yml b/provisioning/ansible/roles/external_mail_handler/tasks/main.yml
index 6e0c251..4fb8577 100644
--- a/provisioning/ansible/roles/external_mail_handler/tasks/main.yml
+++ b/provisioning/ansible/roles/external_mail_handler/tasks/main.yml
@@ -4,6 +4,18 @@
 # Additionally, creates a logrotate config file for logs
 #
 
+- name: Ensure compatible version of binutils
+  apt:
+    name: binutils=2.43.1-5
+    state: present
+    update_cache: yes
+    
+- name: Install aiosmtpd (requirement) globally
+  apt:
+    name: python3-aiosmtpd=1.4.6-1
+    update_cache: yes
+    state: present
+
 - name: "Create script directory {{ script_dir }}"
   file:
     path: "{{ script_dir }}"
diff --git a/provisioning/packer/attacker.json b/provisioning/packer/attacker.json
index 6c1e168..553f209 100644
--- a/provisioning/packer/attacker.json
+++ b/provisioning/packer/attacker.json
@@ -8,7 +8,8 @@
         "initrd=/install.amd/initrd.gz ",
         "auto-install/enable=true ",
         "debconf/priority=critical ",
-        "preseed/url=http://{{user `host_ip_addr`}}:{{.HTTPPort}}/attacker_preseed.cfg ", "<wait5s>",
+        "preseed/url=http://{{user `host_ip_addr`}}:{{.HTTPPort}}/attacker_preseed.cfg ",
+        "<wait5s>",
         "netcfg/choose_interface=enp0s8 ",
         "netcfg/disable_autoconfig=true ",
         "netcfg/get_ipaddress=192.168.56.31 ",
@@ -104,7 +105,7 @@
         "-i",
         "../ansible/hosts"
       ],
-      "pause_before": "5m",
+      "pause_before": "2m",
       "playbook_file": "../ansible/attacker_playbook.yml",
       "type": "ansible"
     }
@@ -117,6 +118,4 @@
     "vm_output": "./exports/attacker",
     "vm_hostonlyif": "{{env `HOSTONLYIF`}}"
   }
-}
-
-
+}
\ No newline at end of file
diff --git a/provisioning/packer/companyrouter.json b/provisioning/packer/companyrouter.json
index 72b2aec..5f168ce 100644
--- a/provisioning/packer/companyrouter.json
+++ b/provisioning/packer/companyrouter.json
@@ -16,7 +16,7 @@
       "guest_os_type": "Linux_64",
       "vm_name": "Company Router",
       "guest_additions_mode": "disable",
-      "headless": "true",  
+      "headless": "true",
       "iso_checksum": "sha256:7793981fbe39cb1fa3cb2f89dd1472751d502823b2fc333449171af1bc225f8f",
       "iso_urls": [
         "https://downloads.ipfire.org/releases/ipfire-2.x/2.25-core141/ipfire-2.25.x86_64-full-core141.iso"
@@ -163,5 +163,4 @@
       "type": "ansible"
     }
   ]
-}
-
+}
\ No newline at end of file
diff --git a/provisioning/packer/http/attacker_preseed.cfg b/provisioning/packer/http/attacker_preseed.cfg
index 1840804..d4cb9e6 100644
--- a/provisioning/packer/http/attacker_preseed.cfg
+++ b/provisioning/packer/http/attacker_preseed.cfg
@@ -455,6 +455,5 @@ d-i preseed/late_command string \
 		in-target sh -c "systemctl start systemd-networkd"; \
 		in-target sh -c "systemctl start systemd-resolved"; \
 		in-target sh -c "systemctl enable ssh.service"; \
-		in-target sh -c "ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf"; \
+		in-target sh -c "echo 'nameserver 172.18.0.1' > /etc/resolv.conf"; \
 		in-target sh -c "echo 'deb http://http.kali.org/kali kali-rolling main non-free contrib' >> /etc/apt/sources.list"
-
diff --git a/provisioning/packer/internetrouter.json b/provisioning/packer/internetrouter.json
index 2d26d27..1632e2b 100644
--- a/provisioning/packer/internetrouter.json
+++ b/provisioning/packer/internetrouter.json
@@ -16,9 +16,9 @@
       "vm_name": "Internet Router",
       "guest_additions_mode": "disable",
       "headless": "true",
-      "iso_checksum": "sha256:7793981fbe39cb1fa3cb2f89dd1472751d502823b2fc333449171af1bc225f8f",
+      "iso_checksum": "sha256:831ee9a6197e0f351fa477b81aab510df6874e7f0e6d16fe1683768b60ff9dc0",
       "iso_urls": [
-        "https://downloads.ipfire.org/releases/ipfire-2.x/2.25-core141/ipfire-2.25.x86_64-full-core141.iso"
+        "https://downloads.ipfire.org/releases/ipfire-2.x/2.25-core157/ipfire-2.25.x86_64-full-core157.iso"
       ],
       "ssh_host": "{{user `ssh_host_addr`}}",
       "ssh_password": "breach",
@@ -58,8 +58,8 @@
         "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>internetrouter<tab><spacebar>",
         "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>localdomain<tab><spacebar>",
         "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>breach<tab>breach<tab><spacebar>",
-        "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>breach<tab>breach<tab><spacebar>",       
-	"<wait10s><tab><spacebar>",
+        "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>breach<tab>breach<tab><spacebar>",
+        "<wait10s><tab><spacebar>",
         "<wait><down><down><tab><spacebar>",
         "<wait><down><tab><spacebar>",
         "<wait><tab><spacebar>",
@@ -82,7 +82,7 @@
         "<wait><tab><tab><spacebar>",
         "<wait><down><tab><tab><spacebar>",
         "<wait><tab><spacebar>",
-        "<wait><spacebar>",    
+        "<wait><spacebar>",
         "<wait60s>",
         "root<wait><enter>breach<wait><enter>",
         "<wait20s>iptables -I INPUT -p tcp --dport 444 -j ACCEPT<enter>",
@@ -96,9 +96,10 @@
         "<wait>sed -i 's/Port 22/Port 222/g' /etc/ssh/sshd_config<enter>",
         "<wait>/etc/rc.d/init.d/sshd restart<enter>",
         "<wait10s>/etc/init.d/sshd restart<enter>",
+        "<wait>echo '3,8.8.8.8,,enabled,' > /var/ipfire/dns/servers<enter>",
+        "<wait10s>/etc/init.d/unbound restart<enter>",
         "<wait10s>"
       ],
-
       "export_opts": [
         "--manifest",
         "--vsys",
@@ -111,9 +112,7 @@
       "output_directory": "{{user `vm_output`}}",
       "keep_registered": "true",
       "skip_export": "true",
-      
       "shutdown_command": "echo 'packer' | shutdown -h -P now",
-
       "vboxmanage_post": [
         [
           "modifyvm",
@@ -145,5 +144,4 @@
       "type": "ansible"
     }
   ]
-}
-
+}
\ No newline at end of file
diff --git a/provisioning/packer/post_install/attacker_setup.sh b/provisioning/packer/post_install/attacker_setup.sh
index 576f28c..3c5f406 100644
--- a/provisioning/packer/post_install/attacker_setup.sh
+++ b/provisioning/packer/post_install/attacker_setup.sh
@@ -1,11 +1,18 @@
 #!/usr/bin/env bash
 
-# Enable root account, set password and reboot
 touch /tmp/runasroot.sh
 
-echo "wget https://archive.kali.org/archive-key.asc -O /etc/apt/trusted.gpg.d/kali-archive-keyring.asc" > /tmp/runasroot.sh
-echo "apt update" >> /tmp/runasroot.sh
-echo "apt install kali-root-login" >> /tmp/runasroot.sh
+# Set up DNS
+# echo "apt-get purge resolvconf -y" >> /tmp/runasroot.sh
+echo "echo 'nameserver 172.18.0.1' > /etc/resolv.conf" > /tmp/runasroot.sh
+
+# Update certificates
+echo "wget http://http.kali.org/kali/pool/main/k/kali-archive-keyring/kali-archive-keyring_2024.1_all.deb -O /tmp/kali-archive-keyring_2024.1_all.deb" >> /tmp/runasroot.sh
+echo "dpkg -i /tmp/kali-archive-keyring_2024.1_all.deb" >> /tmp/runasroot.sh
+echo "apt-get update" >> /tmp/runasroot.sh
+
+# Enable root account, set password and reboot
+echo "apt-get install -y kali-root-login" >> /tmp/runasroot.sh
 echo "echo 'root:breach' | chpasswd" >> /tmp/runasroot.sh
 echo "reboot" >> /tmp/runasroot.sh
 
diff --git a/requirements.txt b/requirements.txt
index 0191641..c3cb119 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,7 @@ ansible==8.7.0
 colorama==0.4.6
 paramiko==3.4.1
 pytest==8.3.3
-pyvmomi==6.7.1.2018.12
+pyvmomi==8.0.3.0.1
 pywinrm==0.4.1
 selenium==4.3.0
 tox==4.18.1
diff --git a/src/vmcontrol/sessionhandler/sessionhandler.py b/src/vmcontrol/sessionhandler/sessionhandler.py
index 7fbcadb..2bc450a 100644
--- a/src/vmcontrol/sessionhandler/sessionhandler.py
+++ b/src/vmcontrol/sessionhandler/sessionhandler.py
@@ -40,7 +40,8 @@ class SessionConfig(DictNamespace):
         "Company Router",
         "Log Server",
         "Internal Server",
-        "DMZ Server"]
+        "DMZ Server",
+    ]
     client_vm = "Client"
     number_of_clones = 3
     vm_start_timeout = 0
@@ -54,14 +55,18 @@ class Clone(DictNamespace):
     user = None
     password = None
     domain = None
+    vrde_port = None
 
 
 class CloneCreator:
-    def __init__(self, father_vm, base_snapshot, number_of_clones, vmm_controller: VMMController):
-        self.father_vm = father_vm
+    def __init__(
+        self, parent_vm, base_snapshot, number_of_clones, vmm_controller: VMMController, vrde_port_start=5000
+    ):
+        self.parent_vm = parent_vm
         self.base_snapshot = base_snapshot
         self.number_of_clones = number_of_clones
         self.vmmc = vmm_controller
+        self.vrde_port_start = vrde_port_start
         self._current_vms = self.vmmc.get_vms()
         self._clones = None
 
@@ -77,9 +82,10 @@ class CloneCreator:
         self.set_management_mac(clone)
         self.set_internal_mac(clone)
         self.set_credentials(clone)
+        self.set_vrde_port(clone)
 
     def set_vm_name(self, clone):
-        clone.vm = self.father_vm + "Clone" + str(clone.id)
+        clone.vm = self.parent_vm + "Clone" + str(clone.id)
         while clone.vm in self._current_vms:
             clone.vm += "a"
 
@@ -94,10 +100,14 @@ class CloneCreator:
         clone.password = "breach"
         clone.domain = "BREACH"
 
+    def set_vrde_port(self, clone):
+        clone.vrde_port = self.vrde_port_start + clone.id - 1
+
     def create_vm(self, clone):
-        self.vmmc.clone(self.father_vm, self.base_snapshot, clone.vm)
+        self.vmmc.clone(self.parent_vm, self.base_snapshot, clone.vm)
         self.vmmc.set_mac(clone.vm, clone.management_mac, if_id=2)
         self.vmmc.set_mac(clone.vm, clone.internal_mac, if_id=1)
+        self.vmmc.set_vrde_port(clone.vm, clone.vrde_port)
 
 
 class SessionHandler:
@@ -192,10 +202,9 @@ class SessionHandler:
 
     def create_clones(self):
         logger.info("Creating clones")
-        father_vm = self.config.client_vm
-        base_snapshot = self.backup_snapshots[father_vm]
-        clone_creator = self.clone_creator_class(
-            father_vm, base_snapshot, self.config.number_of_clones, self.vmmc)
+        parent_vm = self.config.client_vm
+        base_snapshot = self.backup_snapshots[parent_vm]
+        clone_creator = self.clone_creator_class(parent_vm, base_snapshot, self.config.number_of_clones, self.vmmc)
         self.clones = clone_creator.create()
 
     def start_all_vms(self):
@@ -256,8 +265,8 @@ class SessionHandler:
                 time.sleep(timeout)
         if snaps:
             raise SessionHandlerException(
-                "Could not restore and delete all snapshots. Failing: {snaps}"
-                    .format(snaps=snaps))
+                "Could not restore and delete all snapshots. Failing: {snaps}".format(snaps=snaps)
+            )
 
     def poweroff_vms(self, vms):
         fails, max_fails, timeout = 0, 100, 0.01
@@ -272,7 +281,8 @@ class SessionHandler:
                 time.sleep(timeout)
         if running_vms:
             raise SessionHandlerException(
-                "Could not poweroff all machines. Still alive: {vms}".format(vms=running_vms))
+                "Could not poweroff all machines. Still alive: {vms}".format(vms=running_vms)
+            )
 
     def delete_vms(self, vms):
         fails, max_fails, timeout = 0, 100, 0.01
@@ -287,8 +297,7 @@ class SessionHandler:
                 existing_vms.append(vm)
                 time.sleep(timeout)
         if existing_vms:
-            raise SessionHandlerException(
-                "Could not delete all machines. Still there: {vms}".format(vms=existing_vms))
+            raise SessionHandlerException("Could not delete all machines. Still there: {vms}".format(vms=existing_vms))
 
     def take_vm_start_timeout(self):
         time.sleep(self.config.vm_start_timeout)
diff --git a/src/vmcontrol/vmmcontroller/remotevmmcontroller.py b/src/vmcontrol/vmmcontroller/remotevmmcontroller.py
index 0b53c9d..7f0af0e 100644
--- a/src/vmcontrol/vmmcontroller/remotevmmcontroller.py
+++ b/src/vmcontrol/vmmcontroller/remotevmmcontroller.py
@@ -51,9 +51,7 @@ class RemoteVMMController(VMMController):
             elif received_d["type"] == "exception":
                 raise VMMControllerException(received_d["value"])
             else:
-                raise VMMControllerException(
-                    "Unknown Received Message Type: {type}".format(type=received_d["type"])
-                )
+                raise VMMControllerException("Unknown Received Message Type: {type}".format(type=received_d["type"]))
         else:
             raise VMMControllerException("Connection to Daemon lost")
 
@@ -205,11 +203,11 @@ class Messenger:
 
     def _connect_to_remote_socket(self):
         while not self.data_socket:
-            self.data_socket = ssl.wrap_socket(
+            context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
+            context.load_verify_locations(os.path.join(self.cert_dir, "cert.pem"))
+            self.data_socket = context.wrap_socket(
                 socket.socket(socket.AF_INET, socket.SOCK_STREAM),
-                server_side=False,
-                cert_reqs=ssl.CERT_REQUIRED,
-                ca_certs=os.path.join(self.cert_dir, "cert.pem")
+                server_hostname=self.remote_address[0],
             )
             try:
                 logger.debug("Try to connect to {add}".format(add=self.remote_address))
@@ -230,12 +228,12 @@ class Messenger:
 
     def _wait_for_data_socket(self):
         new_sock, _ = self.listening_socket.accept()
-        self.data_socket = ssl.wrap_socket(
-            new_sock,
-            server_side=True,
+        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+        context.load_cert_chain(
             certfile=os.path.join(self.cert_dir, "cert.pem"),
-            keyfile=os.path.join(self.cert_dir, "cert.key")
+            keyfile=os.path.join(self.cert_dir, "cert.key"),
         )
+        self.data_socket = context.wrap_socket(new_sock, server_side=True)
 
     def _close_data_socket(self):
         if self.data_socket:
diff --git a/src/vmcontrol/vmmcontroller/vboxcontroller.py b/src/vmcontrol/vmmcontroller/vboxcontroller.py
index 134c093..604d453 100644
--- a/src/vmcontrol/vmmcontroller/vboxcontroller.py
+++ b/src/vmcontrol/vmmcontroller/vboxcontroller.py
@@ -45,11 +45,7 @@ class VBoxController(VMMController):
 
     def get_macs(self, vm):
         vm_info = self._get_vm_info(vm)
-        macs = [
-            int(value, 16)
-            for key, value in vm_info.items()
-            if key.startswith("macaddress")
-            ]
+        macs = [int(value, 16) for key, value in vm_info.items() if key.startswith("macaddress")]
         return macs
 
     def get_mac(self, vm, if_id=1):
@@ -58,8 +54,7 @@ class VBoxController(VMMController):
             mac_string = vm_info["macaddress" + str(if_id)]
         except KeyError as e:
             raise VMMControllerException(
-                "Cannot find MAC address for interface {if_id} on VM \"{vm}\"".
-                    format(if_id=if_id, vm=vm)
+                'Cannot find MAC address for interface {if_id} on VM "{vm}"'.format(if_id=if_id, vm=vm)
             )
         mac = int(mac_string, 16)
         return mac
@@ -86,10 +81,10 @@ class VBoxController(VMMController):
             out_lines = out.splitlines()
             snapshot_lines = filter(lambda line: "=" in line, out_lines)
             snapshots = [
-                value.strip("\"")
+                value.strip('"')
                 for key, value in map(lambda line: line.split("=", maxsplit=1), snapshot_lines)
-                if key.strip("\"").startswith("SnapshotName")
-                ]
+                if key.strip('"').startswith("SnapshotName")
+            ]
         return snapshots
 
     def create_snapshot(self, vm, snapshot):
@@ -105,18 +100,15 @@ class VBoxController(VMMController):
         self._vboxmanage_execute(vbox_vector)
 
     def clone(self, vm, snapshot, clone):
-        vbox_vector = [
-            "clonevm", vm, "--name", clone,
-            "--options", "link", "--snapshot", snapshot,
-            "--register"
-        ]
+        vbox_vector = ["clonevm", vm, "--name", clone, "--options", "link", "--snapshot", snapshot, "--register"]
         self._vboxmanage_execute(vbox_vector)
 
     def set_credentials(self, vm, user, password, domain):
-        vbox_vector = [
-            "controlvm", vm,
-            "setcredentials", user, password, domain
-        ]
+        vbox_vector = ["controlvm", vm, "setcredentials", user, password, domain]
+        self._vboxmanage_execute(vbox_vector)
+
+    def set_vrde_port(self, vm, port):
+        vbox_vector = ["modifyvm", vm, "--vrdeport", str(port)]
         self._vboxmanage_execute(vbox_vector)
 
     def _get_running_vms(self):
@@ -127,7 +119,7 @@ class VBoxController(VMMController):
 
     def _vm_string_to_list(self, vm_string):
         lines = vm_string.splitlines()
-        vms = [line.split("\"")[1] for line in lines]
+        vms = [line.split('"')[1] for line in lines]
         return vms
 
     def _get_vm_info(self, vm):
@@ -136,10 +128,9 @@ class VBoxController(VMMController):
         out_lines = out.splitlines()
         info_lines = filter(lambda line: "=" in line, out_lines)
         vm_info = {
-            key.strip("\""): value.strip("\"")
-            for key, value in
-            map(lambda line: line.split("=", maxsplit=1), info_lines)
-            }
+            key.strip('"'): value.strip('"')
+            for key, value in map(lambda line: line.split("=", maxsplit=1), info_lines)
+        }
         return vm_info
 
     @staticmethod
@@ -149,11 +140,7 @@ class VBoxController(VMMController):
         out, err = p.communicate()
         if p.returncode != 0:
             raise VMMControllerException(
-                "Error in execution of {vector}\n"
-                "-------\n"
-                "{err}"
-                "-------"
-                    .format(vector=vbox_vector, err=err)
+                "Error in execution of {vector}\n" "-------\n" "{err}" "-------".format(vector=vbox_vector, err=err)
             )
         return out
 
diff --git a/src/vmcontrol/vmmcontroller/vmmcontroller.py b/src/vmcontrol/vmmcontroller/vmmcontroller.py
index a701120..f37523a 100644
--- a/src/vmcontrol/vmmcontroller/vmmcontroller.py
+++ b/src/vmcontrol/vmmcontroller/vmmcontroller.py
@@ -64,6 +64,9 @@ class VMMController:
     def set_credentials(self, vm, user, password, domain):
         raise NotImplementedError()
 
+    def set_vrde_port(self, vm, port):
+        raise NotImplementedError()
+
 
 class VMMControllerException(Exception):
     pass
@@ -71,41 +74,42 @@ class VMMControllerException(Exception):
 
 class LoggingVMMController(VMMController):
     def start(self, vm):
-        logger.debug("Starting \"{vm}\"".format(vm=vm))
+        logger.debug('Starting "{vm}"'.format(vm=vm))
         super().start(vm)
 
     def poweroff(self, vm):
-        logger.debug("Turning off \"{vm}\"".format(vm=vm))
+        logger.debug('Turning off "{vm}"'.format(vm=vm))
         super().poweroff(vm)
 
     def delete(self, vm):
-        logger.debug("Deleting \"{vm}\"".format(vm=vm))
+        logger.debug('Deleting "{vm}"'.format(vm=vm))
         super().delete(vm)
 
     def set_mac(self, vm, mac, if_id=1):
         mac_string = hex(mac)[2:].rjust(12, "0")
-        logger.debug("Setting MAC address of \"{vm}\" interface {if_id} to {mac}"
-                     .format(vm=vm, if_id=if_id, mac=mac_string))
+        logger.debug(
+            'Setting MAC address of "{vm}" interface {if_id} to {mac}'.format(vm=vm, if_id=if_id, mac=mac_string)
+        )
         super().set_mac(vm, mac, if_id=if_id)
 
     def create_snapshot(self, vm, snapshot):
-        logger.debug("Creating snapshot \"{snapshot}\" for \"{vm}\"".format(vm=vm, snapshot=snapshot))
+        logger.debug('Creating snapshot "{snapshot}" for "{vm}"'.format(vm=vm, snapshot=snapshot))
         super().create_snapshot(vm, snapshot)
 
     def delete_snapshot(self, vm, snapshot):
-        logger.debug("Deleting snapshot \"{snapshot}\" of \"{vm}\"".format(vm=vm, snapshot=snapshot))
+        logger.debug('Deleting snapshot "{snapshot}" of "{vm}"'.format(vm=vm, snapshot=snapshot))
         super().delete_snapshot(vm, snapshot)
 
     def restore_snapshot(self, vm, snapshot):
-        logger.debug("Restoring snapshot \"{snapshot}\" of \"{vm}\"".format(vm=vm, snapshot=snapshot))
+        logger.debug('Restoring snapshot "{snapshot}" of "{vm}"'.format(vm=vm, snapshot=snapshot))
         super().restore_snapshot(vm, snapshot)
 
     def clone(self, vm, snapshot, clone):
-        logger.debug("Cloning \"{vm}\" with snapshot \"{snapshot}\" to \"{clone}\""
-                     .format(vm=vm, snapshot=snapshot, clone=clone))
+        logger.debug(
+            'Cloning "{vm}" with snapshot "{snapshot}" to "{clone}"'.format(vm=vm, snapshot=snapshot, clone=clone)
+        )
         super().clone(vm, snapshot, clone)
 
     def set_credentials(self, vm, user, password, domain):
-        logger.debug("Setting credentials of \"{vm}\" to {cred}"
-                     .format(vm=vm, cred=(user, password, domain)))
+        logger.debug('Setting credentials of "{vm}" to {cred}'.format(vm=vm, cred=(user, password, domain)))
         super().set_credentials(vm, user, password, domain)


Test patch:
diff --git a/provisioning/ansible/roles/external_mail_handler/files/test_external_mail_handler.py b/provisioning/ansible/roles/external_mail_handler/files/test_external_mail_handler.py
index 4225d5f..fcea57f 100644
--- a/provisioning/ansible/roles/external_mail_handler/files/test_external_mail_handler.py
+++ b/provisioning/ansible/roles/external_mail_handler/files/test_external_mail_handler.py
@@ -83,7 +83,7 @@ class TestCustomHandler:
         envelope.peer = "127.0.0.1"
         envelope.mail_to = mail.sender
         envelope.rcpt_to = [mail.receiver]
-        envelope.data = mail.to_mime_text().as_string().encode('utf-8')
+        envelope.content = mail.to_mime_text().as_string().encode("utf-8")
         await handler.handle_DATA(None, None, envelope)
         assert handler.swap_sender_receiver.called
         assert handler.send_mail.called
@@ -98,15 +98,19 @@ class TestCustomHandler:
 
     def test_run(self, responder: Responder):
         responder.init_controller = Mock()
-        responder.controller = Mock()  # Mock the controller object itself
+        responder.controller = Mock()
         responder.controller.start = Mock()
         responder.controller.stop = Mock()
 
-        with patch("builtins.input", return_value=""):
-            responder.run()
+        with patch("time.sleep", side_effect=[KeyboardInterrupt]):
+            try:
+                responder.run()
+            except KeyboardInterrupt:
+                pass
+
         assert responder.init_controller.called
         assert responder.controller.start.called
-        assert responder.controller.stop.called
+        assert not responder.controller.stop.called
 
 
 @pytest.fixture()
diff --git a/src/vmcontrol/sessionhandler/tests/test_sessionhandler.py b/src/vmcontrol/sessionhandler/tests/test_sessionhandler.py
index 50d863c..cd85b09 100644
--- a/src/vmcontrol/sessionhandler/tests/test_sessionhandler.py
+++ b/src/vmcontrol/sessionhandler/tests/test_sessionhandler.py
@@ -21,8 +21,7 @@ from unittest.mock import Mock, patch
 
 import pytest
 
-from vmcontrol.sessionhandler import SessionHandler, SessionConfig, SessionHandlerException, \
-    CloneCreator
+from vmcontrol.sessionhandler import SessionHandler, SessionConfig, SessionHandlerException, CloneCreator
 from vmcontrol.sessionhandler.sessionhandler import Clone, DictNamespace
 from vmcontrol.vmmcontroller import VMMControllerException
 from vmcontrol.vmmcontroller.tests.mocks import MockVMMController
@@ -109,8 +108,13 @@ class TestSessionHandler:
     def test_default_config(self):
         config = SessionHandler.default_config()
         assert config.server_vms == [
-            "Internet Router", "Attacker", "Company Router", "Log Server", "Internal Server",
-            "DMZ Server"]
+            "Internet Router",
+            "Attacker",
+            "Company Router",
+            "Log Server",
+            "Internal Server",
+            "DMZ Server",
+        ]
         assert config.client_vm == "Client"
         assert config.number_of_clones == 3
         assert config.vm_start_timeout == 0
@@ -209,8 +213,7 @@ class TestSessionHandler:
         sh.create_clones()
         sh.login_clones()
         for clone in sh.clones:
-            sh.vmmc.set_credentials.assert_any_call(
-                clone.vm, clone.user, clone.password, clone.domain)
+            sh.vmmc.set_credentials.assert_any_call(clone.vm, clone.user, clone.password, clone.domain)
 
     def test_start_session(self, sh: SessionHandler):
         server_vms = sh.config.server_vms
@@ -309,16 +312,16 @@ class TestClone:
 
 @pytest.fixture()
 def cc():
-    father_vm = "Client"
+    parent_vm = "Client"
     base_snapshot = "CloneShot"
     number_of_clones = 5
     mvmmc = MockVMMController()
     some_vm = mvmmc.get_vms()[0]
     some_shot = "Snap"
     mvmmc.create_snapshot(some_vm, some_shot)
-    mvmmc.clone(some_vm, some_shot, father_vm)
-    mvmmc.create_snapshot(father_vm, base_snapshot)
-    cc = CloneCreator(father_vm, base_snapshot, number_of_clones, mvmmc)
+    mvmmc.clone(some_vm, some_shot, parent_vm)
+    mvmmc.create_snapshot(parent_vm, base_snapshot)
+    cc = CloneCreator(parent_vm, base_snapshot, number_of_clones, mvmmc)
     return cc
 
 
@@ -334,7 +337,7 @@ class TestCloneCreator:
     def test_set_vm_name(self, cc: CloneCreator):
         clone = Clone(id=42)
         cc.set_vm_name(clone)
-        assert clone.vm.startswith(cc.father_vm)
+        assert clone.vm.startswith(cc.parent_vm)
 
     def test_set_vm_name_avoiding_current_vms(self, cc: CloneCreator):
         clone = Clone(id=42)
diff --git a/src/vmcontrol/vmmcontroller/tests/mocks.py b/src/vmcontrol/vmmcontroller/tests/mocks.py
index 1c9a732..c56a547 100644
--- a/src/vmcontrol/vmmcontroller/tests/mocks.py
+++ b/src/vmcontrol/vmmcontroller/tests/mocks.py
@@ -25,8 +25,11 @@ def mock_print_decorator():
             self = args[0]
             if self.printing:
                 method_name = str(method.__name__)[6:]
-                print("called \"{method_name}\" with {args} and {kwargs}"
-                      .format(method_name=method_name, args=args[1:], kwargs=kwargs))
+                print(
+                    'called "{method_name}" with {args} and {kwargs}'.format(
+                        method_name=method_name, args=args[1:], kwargs=kwargs
+                    )
+                )
             return method(*args, **kwargs)
 
         return printing_method
@@ -40,10 +43,7 @@ class VM:
     def __init__(self, name):
         self.name = name or ""
         self.snapshots = list()
-        self.macs = [
-            int("0080123456AA", 16) + self.mac_count,
-            int("00801BB456AA", 16) + self.mac_count
-        ]
+        self.macs = [int("0080123456AA", 16) + self.mac_count, int("00801BB456AA", 16) + self.mac_count]
         VM.mac_count += 1
 
 
@@ -139,3 +139,7 @@ class MockVMMController(VMMController):
     @mock_print_decorator()
     def set_credentials(self, vm, user, password, domain):
         pass
+
+    @mock_print_decorator()
+    def set_vrde_port(self, vm, port):
+        pass
diff --git a/src/vmcontrol/vmmcontroller/tests/test_vboxcontroller.py b/src/vmcontrol/vmmcontroller/tests/test_vboxcontroller.py
index e03cec9..90059fe 100644
--- a/src/vmcontrol/vmmcontroller/tests/test_vboxcontroller.py
+++ b/src/vmcontrol/vmmcontroller/tests/test_vboxcontroller.py
@@ -39,11 +39,13 @@ class TestVBoxController:
         assert isinstance(vbc._vboxmanage_execute, Mock)
 
     def test_get_vms(self, vbc: VBoxController):
-        return_value = "\n".join([
-            "\"Attacker\" {8451900b-320a-43b4-9eb9-9bd6656f33ad}",
-            "\"Client\" {4d0986c7-eabc-4cd3-a2f3-e28111a66ac1}",
-            "\"Company Router\" {d75d3108-266b-420e-aa74-bc73f689ee9c}"
-        ])
+        return_value = "\n".join(
+            [
+                '"Attacker" {8451900b-320a-43b4-9eb9-9bd6656f33ad}',
+                '"Client" {4d0986c7-eabc-4cd3-a2f3-e28111a66ac1}',
+                '"Company Router" {d75d3108-266b-420e-aa74-bc73f689ee9c}',
+            ]
+        )
         vbc._vboxmanage_execute = Mock(return_value=return_value)
         vms = vbc.get_vms()
         assert_call(vbc, ["list", "vms"])
@@ -67,11 +69,13 @@ class TestVBoxController:
         assert not vbc.is_running("VM2")
 
     def test_get_running_vms(self, vbc: VBoxController):
-        return_value = "\n".join([
-            "\"Attacker\" {8451900b-320a-43b4-9eb9-9bd6656f33ad}",
-            "\"Client\" {4d0986c7-eabc-4cd3-a2f3-e28111a66ac1}",
-            "\"Company Router\" {d75d3108-266b-420e-aa74-bc73f689ee9c}"
-        ])
+        return_value = "\n".join(
+            [
+                '"Attacker" {8451900b-320a-43b4-9eb9-9bd6656f33ad}',
+                '"Client" {4d0986c7-eabc-4cd3-a2f3-e28111a66ac1}',
+                '"Company Router" {d75d3108-266b-420e-aa74-bc73f689ee9c}',
+            ]
+        )
         vbc._vboxmanage_execute = Mock(return_value=return_value)
         vms = vbc._get_running_vms()
         assert_call(vbc, ["list", "runningvms"])
@@ -89,7 +93,7 @@ class TestVBoxController:
         return_value = {
             "macaddress1": "0800278144CB",
             "macaddress2": "080027859405",
-            "othervalue": "ABAB"
+            "othervalue": "ABAB",
         }
         vbc._get_vm_info = Mock(return_value=return_value)
         macs = vbc.get_macs("VM")
@@ -100,7 +104,7 @@ class TestVBoxController:
         return_value = {
             "macaddress1": "0800278144CB",
             "macaddress2": "080027859405",
-            "othervalue": "ABAB"
+            "othervalue": "ABAB",
         }
         vbc._get_vm_info = Mock(return_value=return_value)
         mac = vbc.get_mac("VM", if_id=1)
@@ -111,7 +115,7 @@ class TestVBoxController:
         return_value = {
             "macaddress1": "0800278144CB",
             "macaddress2": "080027859405",
-            "othervalue": "ABAB"
+            "othervalue": "ABAB",
         }
         vbc._get_vm_info = Mock(return_value=return_value)
         mac = vbc.get_mac("VM", if_id=2)
@@ -122,7 +126,7 @@ class TestVBoxController:
         return_value = {
             "macaddress1": "0800278144CB",
             "macaddress2": "080027859405",
-            "othervalue": "ABAB"
+            "othervalue": "ABAB",
         }
         vbc._get_vm_info = Mock(return_value=return_value)
         with pytest.raises(VMMControllerException) as ei:
@@ -156,6 +160,7 @@ class TestVBoxController:
                 raise VMMControllerException("Some text....")
             else:
                 pass
+
         vbc._vboxmanage_execute = Mock(side_effect=vbox_causes_exception)
         # get_vms may be used for hack
         vbc.get_vms = Mock(return_value=[vm_without_snapshot])
@@ -176,18 +181,13 @@ class TestVBoxController:
 
     def test_clone(self, vbc: VBoxController):
         vbc.clone("VM", "Snapshot", "VMClone")
-        assert_call(vbc, [
-            "clonevm", "VM", "--name", "VMClone",
-            "--options", "link", "--snapshot", "Snapshot",
-            "--register"
-        ])
+        assert_call(
+            vbc, ["clonevm", "VM", "--name", "VMClone", "--options", "link", "--snapshot", "Snapshot", "--register"]
+        )
 
     def test_set_credentials(self, vbc: VBoxController):
         vbc.set_credentials("VM", "TheUser", "ThePassword", "TheDomain")
-        assert_call(vbc, [
-            "controlvm", "VM",
-            "setcredentials", "TheUser", "ThePassword", "TheDomain"
-        ])
+        assert_call(vbc, ["controlvm", "VM", "setcredentials", "TheUser", "ThePassword", "TheDomain"])
 
     def _some_vbox_info_output(self):
         return """name="Company Router"
@@ -339,7 +339,7 @@ CurrentSnapshotNode="SnapshotName-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1"
 
 
 """
-["VBoxManage", "clonevm", self.father, "--name", c.vm_name,
+["VBoxManage", "clonevm", self.parent, "--name", c.vm_name,
                      "--options", "link", "--snapshot", "CloneSnapshot",
                      "--register"])
-"""
\ No newline at end of file
+"""