import os, subprocess, re
from linuxmusterLinuxclient7 import logging, user
_allowedPrinterUriSchemes = ["ipp", "ipps", "http", "https", "smb", "lpd", "socket", "dnssd"]
[docs]
def installPrinter(networkPath, name=None, username=None):
"""
Installs a networked printer for a user
:param networkPath: The network path of the printer in format `ipp://server/printers/PRINTER-01`
:type networkPath: str
:param name: The name for the printer, defaults to None
:type name: str, optional
:param username: The username of the user whom the is installed printer for. Defaults to the executing user
:type username: str, optional
:return: True on success, False otherwise
:rtype: bool
"""
if username == None:
username = user.username()
if name == None:
name = networkPath.split("/")[-1]
if user.isRoot():
return _installPrinter(username, networkPath, name)
else:
# This will call installPrinter() again with root privileges
return _installPrinterWithoutRoot(networkPath, name)
[docs]
def uninstallAllPrintersOfUser(username):
"""
Uninstalls all printers of a given user
:param username: The username of the user
:type username: str
:return: True on success, False otherwise
:rtype: bool
"""
logging.info(f"Uninstalling all printers of {username}")
installedPrinters = _getInstalledPrintersOfUser(username)
for installedPrinter in installedPrinters:
remainingUsers = [u for u in _getAllowedUsersOfPrinter(installedPrinter) if u != username]
if len(remainingUsers) > 0:
if not _setAllowedUsersOfPrinter(installedPrinter, remainingUsers):
return False
elif not _uninstallPrinter(installedPrinter):
return False
return True
[docs]
def translateSambaToIpp(networkPath):
"""
Translates a samba url, like `\\\\server\\PRINTER-01`, to an ipp url like `ipp://server/printers/PRINTER-01`.
:param networkPath: The samba url
:type networkPath: str
:return: An ipp url
:rtype: str
"""
networkPath = networkPath.replace("\\", "/")
# path has to be translated: \\server\EW-FARBLASER -> ipp://server/printers/EW-farblaser
pattern = re.compile("\\/\\/([^/]+)\\/(.+)")
result = pattern.findall(networkPath)
if len(result) != 1 or len(result[0]) != 2:
logging.error(f"Cannot convert printer network path from samba to ipp, as it is invalid: {networkPath}")
return False, None
ippNetworkPath = f"ipp://{result[0][0]}/printers/{result[0][1]}"
return True, ippNetworkPath
# --------------------
# - Helper functions -
# --------------------
def _installPrinter(username, networkPath, name):
if not _printerNetworkPathIsValid(networkPath):
logging.error(f"Refusing to install printer: invalid network path '{networkPath}'")
return False
if not _printerNameIsValid(name):
logging.error(f"Refusing to install printer: invalid printer name '{name}'")
return False
allowedUsers = _getAllowedUsersOfPrinter(name)
if username not in allowedUsers:
allowedUsers.append(username)
logging.info(f"Install Printer {name} on {networkPath}")
installCommand = ["timeout", "10", "/usr/sbin/lpadmin", "-p", name, "-E", "-v", networkPath, "-m", "everywhere", "-u", f"allow:{','.join(allowedUsers)}"]
logging.debug(f"* running '{' '.join(installCommand)}'")
p = subprocess.call(installCommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if p == 0:
logging.debug("* Success Install Printer!")
return True
elif p == 124:
logging.fatal(f"* Timeout error while installing printer {name} on {networkPath}")
else:
logging.fatal(f"* Error installing printer {name} on {networkPath}!")
return False
def _installPrinterWithoutRoot(networkPath, name):
return subprocess.call(["sudo", "/usr/share/linuxmuster-linuxclient7/scripts/sudoTools", "install-printer", "--path", networkPath, "--name", name]) == 0
def _getInstalledPrintersOfUser(username):
logging.info(f"Getting installed printers of {username}")
command = ["/usr/bin/lpstat", "-U", username, "-p"]
#logging.debug(f"running '{command}'")
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
if not result.returncode == 0:
logging.info("No Printers installed.")
return []
rawInstalledPrinters = list(filter(None, result.stdout.split("\n")))
installedPrinters = []
for rawInstalledPrinter in rawInstalledPrinters:
rawInstalledPrinterList = list(filter(None, rawInstalledPrinter.split(" ")))
if len(rawInstalledPrinterList) < 2:
continue
installedPrinter = rawInstalledPrinterList[1]
installedPrinters.append(installedPrinter)
return installedPrinters
def _uninstallPrinter(name):
logging.info(f"Uninstall Printer {name}")
uninstallCommand = ["timeout", "10", "/usr/sbin/lpadmin", "-x", name]
logging.debug(f"* running '{' '.join(uninstallCommand)}'")
p = subprocess.call(uninstallCommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if p == 0:
logging.debug("* Success Uninstall Printer!")
return True
elif p == 124:
logging.fatal(f"* Timeout error while installing printer {name}")
else:
logging.fatal(f"* Error Uninstalling Printer {name}!")
return False
def _getAllowedUsersOfPrinter(name):
"""
Get the users which are allowed to use a printer
:param name: The name of the printer
:type name: str
:return: List of usernames, empty if the printer does not exist or is not restricted to specific users
:rtype: list
"""
command = ["/usr/bin/lpstat", "-l", "-p", name]
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env={**os.environ, "LC_ALL": "C"})
if not result.returncode == 0:
return []
allowedUsers = []
inAllowedUsers = False
for line in result.stdout.split("\n"):
if line.strip() == "Users allowed:":
inAllowedUsers = True
elif inAllowedUsers and line.startswith("\t\t"):
allowedUsers.append(line.strip())
else:
inAllowedUsers = False
if "(all)" in allowedUsers or "(none)" in allowedUsers:
return []
return allowedUsers
def _setAllowedUsersOfPrinter(name, allowedUsers):
"""
Restrict a printer to the given users
:param name: The name of the printer
:type name: str
:param allowedUsers: The users which are allowed to use the printer
:type allowedUsers: list
:return: True on success, False otherwise
:rtype: bool
"""
logging.info(f"Allow printer {name} only for {', '.join(allowedUsers)}")
command = ["timeout", "10", "/usr/sbin/lpadmin", "-p", name, "-u", f"allow:{','.join(allowedUsers)}"]
logging.debug(f"* running '{' '.join(command)}'")
return subprocess.call(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0
def _printerNetworkPathIsValid(networkPath):
"""
Checks if a printer network path is an URI with an allowed scheme (e.g. `ipp://`)
:param networkPath: The network path of the printer
:type networkPath: str
:return: True if valid, False otherwise
:rtype: bool
"""
if not networkPath or not networkPath.isprintable():
return False
match = re.match(r"^([a-zA-Z][a-zA-Z0-9+.-]*)://", networkPath)
if not match:
return False
return match.group(1).lower() in _allowedPrinterUriSchemes
def _printerNameIsValid(name):
"""
Checks if a printer name is valid for CUPS (printable characters except space, tab, `/`, `\\`, `#` and quotes)
:param name: The name of the printer
:type name: str
:return: True if valid, False otherwise
:rtype: bool
"""
if not name or not name.isprintable() or name.startswith("-"):
return False
return not any(c in name for c in " \t/\\#'\"")