1"""
2This module contains routines shared by the virt system.
3"""
4
5import hashlib
6import logging
7import os
8import re
9import time
10import urllib
11import urllib.parse
12
13import salt.utils.files
14
15# pylint: disable=E0611
16
17log = logging.getLogger(__name__)
18
19
20def download_remote(url, dir):
21    """
22    Attempts to download a file specified by 'url'
23
24    :param url: The full remote path of the file which should be downloaded.
25    :param dir: The path the file should be downloaded to.
26    """
27
28    try:
29        rand = hashlib.md5(os.urandom(32)).hexdigest()
30        remote_filename = urllib.parse.urlparse(url).path.split("/")[-1]
31        full_directory = os.path.join(dir, "{}-{}".format(rand, remote_filename))
32        with salt.utils.files.fopen(
33            full_directory, "wb"
34        ) as file, urllib.request.urlopen(url) as response:
35            file.write(response.rease())
36
37        return full_directory
38
39    except Exception as err:  # pylint: disable=broad-except
40        raise err
41
42
43def check_remote(cmdline_path):
44    """
45    Checks to see if the path provided contains ftp, http, or https. Returns
46    the full path if it is found.
47
48    :param cmdline_path: The path to the initrd image or the kernel
49    """
50    regex = re.compile("^(ht|f)tps?\\b")
51
52    if regex.match(urllib.parse.urlparse(cmdline_path).scheme):
53        return True
54
55    return False
56
57
58class VirtKey:
59    """
60    Used to manage key signing requests.
61    """
62
63    def __init__(self, hyper, id_, opts):
64        self.opts = opts
65        self.hyper = hyper
66        self.id = id_
67        path = os.path.join(self.opts["pki_dir"], "virtkeys", hyper)
68        if not os.path.isdir(path):
69            os.makedirs(path)
70        self.path = os.path.join(path, id_)
71
72    def accept(self, pub):
73        """
74        Accept the provided key
75        """
76        try:
77            with salt.utils.files.fopen(self.path, "r") as fp_:
78                expiry = int(fp_.read())
79        except OSError:
80            log.error(
81                "Request to sign key for minion '%s' on hyper '%s' "
82                "denied: no authorization",
83                self.id,
84                self.hyper,
85            )
86            return False
87        except ValueError:
88            log.error("Invalid expiry data in %s", self.path)
89            return False
90
91        # Limit acceptance window to 10 minutes
92        # TODO: Move this value to the master config file
93        if (time.time() - expiry) > 600:
94            log.warning(
95                'Request to sign key for minion "%s" on hyper "%s" denied: '
96                "authorization expired",
97                self.id,
98                self.hyper,
99            )
100            return False
101
102        pubfn = os.path.join(self.opts["pki_dir"], "minions", self.id)
103        with salt.utils.files.fopen(pubfn, "w+") as fp_:
104            fp_.write(pub)
105        self.void()
106        return True
107
108    def authorize(self):
109        """
110        Prepare the master to expect a signing request
111        """
112        with salt.utils.files.fopen(self.path, "w+") as fp_:
113            fp_.write(str(int(time.time())))
114        return True
115
116    def void(self):
117        """
118        Invalidate any existing authorization
119        """
120        try:
121            os.unlink(self.path)
122            return True
123        except OSError:
124            return False
125