Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 76 additions & 3 deletions usr/lib/python3/dist-packages/linuxmusterLinuxclient7/computer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import socket
from linuxmusterLinuxclient7 import logging, ldapHelper, realm, localUserHelper
import socket, os, subprocess
from linuxmusterLinuxclient7 import logging, constants, ldapHelper, realm, localUserHelper

def hostname():
"""
Expand All @@ -10,6 +10,31 @@ def hostname():
"""
return socket.gethostname().split('.', 1)[0]

def hostnameIsValid():
"""
Check if the hostname can be used as a computer account name in the AD (max. 15 characters)

:return: True or False
:rtype: bool
"""
return len(hostname()) <= constants.maxHostnameLength

def checkHostname():
"""
Check if the hostname is valid. If not, an error is logged and shown on the GDM login screen.

:return: True if the hostname is valid, False otherwise
:rtype: bool
"""
if hostnameIsValid():
_setLoginScreenWarning(None)
return True

message = f"The hostname '{hostname()}' is longer than {constants.maxHostnameLength} characters, domain logins will not work! Please use a shorter hostname in the devices.csv."
logging.error(message)
_setLoginScreenWarning(message)
return False

def krbHostName():
"""
Get the krb hostname, eg. `COMPUTER01$`
Expand Down Expand Up @@ -54,4 +79,52 @@ def isInAD():
if not rc:
return False

return "domain computers" in groups
return "domain computers" in groups

# --------------------
# - Helper functions -
# --------------------

def _setLoginScreenWarning(message):
"""
Show a warning on the GDM login screen or remove it

:param message: The message to show, None to remove the warning
:type message: str
:return: True if the warning was changed, False otherwise
:rtype: bool
"""
if not os.path.isdir(constants.gdmDconfDir):
return False

if message == None:
if not os.path.exists(constants.loginScreenWarningFile):
return False
os.remove(constants.loginScreenWarningFile)
else:
content = f"[org/gnome/login-screen]\nbanner-message-enable=true\nbanner-message-text=\"{message}\"\n"
if os.path.exists(constants.loginScreenWarningFile):
with open(constants.loginScreenWarningFile, "r") as file:
if file.read() == content:
return False
with open(constants.loginScreenWarningFile, "w") as file:
file.write(content)

subprocess.call(["/usr/share/gdm/generate-config"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if not _userIsLoggedIn():
subprocess.call(["systemctl", "try-restart", "gdm"])
return True

def _userIsLoggedIn():
"""
Check if a user (other than the display manager) is logged in

:return: True or False
:rtype: bool
"""
result = subprocess.run(["loginctl", "list-users", "--no-legend"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
for line in result.stdout.split("\n"):
columns = line.split()
if len(columns) >= 2 and not columns[1].startswith("gdm"):
return True
return False
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
shareMountBasepath = "/home/{}/media"
hiddenShareMountBasepath = "/srv/samba/{}"
machineAccountSysvolMountPath = "/var/lib/samba/sysvol"
maxHostnameLength = 15
gdmDconfDir = "/usr/share/gdm/dconf"
loginScreenWarningFile = gdmDconfDir + "/95-linuxmuster-linuxclient7"
defaultShareLetterTemplate = " ({letter}:)"

etcBaseDir = "/etc/linuxmuster-linuxclient7"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ def setup(domain=None, user=None):
"""
logging.info('#### linuxmuster-linuxclient7 setup ####')

if not computer.checkHostname():
return False

userCacheCleared = realm.clearUserCache()
if not userCacheCleared and isSetup():
return False
Expand Down Expand Up @@ -88,6 +91,10 @@ def status():

logging.info("Linuxmuster-linuxclient7 is setup!")

logging.info("Checking hostname...")
if not computer.checkHostname():
return False

logging.info("Checking sssd krb5_child capabilities...")
if not _checkSssdKrb5ChildCaps():
return False
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,70 @@
from subprocess import CompletedProcess
from unittest import mock
from .. import computer
import os

@mock.patch("linuxmusterLinuxclient7.computer.socket.gethostname")
def test_hostname(mockSocketGetHostname):
mockSocketGetHostname.return_value = "computer1.linuxmuster.lan"
assert computer.hostname() == "computer1"

@mock.patch("linuxmusterLinuxclient7.computer.hostname")
def test_hostnameIsValid(mockHostname):
mockHostname.return_value = "computer1"
assert computer.hostnameIsValid()
mockHostname.return_value = "aabk-r100-pc012"
assert computer.hostnameIsValid()
mockHostname.return_value = "aabk-r100-pc0123"
assert not computer.hostnameIsValid()

@mock.patch("linuxmusterLinuxclient7.computer._setLoginScreenWarning")
@mock.patch("linuxmusterLinuxclient7.computer.hostname")
def test_checkHostname(mockHostname, mockSetLoginScreenWarning):
mockHostname.return_value = "computer1"
assert computer.checkHostname()
assert mockSetLoginScreenWarning.call_args.args[0] is None

mockHostname.return_value = "aabk-r100-pc0123"
assert not computer.checkHostname()
assert "aabk-r100-pc0123" in mockSetLoginScreenWarning.call_args.args[0]

@mock.patch("linuxmusterLinuxclient7.computer._userIsLoggedIn")
@mock.patch("linuxmusterLinuxclient7.computer.subprocess.call")
def test_setLoginScreenWarning(mockSubprocessCall, mockUserIsLoggedIn, tmp_path):
warningFile = str(tmp_path / "95-linuxmuster-linuxclient7")
mockUserIsLoggedIn.return_value = False

with mock.patch("linuxmusterLinuxclient7.computer.constants.gdmDconfDir", str(tmp_path)), mock.patch("linuxmusterLinuxclient7.computer.constants.loginScreenWarningFile", warningFile):
assert not computer._setLoginScreenWarning(None)
assert mockSubprocessCall.call_count == 0

assert computer._setLoginScreenWarning("Hostname too long")
with open(warningFile) as file:
content = file.read()
assert "banner-message-enable=true" in content
assert 'banner-message-text="Hostname too long"' in content
assert ["systemctl", "try-restart", "gdm"] in [c.args[0] for c in mockSubprocessCall.call_args_list]

mockSubprocessCall.reset_mock()
assert not computer._setLoginScreenWarning("Hostname too long")
assert mockSubprocessCall.call_count == 0

mockUserIsLoggedIn.return_value = True
assert computer._setLoginScreenWarning(None)
assert not os.path.exists(warningFile)
assert ["systemctl", "try-restart", "gdm"] not in [c.args[0] for c in mockSubprocessCall.call_args_list]

with mock.patch("linuxmusterLinuxclient7.computer.constants.gdmDconfDir", str(tmp_path / "missing")):
assert not computer._setLoginScreenWarning("Hostname too long")

@mock.patch("linuxmusterLinuxclient7.computer.subprocess.run")
def test_userIsLoggedIn(mockSubprocessRun):
mockSubprocessRun.return_value = CompletedProcess(args=[], returncode=0, stdout="60578 gdm-greeter no active\n")
assert not computer._userIsLoggedIn()

mockSubprocessRun.return_value = CompletedProcess(args=[], returncode=0, stdout="60578 gdm-greeter no active\n1662401376 yukigrun no active\n")
assert computer._userIsLoggedIn()

@mock.patch("linuxmusterLinuxclient7.computer.hostname")
def test_krbHostName(mockHostname):
mockHostname.return_value = "computer1"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from unittest import mock
from .. import setup

@mock.patch("linuxmusterLinuxclient7.setup.realm.clearUserCache")
@mock.patch("linuxmusterLinuxclient7.setup.computer.checkHostname")
def test_setupInvalidHostname(mockCheckHostname, mockClearUserCache):
mockCheckHostname.return_value = False
assert not setup.setup()
assert mockClearUserCache.call_count == 0

@mock.patch("linuxmusterLinuxclient7.setup._checkSssdKrb5ChildCaps")
@mock.patch("linuxmusterLinuxclient7.setup.computer.checkHostname")
@mock.patch("linuxmusterLinuxclient7.setup.isSetup")
def test_statusInvalidHostname(mockIsSetup, mockCheckHostname, mockCheckSssdKrb5ChildCaps):
mockIsSetup.return_value = True
mockCheckHostname.return_value = False
assert not setup.status()
assert mockCheckSssdKrb5ChildCaps.call_count == 0
4 changes: 3 additions & 1 deletion usr/share/linuxmuster-linuxclient7/scripts/onBoot
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
# This script is called in root context when the client is booted
try:
import os, sys
from linuxmusterLinuxclient7 import logging, hooks, shares, constants, keytab, realm
from linuxmusterLinuxclient7 import logging, hooks, shares, constants, keytab, realm, computer

logging.info("====== onBoot started ======")

computer.checkHostname()

keytab.patchKeytab()
realm.pullKerberosTicketForComputerAccount(timeout=5)

Expand Down
Loading