File: netbsd.py

package info (click to toggle)
cloud-init 25.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 12,412 kB
  • sloc: python: 135,894; sh: 3,883; makefile: 141; javascript: 30; xml: 22
file content (180 lines) | stat: -rw-r--r-- 5,647 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# Copyright (C) 2019-2020 Gonéri Le Bouder
#
# This file is part of cloud-init. See LICENSE file for license information.

import functools
import logging
import os
import platform
from typing import Any

import cloudinit.distros.bsd
from cloudinit import subp, util

try:
    import crypt  # pylint: disable=W4901

    salt = crypt.METHOD_BLOWFISH  # pylint: disable=E1101
    blowfish_hash: Any = functools.partial(
        crypt.crypt,
        salt=crypt.mksalt(salt),
    )
except (ImportError, AttributeError):
    try:
        from passlib.hash import bcrypt

        blowfish_hash = bcrypt.hash
    except ImportError:

        def blowfish_hash(_):
            """Raise when called so that importing this module doesn't throw
            ImportError when this module is not used. In this case, crypt
            and passlib are not needed.
            """
            raise ImportError(
                "crypt and passlib not found, missing dependency"
            )


LOG = logging.getLogger(__name__)


class NetBSD(cloudinit.distros.bsd.BSD):
    """
    Distro subclass for NetBSD.

    (N.B. OpenBSD inherits from this class.)
    """

    ci_sudoers_fn = "/usr/pkg/etc/sudoers.d/90-cloud-init-users"
    group_add_cmd_prefix = ["groupadd"]

    # For NetBSD (from https://man.netbsd.org/passwd.5) a password field
    # value of either "" or "*************" (13 "*") indicates no password,
    # a password field prefixed with "*LOCKED*" indicates a locked
    # password, and a password field of "*LOCKED*" followed by 13 "*"
    # indicates a locked and blank password.
    shadow_empty_locked_passwd_patterns = [
        r"^{username}::",
        r"^{username}:\*\*\*\*\*\*\*\*\*\*\*\*\*:",
        r"^{username}:\*LOCKED\*\*\*\*\*\*\*\*\*\*\*\*\*\*:",
    ]

    def __init__(self, name, cfg, paths):
        super().__init__(name, cfg, paths)
        if os.path.exists("/usr/pkg/bin/pkgin"):
            self.pkg_cmd_install_prefix = ["pkgin", "-y", "install"]
            self.pkg_cmd_remove_prefix = ["pkgin", "-y", "remove"]
            self.pkg_cmd_update_prefix = ["pkgin", "-y", "update"]
            self.pkg_cmd_upgrade_prefix = ["pkgin", "-y", "full-upgrade"]
        else:
            self.pkg_cmd_install_prefix = ["pkg_add", "-U"]
            self.pkg_cmd_remove_prefix = ["pkg_delete"]

    def _get_add_member_to_group_cmd(self, member_name, group_name):
        return ["usermod", "-G", group_name, member_name]

    def add_user(self, name, **kwargs) -> bool:
        """
        Add a user to the system using standard tools

        Returns False if user already exists, otherwise True.
        """
        if util.is_user(name):
            LOG.info("User %s already exists, skipping.", name)
            return False

        adduser_cmd = ["useradd"]
        log_adduser_cmd = ["useradd"]

        adduser_opts = {
            "homedir": "-d",
            "gecos": "-c",
            "primary_group": "-g",
            "groups": "-G",
            "shell": "-s",
        }
        adduser_flags = {
            "no_user_group": "--no-user-group",
            "system": "--system",
            "no_log_init": "--no-log-init",
        }

        for key, val in kwargs.items():
            if key in adduser_opts and val and isinstance(val, str):
                adduser_cmd.extend([adduser_opts[key], val])

            elif key in adduser_flags and val:
                adduser_cmd.append(adduser_flags[key])
                log_adduser_cmd.append(adduser_flags[key])

        if "no_create_home" not in kwargs or "system" not in kwargs:
            adduser_cmd += ["-m"]
            log_adduser_cmd += ["-m"]

        adduser_cmd += [name]
        log_adduser_cmd += [name]

        # Run the command
        LOG.info("Adding user %s", name)
        try:
            subp.subp(adduser_cmd, logstring=log_adduser_cmd)
        except Exception:
            util.logexc(LOG, "Failed to create user %s", name)
            raise
        # Set the password if it is provided
        # For security consideration, only hashed passwd is assumed
        passwd_val = kwargs.get("passwd", None)
        if passwd_val is not None:
            self.set_passwd(name, passwd_val, hashed=True)

        # Indicate that a new user was created
        return True

    def set_passwd(self, user, passwd, hashed=False):
        if hashed:
            hashed_pw = passwd
        else:
            hashed_pw = blowfish_hash(passwd)

        try:
            subp.subp(["usermod", "-p", hashed_pw, user])
        except Exception:
            util.logexc(LOG, "Failed to set password for %s", user)
            raise
        self.unlock_passwd(user)

    def lock_passwd(self, name):
        try:
            subp.subp(["usermod", "-C", "yes", name])
        except Exception:
            util.logexc(LOG, "Failed to lock user %s", name)
            raise

    def unlock_passwd(self, name):
        try:
            subp.subp(["usermod", "-C", "no", name])
        except Exception:
            util.logexc(LOG, "Failed to unlock user %s", name)
            raise

    def apply_locale(self, locale, out_fn=None):
        LOG.debug("Cannot set the locale.")

    def _get_pkg_cmd_environ(self):
        """Return env vars used in NetBSD package_command operations"""
        os_release = platform.release()
        os_arch = platform.machine()
        return {
            "PKG_PATH": (
                f"http://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD"
                f"/{os_arch}/{os_release}/All"
            )
        }

    def update_package_sources(self, *, force=False):
        pass


class Distro(NetBSD):
    pass