]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
scheduler: Added extra check for administrative tasks from Snaps
authorTill Kamppeter <till.kamppeter@gmail.com>
Mon, 1 Mar 2021 17:26:43 +0000 (18:26 +0100)
committerTill Kamppeter <till.kamppeter@gmail.com>
Mon, 1 Mar 2021 17:26:43 +0000 (18:26 +0100)
Let the scheduler check whether if an administrative request
(create/modify print queue, delete someone else's jobs, ...) from a
client is from a fully confined Snap and then only grant access if the
client Snap plugs "cups-control". If client Snap plugs "cups" instead
it can only print, check status, or remove the caller's own jobs. For
requests from classically confined Snaps or unsnapped clients access
is always granted.

This is to protect arbitrary Snaps from the Snap Store to do
administrative CUPS tasks. The Snap Store allows automatic connection
only of the "cups" interface, not of the cups-control interface.

This facility is optional, to be activated by configure options,
"--enable-snapped-clients" for unsnapped CUPS and
"--enable-snapped-cupsd" when CUPS itself is also in a Snap. The
former accesses the needed information about the client using
libsnapd-glib (which cannot be used from within a Snap) and latter
uses the "snapctl" utility (which only works from within a Snap). In
both cases also libapparmor is needed to determine whether the client
is actually a Snap.

CHANGES.md
Makedefs.in
config-scripts/cups-snap.m4 [new file with mode: 0644]
config.h.in
configure.ac
scheduler/Makefile
scheduler/auth.c

index 589246c531c8132ea5562d82dbe0e3ab3d337c60..f7cf2df5cef9a61cde7335057b1a4b8d34c10971 100644 (file)
@@ -4,6 +4,9 @@ Changes in OpenPrinting CUPS
 CUPS v2.4rc1 (Pending)
 ----------------------
 
+- Let the scheduler check whether if an administrative request from a client
+  is from a confined Snap and then only grant access if the client Snap plugs
+  "cups-control". This is optional, to be activated by configure options.
 - The `testlang` unit test program now loops over all of the available locales
   by default (Issue #85)
 - The `cupsfilter` command now shows error messages when options are used
index 9ee75856dec509148acd5845ae6c96114ac7d42b..477ef37fc9cc03b6e155b071831f93888d9ba40f 100644 (file)
@@ -171,6 +171,8 @@ DSOFLAGS    =       @DSOFLAGS@
 DNSSDLIBS      =       @DNSSDLIBS@
 IPPFIND_BIN    =       @IPPFIND_BIN@
 IPPFIND_MAN    =       @IPPFIND_MAN@
+APPARMORLIBS   =       @APPARMORLIBS@
+SNAPDGLIBLIBS  =       @SNAPDGLIBLIBS@
 LDFLAGS                =       @LDFLAGS@
 LINKCUPS       =       @LINKCUPS@
 LINKCUPSSTATIC =       ../cups/$(LIBCUPSSTATIC) $(LIBS)
diff --git a/config-scripts/cups-snap.m4 b/config-scripts/cups-snap.m4
new file mode 100644 (file)
index 0000000..a16446f
--- /dev/null
@@ -0,0 +1,82 @@
+dnl
+dnl Support for packaging CUPS in a Snap.
+dnl
+dnl Copyright © 2020 by Till Kamppeter
+dnl Copyright © 2007-2019 by Apple Inc.
+dnl
+dnl Licensed under Apache License v2.0.  See the file "LICENSE" for more
+dnl information.
+dnl
+
+# Snap packaging support
+
+AC_ARG_ENABLE(snapped_cupsd, [  --enable-snapped-cupsd  enable support for packaging CUPS in a Snap])
+AC_ARG_ENABLE(snapped_clients, [  --enable-snapped-clients enable support for CUPS controlling admin access from snapped clients])
+AC_ARG_WITH(snapctl, [  --with-snapctl          Set path for snapctl, only needed with --enable-snapped-cupsd, default=/usr/bin/snapctl],
+        SNAPCTL="$withval", SNAPCTL="/usr/bin/snapctl")
+AC_DEFINE_UNQUOTED(SNAPCTL, "$SNAPCTL")
+AC_ARG_WITH(cups_control_slot, [  --with-cups-control-slot Name for cups-control slot as defined in snapcraft.yaml, only needed with --enable-snapped-cupsd, default=admin],
+        CUPS_CONTROL_SLOT="$withval", CUPS_CONTROL_SLOT="admin")
+AC_DEFINE_UNQUOTED(CUPS_CONTROL_SLOT, "$CUPS_CONTROL_SLOT")
+
+APPARMORLIBS=""
+SNAPDGLIBLIBS=""
+ENABLE_SNAPPED_CUPSD="NO"
+ENABLE_SNAPPED_CLIENTS="NO"
+
+# Both --enable-snapped-cupsd and --enable-snapped-clients are about additional
+# access control for clients, allowing clients which are confined Snaps only
+# to do adminstrative tasks (create queues, delete someone else's jobs, ...)
+# if they plug the "cups-control" interface, so  --enable-snapped-cupsd implies
+# --enable-snapped-clients. The difference is only the method how to determine
+# whether a client Snap is confined and plugs "cups-control".
+if test x$enable_snapped_cupsd == xyes; then
+       enable_snapped_clients=yes;
+fi
+
+if test "x$PKGCONFIG" != x -a x$enable_snapped_clients == xyes; then
+       AC_MSG_CHECKING(for libapparmor)
+       if $PKGCONFIG --exists libapparmor; then
+               AC_MSG_RESULT(yes)
+               CFLAGS="$CFLAGS `$PKGCONFIG --cflags libapparmor`"
+               APPARMORLIBS="`$PKGCONFIG --libs libapparmor`"
+               AC_DEFINE(HAVE_APPARMOR)
+               if test x$enable_snapped_cupsd == xyes; then
+                       AC_PATH_TOOL(SNAPCTL, snapctl)
+                       AC_MSG_CHECKING(for "snapctl is-connected" support)
+                       if test "x$SNAPCTL" != x && $SNAPCTL is-connected --help >/dev/null 2>&1; then
+                               AC_MSG_RESULT(yes)
+                               AC_DEFINE(HAVE_SNAPCTL_IS_CONNECTED)
+                               AC_DEFINE(BUILD_SNAP)
+                               ENABLE_SNAPPED_CUPSD="YES"
+                               ENABLE_SNAPPED_CLIENTS="YES"
+                       else
+                               AC_MSG_RESULT(no)
+                       fi
+               else
+                       AC_MSG_CHECKING(for snapd-glib)
+                       if $PKGCONFIG --exists snapd-glib glib-2.0 gio-2.0; then
+                               AC_MSG_RESULT(yes)
+                               CFLAGS="$CFLAGS `$PKGCONFIG --cflags snapd-glib glib-2.0 gio-2.0`"
+                               SNAPDGLIBLIBS="`$PKGCONFIG --libs snapd-glib glib-2.0 gio-2.0`"
+                               AC_DEFINE(HAVE_SNAPDGLIB)
+                               AC_DEFINE(BUILD_SNAP)
+                               ENABLE_SNAPPED_CLIENTS="YES"
+                       else
+                               AC_MSG_RESULT(no)
+                       fi
+               fi
+       else
+               AC_MSG_RESULT(no)
+       fi
+fi
+
+AC_MSG_CHECKING(for Snap support)
+if test "x$ENABLE_SNAPPED_CLIENTS" != "xNO"; then
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
+fi
+
+AC_SUBST(APPARMORLIBS)
+AC_SUBST(SNAPDGLIBLIBS)
index 4807b09e4601f332744739aad3b42d055e8c8f7f..47b826c8bcd4e5911188b0e64e6d46f364445c00 100644 (file)
 #undef HAVE_SYS_STATVFS_H
 #undef HAVE_SYS_VFS_H
 
+/*
+ * Do we want Snap packaging support and have the needed libraries and
+ * utilities?
+ */
+
+#undef HAVE_APPARMOR
+#undef HAVE_SNAPDGLIB
+#undef HAVE_SNAPCTL_IS_CONNECTED
+#undef SNAPCTL
+#undef CUPS_CONTROL_SLOT
+#undef BUILD_SNAP
 
 /*
  * Location of macOS localization bundle, if any.
index 2e8581b5d0fb970b8889b2b6baf9147407e60cd9..fbd374daecb661d55640d1b512705428632d56f8 100644 (file)
@@ -32,6 +32,7 @@ sinclude(config-scripts/cups-ssl.m4)
 sinclude(config-scripts/cups-pam.m4)
 sinclude(config-scripts/cups-largefile.m4)
 sinclude(config-scripts/cups-dnssd.m4)
+sinclude(config-scripts/cups-snap.m4)
 sinclude(config-scripts/cups-startup.m4)
 sinclude(config-scripts/cups-defaults.m4)
 
index 138f174adda35a873b11c8d1cca6e9682be58e8d..3d5ed065df6818d0fb542403a4b4da357da8ce11 100644 (file)
@@ -318,13 +318,15 @@ cupsd:    $(CUPSDOBJS) libcupsmime.a ../cups/$(LIBCUPS)
        echo Linking $@...
        $(LD_CC) $(ALL_LDFLAGS) -o cupsd $(CUPSDOBJS) libcupsmime.a \
                $(PAMLIBS) $(LIBPAPER) $(LIBMALLOC) $(DNSSDLIBS) $(SERVERLIBS) \
-               $(ONDEMANDLIBS) $(LIBWRAP) $(LIBGSSAPI) $(COMMONLIBS) $(LINKCUPS)
+               $(ONDEMANDLIBS) $(LIBWRAP) $(LIBGSSAPI) $(COMMONLIBS) $(LINKCUPS) \
+               $(APPARMORLIBS) $(SNAPDGLIBLIBS)
        $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@
 
 cupsd-static:  $(CUPSDOBJS) libcupsmime.a ../cups/$(LIBCUPSSTATIC)
        echo Linking $@...
        $(LD_CC) $(ALL_LDFLAGS) -o cupsd-static $(CUPSDOBJS) libcupsmime.a \
-               $(PAMLIBS) $(LIBPAPER) $(LIBMALLOC) $(SERVERLIBS) $(ONDEMANDLIBS) \ $(LIBWRAP) $(LINKCUPSSTATIC)
+               $(PAMLIBS) $(LIBPAPER) $(LIBMALLOC) $(SERVERLIBS) $(ONDEMANDLIBS) \
+               $(LIBWRAP) $(APPARMORLIBS) $(SNAPDGLIBLIBS) $(LINKCUPSSTATIC)
        $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@
 
 
index 4fbad6e2440f38375b957de68d1b0e6f20ff2033..0107e15cc395e500d445cd1b1bd2fb01d1a4b963 100644 (file)
@@ -51,6 +51,13 @@ typedef struct sockpeercred cupsd_ucred_t;
 #  endif
 #  define CUPSD_UCRED_UID(c) (c).uid
 #endif /* HAVE_SYS_UCRED_H */
+#ifdef BUILD_SNAP
+#  include <sys/apparmor.h>
+#  ifdef HAVE_SNAPDGLIB
+#    include <glib.h>
+#    include <snapd-glib/snapd-glib.h>
+#  endif
+#endif /* BUILD_SNAP */
 
 
 /*
@@ -1520,6 +1527,396 @@ cupsdFreeLocation(cupsd_location_t *loc)/* I - Location to free */
 }
 
 
+/*
+ * 'cupsdCheckAdminTask()' - Do additional checks on administrative tasks
+ */
+
+int                                      /* O - 1 if admin task authorized */
+cupsdCheckAdminTask(cupsd_client_t *con) /* I - Connection */
+{
+  int ret = 1; /* Return value */
+
+  cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Administrative task");
+
+ /*
+  * If the client accesses locally via domain socket, find out whether it
+  * is a Snap. Grant access if it is not a Snap, if it is a classic Snap
+  * or if it is a confined Snap which plugs "cups-control", deny access
+  * if it is a confined Snap not plugging "cups-control" or if an error
+  * occurs in the process of finding this out.
+  */
+
+#if defined(AF_LOCAL) && defined(BUILD_SNAP)
+
+ /*
+  * Get the client's file descriptor and from this its AppArmor context
+  */
+
+  if (httpAddrFamily(con->http->hostaddr) == AF_LOCAL)
+  {
+    int                 peerfd;         /* Peer's file descriptor */
+
+    peerfd = httpGetFd(con->http);
+
+    if (peerfd < 0)
+    {
+      cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: Unable to get peer file descriptor of client connecting via domain socket");
+    }
+    else
+    {
+      char *context = NULL; /* AppArmor profile name of client */
+#  ifdef HAVE_SNAPCTL_IS_CONNECTED
+      char buf[1024];
+      char *argv[5];       /* snapctl command line */
+      int fds[2],          /* Pipe file descriptors for stderr of snapctl */
+         nullfd;          /* /dev/null file descriptor for stdout of snapctl */
+      pid_t pid;           /* PID of snapctl */
+      cups_file_t *snapctl_stderr; /* CUPS FP for stderr of snapctl */
+      int status = 65536;  /* Status of forked snapctl process */
+      int wstatus;         /* Wait result of forked snapctl process */
+#  else
+#    ifdef HAVE_SNAPDGLIB
+      char *snap_name = NULL;    /* Client Snap name */
+      char *dot;                 /* Pointer to dot in AppArmor profile name */
+      SnapdClient *snapd = NULL; /* Data structure of snapd access */
+      SnapdSnap *snap = NULL;    /* Data structure of client Snap */
+      GPtrArray *plugs = NULL;   /* Plug search result of client Snap */
+      GError *error = NULL;      /* Glib error */
+#    endif /* HAVE_SNAPDGLIB */
+#  endif /* HAVE_SNAPCTL_IS_CONNECTED */
+
+#  ifndef HAVE_SNAPCTL_IS_CONNECTED
+
+      /* If AppArmor is not enabled, then we can't identify the client */
+      /* With cupsd running in a Snaps, the "mount-observe" interface
+         needs to be plugged, therefore we do this only if not snapped. */
+      if (!aa_is_enabled())
+      {
+       cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: No AppArmor in use");
+       goto snap_check_done;
+      }
+
+#  endif /* !HAVE_SNAPCTL_IS_CONNECTED */
+
+      if (aa_getpeercon(peerfd, &context, NULL) < 0)
+      {
+       cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: AppArmor profile could not be retrieved for client process - Error: %s", strerror(errno));
+       goto snap_check_done;
+      } else
+       cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: AppArmor profile of client process: %s", context);
+
+#  ifdef HAVE_SNAPCTL_IS_CONNECTED
+
+     /*
+      * Run
+      *
+      * snapctl is-connected --apparmor-label=AA_CONTEXT CUPS_CONTROL_SLOT
+      *
+      * with AA_CONTEXTbeing the AppArmor profile name of the client, or
+      * "unconfined" for an unconfined client and CUPS_CONTROL_SLOT
+      * the name of the slot of the CUPS Snap to which clients plug with
+      * their cups-control plug in order to do administrative CUPS tasks.
+      *
+      * The exit status of the command tells which type of client we have
+      * to do with:
+      *
+      *    0: The client is a confined Snap and plugs cups-control
+      *           -> Grant access
+      *    1: The client is a confined Snap and does not plug cups-control
+      *           -> Deny access
+      *   10: The client is a classic Snap
+      *           -> Grant access
+      *   11: The client is not a Snap
+      *           -> Grant access
+      *
+      * NOTE: This method only works if cupsd is running in a Snap providing
+      *       a slot for the client's "cups-control" plug. Do not build CUPS
+      *       with this method when intending to use it unsnapped, for example
+      *       in a Debian or RPM package, or directly installed into your
+      *       system. The errors of snapctl missing or running without a Snap
+      *       context will deny all administrative accesses!
+      *
+      * When running inside a Snap this method is preferred, as it does not
+      * require full access to the snapd under which cupsd is running.
+      */
+
+      /* snapctl command line */
+      snprintf(buf, sizeof(buf), "--apparmor-label=%s", context);
+      argv[0] = SNAPCTL;
+      argv[1] = "is-connected";
+      argv[2] = buf;
+      argv[3] = CUPS_CONTROL_SLOT;
+      argv[4] = NULL;
+
+      /* Create a pipe to catch stderr output from snapctl */
+      if (pipe(fds))
+      {
+       fds[0] = -1;
+       fds[1] = -1;
+       cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: Unable to establish stderr pipe for snapctl call - %s", strerror(errno));
+       ret = 0;
+       goto snap_check_done;
+      }
+
+      /* Set the "close on exec" flag on each end of the pipe... */
+      if (fcntl(fds[0], F_SETFD, fcntl(fds[0], F_GETFD) | FD_CLOEXEC))
+      {
+       close(fds[0]);
+       close(fds[1]);
+       fds[0] = -1;
+       fds[1] = -1;
+       cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: Unable to set \"close on exec\" flag on read end of the stderr pipe for snapctl call - %s", strerror(errno));
+       ret = 0;
+       goto snap_check_done;
+      }
+      if (fcntl(fds[1], F_SETFD, fcntl(fds[1], F_GETFD) | FD_CLOEXEC))
+      {
+       close(fds[0]);
+       close(fds[1]);
+       cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: Unable to set \"close on exec\" flag on write end of the stderr pipe for snapctl call - %s", strerror(errno));
+       ret = 0;
+       goto snap_check_done;
+      }
+
+      if ((pid = fork()) == 0)
+      {
+       /* Couple pipe with stderr of Ghostscript process */
+       if (fds[1] >= 0) {
+         if (fds[1] != 2) {
+           if (dup2(fds[1], 2) < 0) {
+             cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: Unable to couple pipe with stderr of snapctl process - %s", strerror(errno));
+             exit(100);
+           }
+           close(fds[1]);
+         }
+         close(fds[0]);
+       } else {
+         cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: Invalid pipe file descriptor to couple with stderr of snapctl process - %s", strerror(errno));
+         exit(100);
+       }
+
+       /* Send snapctl's stdout to the Nirwana, as snapctl is supposed to
+          not output anything here */
+       if ((nullfd = open("/dev/null", O_RDWR)) > 2)
+       {
+         dup2(nullfd, 1);
+         close(nullfd);
+       }
+       else
+         close(nullfd);
+       fcntl(1, F_SETFL, O_NDELAY);
+
+       /* Execute snapctl command line ... */
+       cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Running command: " SNAPCTL " is-connected %s " CUPS_CONTROL_SLOT, buf);
+       execv(SNAPCTL, argv);
+       cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: Unable to launch snapctl: %s", strerror(errno));
+       exit(100);
+      }
+      else if (pid < 0)
+      {
+       cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: Unable to fork for snapctl call - %s", strerror(errno));
+       ret = 0;
+       goto snap_check_done;
+      }
+      cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Started snapctl (PID %d)", pid);
+
+      close(fds[1]);
+
+      /* Read out stderr from snapctl */
+      buf[0] = '\0';
+      snapctl_stderr = cupsFileOpenFd(fds[0], "r");
+      if (snapctl_stderr)
+      {
+       while (cupsFileGets(snapctl_stderr, buf, sizeof(buf)) && buf[0])
+       {
+         cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: Error message from snapctl: %s", buf);
+         ret = 0;
+       }
+       cupsFileClose(snapctl_stderr);
+      }
+      close(fds[0]);
+
+      /* Wait for snapctl to finish */
+    retry_wait:
+      if (waitpid (pid, &wstatus, 0) == -1)
+      {
+       if (errno == EINTR)
+         goto retry_wait;
+       cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: snapctl (PID %d) stopped with an error - %s", pid, strerror(errno));
+       ret = 0;
+       goto snap_check_done;
+      }
+      if (ret == 0)
+       goto snap_check_done;
+      cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: snapctl (PID %d) exited with no errors.", pid);
+
+      /* How did snapctl terminate */
+      if (WIFEXITED(wstatus))
+      {
+       /* Via regular exit */
+       status = WEXITSTATUS(wstatus);
+       if (status == 0)
+         cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap connecting via \"cups-control\" interface, access granted");
+       else if (status == 1)
+       {
+         cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap does not connect via \"cups-control\" interface, permission denied");
+         ret = 0;
+       }
+       else if (status == 10)
+         cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap under classic confinement, access granted");
+       else if (status == 11)
+         cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client is not a Snap, access granted");
+       else if (status == 100)
+       {
+         cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: snapctl not executed");
+         ret = 0;
+       }
+       else
+       {
+         cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: snapctl exited with unknown status: %d", status);
+         ret = 0;
+       }
+      }
+      else if (WIFSIGNALED(wstatus))
+      {
+       /* Via signal */
+       cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: snapctl caught the signal %d", WTERMSIG(wstatus));
+       ret = 0;
+      }
+
+    snap_check_done:
+      if (context)
+       free(context);
+
+#  else
+#    ifdef HAVE_SNAPDGLIB
+
+     /*
+      * If the client is a Snap, extract the client Snap's name from
+      * the AppArmor context and the query snapd to find out about the
+      * Snap's confinement type and whether it plugs
+      * cups-control. Grant access if
+      *
+      *   - the client is not a Snap
+      *   - the client is a classic Snap
+      *   - the client is a confined Snap plugging "cups-control"
+      *
+      * We deny access if
+      *
+      *   - the client is a confined Snap not plugging "cups-control"
+      *   - an error occurs during the steps of this method
+      *
+      * NOTE: This method is only for use of cupsd when it is not
+      *       packaged in a Snap. In a Snap one would need to plug the
+      *       snapd-control interface, which gives full control on
+      *       snapd, a high security risk. Therefore one will not get
+      *       automatic connection of this interface granted in the
+      *       Snap Store. This is the reason why the snapctl method
+      *       (above) got created by the snapd developers.
+      *
+      * This is the preferred method to run CUPS unsnapped, as is the
+      * only way to check Snap status on clients from an unsnapped
+      * cupsd.
+      */
+
+      /* If the AppArmor context does not begin with "snap.", then this
+         is not a snap */
+      if (strncmp(context, "snap.", 5) != 0)
+      {
+       cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: AppArmor context not from a Snap");
+        goto snap_check_done;
+      }
+
+      dot = strchr(context + 5, '.');
+      if (dot == NULL)
+      {
+        cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Malformed snapd AppArmor profile name: %s", context);
+        goto snap_check_done;
+      }
+      snap_name = strndup(context + 5, (size_t)(dot - context - 5));
+      cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client is the Snap %s", snap_name);
+
+      /* Connect to snapd */
+      snapd = snapd_client_new();
+      if (!snapd)
+      {
+       cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Could not connect to snapd, permission denied");
+       ret = 0;
+       goto snap_check_done;
+      }
+
+      /* Check whether the client Snap is under classic confinement */
+      snap = snapd_client_get_snap_sync(snapd, snap_name, NULL, &error);
+      if (!snap)
+      {
+        cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Could not obtain client Snap data: \"%s\", permission denied", error->message);
+       ret = 0;
+       goto snap_check_done;
+      }
+
+      /* Snaps using classic confinement are granted access */
+      if (snapd_snap_get_confinement(snap) == SNAPD_CONFINEMENT_CLASSIC)
+      {
+        cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap under classic confinement, access granted");
+        goto snap_check_done;
+      }
+
+      /* Get list of interfaces to which the client Snap is plugging */
+      if (!snapd_client_get_connections2_sync(snapd, SNAPD_GET_CONNECTIONS_FLAGS_NONE, snap_name, "cups-control", NULL, NULL, &plugs, NULL, NULL, &error))
+      {
+        cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Could not obtain the client Snap's interface connections: \"%s\", permission denied", error->message);
+       ret = 0;
+       goto snap_check_done;
+      }
+
+      if (plugs->len <= 0)
+      {
+       cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap does not connect via \"cups-control\" interface, permission denied");
+       ret = 0;
+       goto snap_check_done;
+      }
+
+      cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap connecting via \"cups-control\" interface, access granted");
+
+    snap_check_done:
+      if (context)
+       free(context);
+      if (snap_name)
+       free(snap_name);
+      if (snapd)
+       g_clear_object(&snapd);
+      if (snap)
+       g_clear_object(&snap);
+      if (plugs)
+       g_clear_object(&plugs);
+
+#    else
+
+     /*
+      * Issue warning if requirements for building Snap-related access control
+      * not fulfilled
+      */
+
+      cupsdLogMessage(CUPSD_LOG_WARN, "cupsdCheckAdminTask: Compiling problem: Neither libsnapd-glib nor \"snapctl is-connected\" available, no Snap-related access control built!");
+
+    snap_check_done:
+      if (context)
+       free(context);
+
+#    endif /* HAVE_SNAPDGLIB */
+#  endif /* HAVE_SNAPCTL_IS_CONNECTED */
+
+    }
+  }
+
+#endif /* AF_LOCAL && BUILD_SNAP */
+
+  cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Access %s", ret == 1 ? "granted" : "denied");
+
+  return ret;
+}
+
+
 /*
  * 'cupsdIsAuthorized()' - Check to see if the user is authorized...
  */
@@ -1714,12 +2111,9 @@ cupsdIsAuthorized(cupsd_client_t *con,   /* I - Connection */
 
  /*
   * OK, got a username.  See if we need normal user access, or group
-  * access... (root always matches)
+  * access...
   */
 
-  if (!strcmp(username, "root"))
-    return (HTTP_OK);
-
  /*
   * Strip any @domain or @KDC from the username and owner...
   */
@@ -1749,6 +2143,21 @@ cupsdIsAuthorized(cupsd_client_t *con,   /* I - Connection */
   else
     pw = NULL;
 
+ /*
+  * For matching user and group memberships below we will first go
+  * through all names except @SYSTEM to authorize the task as
+  * non-administrative, like printing or deleting one's own job, if this
+  * fails we will check whether we can authorize via the special name
+  * @SYSTEM, as an administrative task, like creating a print queue or
+  * deleting someone else's job.
+  * Note that tasks are considered as administrative by the policies
+  * in cupsd.conf, when they require the user or group @SYSTEM.
+  * We do this separation because if the client is a Snap connecting via
+  * domain socket, we need to additionally check whether it plugs to us
+  * through the "cups-control" interface which allows administration and
+  * not through the "cups" interface which allows only printing.
+  */
+
   if (best->level == CUPSD_AUTH_USER)
   {
    /*
@@ -1779,8 +2188,15 @@ cupsdIsAuthorized(cupsd_client_t *con,   /* I - Connection */
       {
        if (!_cups_strncasecmp(name, "@AUTHKEY(", 9) && check_authref(con, name + 9))
          return (HTTP_OK);
-       else if (!_cups_strcasecmp(name, "@SYSTEM") && SystemGroupAuthKey &&
-                check_authref(con, SystemGroupAuthKey))
+      }
+
+      for (name = (char *)cupsArrayFirst(best->names);
+           name;
+          name = (char *)cupsArrayNext(best->names))
+      {
+       if (!_cups_strcasecmp(name, "@SYSTEM") && SystemGroupAuthKey &&
+           check_authref(con, SystemGroupAuthKey) &&
+           cupsdCheckAdminTask(con))
          return (HTTP_OK);
       }
 
@@ -1797,9 +2213,8 @@ cupsdIsAuthorized(cupsd_client_t *con,    /* I - Connection */
        return (HTTP_OK);
       else if (!_cups_strcasecmp(name, "@SYSTEM"))
       {
-        for (i = 0; i < NumSystemGroups; i ++)
-         if (cupsdCheckGroup(username, pw, SystemGroups[i]))
-           return (HTTP_OK);
+       /* Do @SYSTEM later, when every other entry fails */
+       continue;
       }
       else if (name[0] == '@')
       {
@@ -1810,6 +2225,19 @@ cupsdIsAuthorized(cupsd_client_t *con,   /* I - Connection */
         return (HTTP_OK);
     }
 
+    for (name = (char *)cupsArrayFirst(best->names);
+        name;
+        name = (char *)cupsArrayNext(best->names))
+    {
+      if (!_cups_strcasecmp(name, "@SYSTEM"))
+      {
+        for (i = 0; i < NumSystemGroups; i ++)
+         if (cupsdCheckGroup(username, pw, SystemGroups[i]) &&
+             cupsdCheckAdminTask(con))
+           return (HTTP_OK);
+      }
+    }
+
     return (con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED);
   }
 
@@ -1827,16 +2255,31 @@ cupsdIsAuthorized(cupsd_client_t *con,  /* I - Connection */
        name;
        name = (char *)cupsArrayNext(best->names))
   {
+    if (!_cups_strcasecmp(name, "@SYSTEM"))
+    {
+      /* Do @SYSTEM later, when every other entry fails */
+      continue;
+    }
+
     cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: Checking group \"%s\" membership...", name);
 
+    if (cupsdCheckGroup(username, pw, name))
+      return (HTTP_OK);
+  }
+
+  for (name = (char *)cupsArrayFirst(best->names);
+       name;
+       name = (char *)cupsArrayNext(best->names))
+  {
     if (!_cups_strcasecmp(name, "@SYSTEM"))
     {
+      cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: Checking group \"%s\" membership...", name);
+
       for (i = 0; i < NumSystemGroups; i ++)
-       if (cupsdCheckGroup(username, pw, SystemGroups[i]))
+       if (cupsdCheckGroup(username, pw, SystemGroups[i]) &&
+           cupsdCheckAdminTask(con))
          return (HTTP_OK);
     }
-    else if (cupsdCheckGroup(username, pw, name))
-      return (HTTP_OK);
   }
 
  /*