1"""
2A few checks to make sure the environment is sane
3"""
4
5import errno
6import logging
7import os
8import re
9import socket
10import stat
11import sys
12
13import salt.defaults.exitcodes
14import salt.utils.files
15import salt.utils.path
16import salt.utils.platform
17import salt.utils.user
18from salt.exceptions import CommandExecutionError, SaltClientError, SaltSystemExit
19from salt.log import is_console_configured
20from salt.log.setup import LOG_LEVELS
21
22# Original Author: Jeff Schroeder <jeffschroeder@computer.org>
23
24
25try:
26    import win32file
27    import salt.utils.win_reg
28except ImportError:
29    import resource
30
31log = logging.getLogger(__name__)
32
33ROOT_DIR = "c:\\salt" if salt.utils.platform.is_windows() else "/"
34DEFAULT_SCHEMES = ["tcp://", "udp://", "file://"]
35
36
37def zmq_version():
38    """
39    ZeroMQ python bindings >= 2.1.9 are required
40    """
41    try:
42        import zmq
43    except Exception:  # pylint: disable=broad-except
44        # Return True for local mode
45        return True
46    ver = zmq.__version__
47    # The last matched group can be None if the version
48    # is something like 3.1 and that will work properly
49    match = re.match(r"^(\d+)\.(\d+)(?:\.(\d+))?", ver)
50
51    # Fallthrough and hope for the best
52    if not match:
53        msg = "Using untested zmq python bindings version: '{}'".format(ver)
54        if is_console_configured():
55            log.warning(msg)
56        else:
57            sys.stderr.write("WARNING {}\n".format(msg))
58        return True
59
60    major, minor, point = match.groups()
61
62    if major.isdigit():
63        major = int(major)
64    if minor.isdigit():
65        minor = int(minor)
66
67    # point very well could be None
68    if point and point.isdigit():
69        point = int(point)
70
71    if major == 2 and minor == 1:
72        # zmq 2.1dev could be built against a newer libzmq
73        if "dev" in ver and not point:
74            msg = "Using dev zmq module, please report unexpected results"
75            if is_console_configured():
76                log.warning(msg)
77            else:
78                sys.stderr.write("WARNING: {}\n".format(msg))
79            return True
80        elif point and point >= 9:
81            return True
82    elif major > 2 or (major == 2 and minor > 1):
83        return True
84
85    # If all else fails, gracefully croak and warn the user
86    log.critical("ZeroMQ python bindings >= 2.1.9 are required")
87    if "salt-master" in sys.argv[0]:
88        msg = (
89            "The Salt Master is unstable using a ZeroMQ version "
90            "lower than 2.1.11 and requires this fix: http://lists.zeromq."
91            "org/pipermail/zeromq-dev/2011-June/012094.html"
92        )
93        if is_console_configured():
94            log.critical(msg)
95        else:
96            sys.stderr.write("CRITICAL {}\n".format(msg))
97    return False
98
99
100def lookup_family(hostname):
101    """
102    Lookup a hostname and determine its address family. The first address returned
103    will be AF_INET6 if the system is IPv6-enabled, and AF_INET otherwise.
104    """
105    # If lookups fail, fall back to AF_INET sockets (and v4 addresses).
106    fallback = socket.AF_INET
107    try:
108        hostnames = socket.getaddrinfo(
109            hostname or None, None, socket.AF_UNSPEC, socket.SOCK_STREAM
110        )
111        if not hostnames:
112            return fallback
113        h = hostnames[0]
114        return h[0]
115    except socket.gaierror:
116        return fallback
117
118
119def verify_socket(interface, pub_port, ret_port):
120    """
121    Attempt to bind to the sockets to verify that they are available
122    """
123
124    addr_family = lookup_family(interface)
125    for port in pub_port, ret_port:
126        sock = socket.socket(addr_family, socket.SOCK_STREAM)
127        try:
128            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
129            sock.bind((interface, int(port)))
130        except Exception as exc:  # pylint: disable=broad-except
131            msg = "Unable to bind socket {}:{}".format(interface, port)
132            if exc.args:
133                msg = "{}, error: {}".format(msg, str(exc))
134            else:
135                msg = "{}, this might not be a problem.".format(msg)
136            msg += "; Is there another salt-master running?"
137            if is_console_configured():
138                log.warning(msg)
139            else:
140                sys.stderr.write("WARNING: {}\n".format(msg))
141            return False
142        finally:
143            sock.close()
144
145    return True
146
147
148def verify_logs_filter(files):
149    to_verify = []
150    for filename in files:
151        verify_file = True
152        for scheme in DEFAULT_SCHEMES:
153            if filename.startswith(scheme):
154                verify_file = False
155                break
156        if verify_file:
157            to_verify.append(filename)
158    return to_verify
159
160
161def verify_log_files(files, user):
162    """
163    Verify the log files exist and are owned by the named user.  Filenames that
164    begin with tcp:// and udp:// will be filtered out. Filenames that begin
165    with file:// are handled correctly
166    """
167    return verify_files(verify_logs_filter(files), user)
168
169
170def _get_pwnam(user):
171    """
172    Get the user from passwords database
173    """
174    if salt.utils.platform.is_windows():
175        return True
176    import pwd  # after confirming not running Windows
177
178    try:
179        return pwd.getpwnam(user)
180    except KeyError:
181        msg = (
182            "Failed to prepare the Salt environment for user {}. The user is not"
183            " available.".format(user)
184        )
185        if is_console_configured():
186            log.critical(msg)
187        else:
188            print(msg, file=sys.stderr, flush=True)
189        sys.exit(salt.defaults.exitcodes.EX_NOUSER)
190
191
192def verify_files(files, user):
193    """
194    Verify that the named files exist and are owned by the named user
195    """
196    if salt.utils.platform.is_windows():
197        return True
198
199    # after confirming not running Windows
200    pwnam = _get_pwnam(user)
201    uid = pwnam[2]
202
203    for fn_ in files:
204        dirname = os.path.dirname(fn_)
205        try:
206            if dirname:
207                try:
208                    os.makedirs(dirname)
209                except OSError as err:
210                    if err.errno != errno.EEXIST:
211                        raise
212            if not os.path.isfile(fn_):
213                with salt.utils.files.fopen(fn_, "w"):
214                    pass
215
216        except OSError as err:
217            if os.path.isfile(dirname):
218                msg = "Failed to create path {}, is {} a file?".format(fn_, dirname)
219                raise SaltSystemExit(msg=msg)
220            if err.errno != errno.EACCES:
221                raise
222            msg = 'No permissions to access "{}", are you running as the correct user?'.format(
223                fn_
224            )
225            raise SaltSystemExit(msg=msg)
226
227        except OSError as err:  # pylint: disable=duplicate-except
228            msg = 'Failed to create path "{}" - {}'.format(fn_, err)
229            raise SaltSystemExit(msg=msg)
230
231        stats = os.stat(fn_)
232        if uid != stats.st_uid:
233            try:
234                os.chown(fn_, uid, -1)
235            except OSError:
236                pass
237    return True
238
239
240def verify_env(
241    dirs, user, permissive=False, pki_dir="", skip_extra=False, root_dir=ROOT_DIR
242):
243    """
244    Verify that the named directories are in place and that the environment
245    can shake the salt
246    """
247    if salt.utils.platform.is_windows():
248        return win_verify_env(
249            root_dir, dirs, permissive=permissive, skip_extra=skip_extra
250        )
251
252    # after confirming not running Windows
253    pwnam = _get_pwnam(user)
254    uid = pwnam[2]
255    gid = pwnam[3]
256    groups = salt.utils.user.get_gid_list(user, include_default=False)
257
258    for dir_ in dirs:
259        if not dir_:
260            continue
261        if not os.path.isdir(dir_):
262            try:
263                with salt.utils.files.set_umask(0o022):
264                    os.makedirs(dir_)
265                # If starting the process as root, chown the new dirs
266                if os.getuid() == 0:
267                    os.chown(dir_, uid, gid)
268            except OSError as err:
269                msg = 'Failed to create directory path "{0}" - {1}\n'
270                sys.stderr.write(msg.format(dir_, err))
271                sys.exit(err.errno)
272
273        mode = os.stat(dir_)
274        # If starting the process as root, chown the new dirs
275        if os.getuid() == 0:
276            fmode = os.stat(dir_)
277            if fmode.st_uid != uid or fmode.st_gid != gid:
278                if permissive and fmode.st_gid in groups:
279                    # Allow the directory to be owned by any group root
280                    # belongs to if we say it's ok to be permissive
281                    pass
282                else:
283                    # chown the file for the new user
284                    os.chown(dir_, uid, gid)
285            for subdir in [a for a in os.listdir(dir_) if "jobs" not in a]:
286                fsubdir = os.path.join(dir_, subdir)
287                if "{}jobs".format(os.path.sep) in fsubdir:
288                    continue
289                for root, dirs, files in salt.utils.path.os_walk(fsubdir):
290                    for name in files:
291                        if name.startswith("."):
292                            continue
293                        path = os.path.join(root, name)
294                        try:
295                            fmode = os.stat(path)
296                        except OSError:
297                            pass
298                        if fmode.st_uid != uid or fmode.st_gid != gid:
299                            if permissive and fmode.st_gid in groups:
300                                pass
301                            else:
302                                # chown the file for the new user
303                                os.chown(path, uid, gid)
304                    for name in dirs:
305                        path = os.path.join(root, name)
306                        fmode = os.stat(path)
307                        if fmode.st_uid != uid or fmode.st_gid != gid:
308                            if permissive and fmode.st_gid in groups:
309                                pass
310                            else:
311                                # chown the file for the new user
312                                os.chown(path, uid, gid)
313        # Allow the pki dir to be 700 or 750, but nothing else.
314        # This prevents other users from writing out keys, while
315        # allowing the use-case of 3rd-party software (like django)
316        # to read in what it needs to integrate.
317        #
318        # If the permissions aren't correct, default to the more secure 700.
319        # If acls are enabled, the pki_dir needs to remain readable, this
320        # is still secure because the private keys are still only readable
321        # by the user running the master
322        if dir_ == pki_dir:
323            smode = stat.S_IMODE(mode.st_mode)
324            if smode != 448 and smode != 488:
325                if os.access(dir_, os.W_OK):
326                    os.chmod(dir_, 448)
327                else:
328                    msg = 'Unable to securely set the permissions of "{0}".'
329                    msg = msg.format(dir_)
330                    if is_console_configured():
331                        log.critical(msg)
332                    else:
333                        sys.stderr.write("CRITICAL: {}\n".format(msg))
334
335    if skip_extra is False:
336        # Run the extra verification checks
337        zmq_version()
338
339
340def check_user(user):
341    """
342    Check user and assign process uid/gid.
343    """
344    if salt.utils.platform.is_windows():
345        return True
346    if user == salt.utils.user.get_user():
347        return True
348
349    # after confirming not running Windows
350    pwuser = _get_pwnam(user)
351
352    try:
353        if hasattr(os, "initgroups"):
354            os.initgroups(user, pwuser.pw_gid)  # pylint: disable=minimum-python-version
355        else:
356            os.setgroups(salt.utils.user.get_gid_list(user, include_default=False))
357        os.setgid(pwuser.pw_gid)
358        os.setuid(pwuser.pw_uid)
359
360        # We could just reset the whole environment but let's just override
361        # the variables we can get from pwuser
362        if "HOME" in os.environ:
363            os.environ["HOME"] = pwuser.pw_dir
364
365        if "SHELL" in os.environ:
366            os.environ["SHELL"] = pwuser.pw_shell
367
368        for envvar in ("USER", "LOGNAME"):
369            if envvar in os.environ:
370                os.environ[envvar] = pwuser.pw_name
371
372    except OSError:
373        msg = 'Salt configured to run as user "{}" but unable to switch.'.format(user)
374        if is_console_configured():
375            log.critical(msg)
376        else:
377            sys.stderr.write("CRITICAL: {}\n".format(msg))
378        return False
379    return True
380
381
382def list_path_traversal(path):
383    """
384    Returns a full list of directories leading up to, and including, a path.
385
386    So list_path_traversal('/path/to/salt') would return:
387        ['/', '/path', '/path/to', '/path/to/salt']
388    in that order.
389
390    This routine has been tested on Windows systems as well.
391    list_path_traversal('c:\\path\\to\\salt') on Windows would return:
392        ['c:\\', 'c:\\path', 'c:\\path\\to', 'c:\\path\\to\\salt']
393    """
394    out = [path]
395    (head, tail) = os.path.split(path)
396    if tail == "":
397        # paths with trailing separators will return an empty string
398        out = [head]
399        (head, tail) = os.path.split(head)
400    while head != out[0]:
401        # loop until head is the same two consecutive times
402        out.insert(0, head)
403        (head, tail) = os.path.split(head)
404    return out
405
406
407def check_path_traversal(path, user="root", skip_perm_errors=False):
408    """
409    Walk from the root up to a directory and verify that the current
410    user has access to read each directory. This is used for  making
411    sure a user can read all parent directories of the minion's  key
412    before trying to go and generate a new key and raising an IOError
413    """
414    for tpath in list_path_traversal(path):
415        if not os.access(tpath, os.R_OK):
416            msg = "Could not access {}.".format(tpath)
417            if not os.path.exists(tpath):
418                msg += " Path does not exist."
419            else:
420                current_user = salt.utils.user.get_user()
421                # Make the error message more intelligent based on how
422                # the user invokes salt-call or whatever other script.
423                if user != current_user:
424                    msg += " Try running as user {}.".format(user)
425                else:
426                    msg += " Please give {} read permissions.".format(user)
427
428            # We don't need to bail on config file permission errors
429            # if the CLI
430            # process is run with the -a flag
431            if skip_perm_errors:
432                return
433            # Propagate this exception up so there isn't a sys.exit()
434            # in the middle of code that could be imported elsewhere.
435            raise SaltClientError(msg)
436
437
438def check_max_open_files(opts):
439    """
440    Check the number of max allowed open files and adjust if needed
441    """
442    mof_c = opts.get("max_open_files", 100000)
443    if sys.platform.startswith("win"):
444        # Check the Windows API for more detail on this
445        # http://msdn.microsoft.com/en-us/library/xt874334(v=vs.71).aspx
446        # and the python binding http://timgolden.me.uk/pywin32-docs/win32file.html
447        mof_s = mof_h = win32file._getmaxstdio()
448    else:
449        mof_s, mof_h = resource.getrlimit(resource.RLIMIT_NOFILE)
450
451    accepted_keys_dir = os.path.join(opts.get("pki_dir"), "minions")
452    accepted_count = len(os.listdir(accepted_keys_dir))
453
454    log.debug("This salt-master instance has accepted %s minion keys.", accepted_count)
455
456    level = logging.INFO
457
458    if (accepted_count * 4) <= mof_s:
459        # We check for the soft value of max open files here because that's the
460        # value the user chose to raise to.
461        #
462        # The number of accepted keys multiplied by four(4) is lower than the
463        # soft value, everything should be OK
464        return
465
466    msg = (
467        "The number of accepted minion keys({}) should be lower than 1/4 "
468        "of the max open files soft setting({}). ".format(accepted_count, mof_s)
469    )
470
471    if accepted_count >= mof_s:
472        # This should never occur, it might have already crashed
473        msg += "salt-master will crash pretty soon! "
474        level = logging.CRITICAL
475    elif (accepted_count * 2) >= mof_s:
476        # This is way too low, CRITICAL
477        level = logging.CRITICAL
478    elif (accepted_count * 3) >= mof_s:
479        level = logging.WARNING
480        # The accepted count is more than 3 time, WARN
481    elif (accepted_count * 4) >= mof_s:
482        level = logging.INFO
483
484    if mof_c < mof_h:
485        msg += (
486            "According to the system's hard limit, there's still a "
487            "margin of {} to raise the salt's max_open_files "
488            "setting. ".format(mof_h - mof_c)
489        )
490
491    msg += "Please consider raising this value."
492    log.log(level=level, msg=msg)
493
494
495def _realpath_darwin(path):
496    base = ""
497    for part in path.split(os.path.sep)[1:]:
498        if base != "":
499            if os.path.islink(os.path.sep.join([base, part])):
500                base = os.readlink(os.path.sep.join([base, part]))
501            else:
502                base = os.path.abspath(os.path.sep.join([base, part]))
503        else:
504            base = os.path.abspath(os.path.sep.join([base, part]))
505    return base
506
507
508def _realpath_windows(path):
509    base = ""
510    for part in path.split(os.path.sep):
511        if base != "":
512            try:
513                # Need to use salt.utils.path.readlink as it handles junctions
514                part = salt.utils.path.readlink(os.path.sep.join([base, part]))
515                base = os.path.abspath(part)
516            except OSError:
517                base = os.path.abspath(os.path.sep.join([base, part]))
518        else:
519            base = part
520    return base
521
522
523def _realpath(path):
524    """
525    Cross platform realpath method. On Windows when python 3, this method
526    uses the os.readlink method to resolve any filesystem links.
527    All other platforms and version use ``os.path.realpath``.
528    """
529    if salt.utils.platform.is_darwin():
530        return _realpath_darwin(path)
531    elif salt.utils.platform.is_windows():
532        return _realpath_windows(path)
533    return os.path.realpath(path)
534
535
536def clean_path(root, path, subdir=False):
537    """
538    Accepts the root the path needs to be under and verifies that the path is
539    under said root. Pass in subdir=True if the path can result in a
540    subdirectory of the root instead of having to reside directly in the root
541    """
542    real_root = _realpath(root)
543    if not os.path.isabs(real_root):
544        return ""
545    if not os.path.isabs(path):
546        path = os.path.join(root, path)
547    path = os.path.normpath(path)
548    real_path = _realpath(path)
549    if subdir:
550        if real_path.startswith(real_root):
551            return real_path
552    else:
553        if os.path.dirname(real_path) == os.path.normpath(real_root):
554            return real_path
555    return ""
556
557
558def valid_id(opts, id_):
559    """
560    Returns if the passed id is valid
561    """
562    try:
563        if any(x in id_ for x in ("/", "\\", "\0")):
564            return False
565        return bool(clean_path(opts["pki_dir"], id_))
566    except (AttributeError, KeyError, TypeError, UnicodeDecodeError):
567        return False
568
569
570def safe_py_code(code):
571    """
572    Check a string to see if it has any potentially unsafe routines which
573    could be executed via python, this routine is used to improve the
574    safety of modules suct as virtualenv
575    """
576    bads = ("import", ";", "subprocess", "eval", "open", "file", "exec", "input")
577    for bad in bads:
578        if code.count(bad):
579            return False
580    return True
581
582
583def verify_log(opts):
584    """
585    If an insecre logging configuration is found, show a warning
586    """
587    level = LOG_LEVELS.get(str(opts.get("log_level")).lower(), logging.NOTSET)
588
589    if level < logging.INFO:
590        log.warning(
591            "Insecure logging configuration detected! Sensitive data may be logged."
592        )
593
594
595def win_verify_env(path, dirs, permissive=False, pki_dir="", skip_extra=False):
596    """
597    Verify that the named directories are in place and that the environment
598    can shake the salt
599    """
600    import salt.utils.win_functions
601    import salt.utils.win_dacl
602    import salt.utils.path
603
604    # Make sure the file_roots is not set to something unsafe since permissions
605    # on that directory are reset
606
607    # `salt.utils.path.safe_path` will consider anything inside `C:\Windows` to
608    # be unsafe. In some instances the test suite uses
609    # `C:\Windows\Temp\salt-tests-tmpdir\rootdir` as the file_roots. So, we need
610    # to consider anything in `C:\Windows\Temp` to be safe
611    system_root = os.environ.get("SystemRoot", r"C:\Windows")
612    allow_path = "\\".join([system_root, "TEMP"])
613    if not salt.utils.path.safe_path(path=path, allow_path=allow_path):
614        raise CommandExecutionError(
615            "`file_roots` set to a possibly unsafe location: {}".format(path)
616        )
617
618    # Create the root path directory if missing
619    if not os.path.isdir(path):
620        os.makedirs(path)
621
622    current_user = salt.utils.win_functions.get_current_user()
623    # Set permissions to the registry key
624    if salt.utils.win_functions.is_admin(current_user):
625        reg_path = "HKLM\\SOFTWARE\\Salt Project\\salt"
626        if not salt.utils.win_reg.key_exists(
627            hive="HKLM", key="SOFTWARE\\Salt Project\\salt"
628        ):
629            salt.utils.win_reg.set_value(
630                hive="HKLM", key="SOFTWARE\\Salt Project\\salt"
631            )
632        try:
633            # Make the Administrators group owner
634            # Use the SID to be locale agnostic
635            salt.utils.win_dacl.set_owner(
636                obj_name=reg_path, principal="S-1-5-32-544", obj_type="registry"
637            )
638        except CommandExecutionError:
639            msg = 'Unable to securely set the owner of "{}".'.format(reg_path)
640            if is_console_configured():
641                log.critical(msg)
642            else:
643                sys.stderr.write("CRITICAL: {}\n".format(msg))
644
645        try:
646            # Get a clean dacl by not passing an obj_name
647            dacl = salt.utils.win_dacl.dacl(obj_type="registry")
648
649            # Add aces to the dacl, use the GUID (locale non-specific)
650            # Administrators Group
651            dacl.add_ace(
652                principal="S-1-5-32-544",
653                access_mode="grant",
654                permissions="full_control",
655                applies_to="this_key_subkeys",
656            )
657            # System
658            dacl.add_ace(
659                principal="S-1-5-18",
660                access_mode="grant",
661                permissions="full_control",
662                applies_to="this_key_subkeys",
663            )
664            # Owner
665            dacl.add_ace(
666                principal="S-1-3-4",
667                access_mode="grant",
668                permissions="full_control",
669                applies_to="this_key_subkeys",
670            )
671
672            # Save the dacl to the object
673            dacl.save(obj_name=reg_path, protected=True)
674
675        except CommandExecutionError:
676            msg = 'Unable to securely set the permissions of "{}"'.format(reg_path)
677            if is_console_configured():
678                log.critical(msg)
679            else:
680                sys.stderr.write("CRITICAL: {}\n".format(msg))
681
682    # Set permissions to the root path directory
683    if salt.utils.win_functions.is_admin(current_user):
684        try:
685            # Make the Administrators group owner
686            # Use the SID to be locale agnostic
687            salt.utils.win_dacl.set_owner(obj_name=path, principal="S-1-5-32-544")
688
689        except CommandExecutionError:
690            msg = "Unable to securely set the owner of {}".format(path)
691            if is_console_configured():
692                log.critical(msg)
693            else:
694                sys.stderr.write("CRITICAL: {}\n".format(msg))
695
696        if not permissive:
697            try:
698                # Get a clean dacl by not passing an obj_name
699                dacl = salt.utils.win_dacl.dacl()
700
701                # Add aces to the dacl, use the GUID (locale non-specific)
702                # Administrators Group
703                dacl.add_ace(
704                    principal="S-1-5-32-544",
705                    access_mode="grant",
706                    permissions="full_control",
707                    applies_to="this_folder_subfolders_files",
708                )
709                # System
710                dacl.add_ace(
711                    principal="S-1-5-18",
712                    access_mode="grant",
713                    permissions="full_control",
714                    applies_to="this_folder_subfolders_files",
715                )
716                # Owner
717                dacl.add_ace(
718                    principal="S-1-3-4",
719                    access_mode="grant",
720                    permissions="full_control",
721                    applies_to="this_folder_subfolders_files",
722                )
723
724                # Save the dacl to the object
725                dacl.save(obj_name=path, protected=True)
726
727            except CommandExecutionError:
728                msg = 'Unable to securely set the permissions of "{}".'.format(path)
729                if is_console_configured():
730                    log.critical(msg)
731                else:
732                    sys.stderr.write("CRITICAL: {}\n".format(msg))
733
734    # Create the directories
735    for dir_ in dirs:
736        if not dir_:
737            continue
738        if not os.path.isdir(dir_):
739            try:
740                os.makedirs(dir_)
741            except OSError as err:
742                msg = 'Failed to create directory path "{0}" - {1}\n'
743                sys.stderr.write(msg.format(dir_, err))
744                sys.exit(err.errno)
745
746        # The PKI dir gets its own permissions
747        if dir_ == pki_dir:
748            try:
749                # Make Administrators group the owner
750                salt.utils.win_dacl.set_owner(obj_name=path, principal="S-1-5-32-544")
751
752                # Give Admins, System and Owner permissions
753                # Get a clean dacl by not passing an obj_name
754                dacl = salt.utils.win_dacl.dacl()
755
756                # Add aces to the dacl, use the GUID (locale non-specific)
757                # Administrators Group
758                dacl.add_ace(
759                    principal="S-1-5-32-544",
760                    access_mode="grant",
761                    permissions="full_control",
762                    applies_to="this_folder_subfolders_files",
763                )
764                # System
765                dacl.add_ace(
766                    principal="S-1-5-18",
767                    access_mode="grant",
768                    permissions="full_control",
769                    applies_to="this_folder_subfolders_files",
770                )
771                # Owner
772                dacl.add_ace(
773                    principal="S-1-3-4",
774                    access_mode="grant",
775                    permissions="full_control",
776                    applies_to="this_folder_subfolders_files",
777                )
778
779                # Save the dacl to the object
780                dacl.save(obj_name=dir_, protected=True)
781
782            except CommandExecutionError:
783                msg = 'Unable to securely set the permissions of "{0}".'
784                msg = msg.format(dir_)
785                if is_console_configured():
786                    log.critical(msg)
787                else:
788                    sys.stderr.write("CRITICAL: {}\n".format(msg))
789
790    if skip_extra is False:
791        # Run the extra verification checks
792        zmq_version()
793