----------------------
- Added support for CUPS running in a snapcraft snap.
+- Added extra check for administrative inquiries from snapped clients.
- 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
dnl
-dnl Support for packaging CUPS in a Snap.
+dnl Support for packaging CUPS in a Snap and have it work with client Snaps.
dnl
-dnl Copyright © 2020 by Till Kamppeter
-dnl Copyright © 2007-2019 by Apple Inc.
+dnl Copyright © 2021 by Till Kamppeter
+dnl Copyright © 2021 by OpenPrinting
dnl
dnl Licensed under Apache License v2.0. See the file "LICENSE" for more
dnl information.
dnl
-# Snap packaging support
+# Snap packaging and Snap interaction 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])
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)
+ AC_DEFINE(SUPPORT_SNAPPED_CUPSD)
+ AC_DEFINE(SUPPORT_SNAPPED_CLIENTS)
ENABLE_SNAPPED_CUPSD="YES"
ENABLE_SNAPPED_CLIENTS="YES"
else
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)
+ AC_DEFINE(SUPPORT_SNAPPED_CLIENTS)
ENABLE_SNAPPED_CLIENTS="YES"
else
AC_MSG_RESULT(no)
AC_MSG_CHECKING(for Snap support)
if test "x$ENABLE_SNAPPED_CLIENTS" != "xNO"; then
- AC_MSG_RESULT(yes)
+ if test "x$ENABLE_SNAPPED_CUPSD" != "xNO"; then
+ AC_MSG_RESULT(yes: cupsd + clients)
+ else
+ AC_MSG_RESULT(yes: clients only)
+ fi
else
AC_MSG_RESULT(no)
fi
#undef HAVE_SNAPCTL_IS_CONNECTED
#undef SNAPCTL
#undef CUPS_CONTROL_SLOT
-#undef BUILD_SNAP
+#undef SUPPORT_SNAPPED_CUPSD
+#undef SUPPORT_SNAPPED_CLIENTS
/*
* Location of macOS localization bundle, if any.
# endif
# define CUPSD_UCRED_UID(c) (c).uid
#endif /* HAVE_SYS_UCRED_H */
-#ifdef BUILD_SNAP
-# include <sys/apparmor.h>
+#ifdef SUPPORT_SNAPPED_CLIENTS
+# ifdef HAVE_APPARMOR
+# include <sys/apparmor.h>
+# endif
# ifdef HAVE_SNAPDGLIB
# include <glib.h>
# include <snapd-glib/snapd-glib.h>
# endif
-#endif /* BUILD_SNAP */
+#endif /* SUPPORT_SNAPPED_CLIENTS */
/*
* occurs in the process of finding this out.
*/
-#if defined(AF_LOCAL) && defined(BUILD_SNAP)
+#if defined(AF_LOCAL) && defined(SUPPORT_SNAPPED_CLIENTS)
/*
* Get the client's file descriptor and from this its AppArmor context
else
{
char *context = NULL; /* AppArmor profile name of client */
-# ifdef HAVE_SNAPCTL_IS_CONNECTED
+# if defined(SUPPORT_SNAPPED_CUPSD) && defined(HAVE_SNAPCTL_IS_CONNECTED)
char buf[1024];
char *argv[5]; /* snapctl command line */
int fds[2], /* Pipe file descriptors for stderr of snapctl */
int status = 65536; /* Status of forked snapctl process */
int wstatus; /* Wait result of forked snapctl process */
# else
-# ifdef HAVE_SNAPDGLIB
+# if !defined(SUPPORT_SNAPPED_CUPSD) && defined(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 */
+# endif /* !SUPPORT_SNAPPED_CUPSD && HAVE_SNAPDGLIB */
+# endif /* SUPPORT_SNAPPED_CUPSD && HAVE_SNAPCTL_IS_CONNECTED */
+
-# ifndef HAVE_SNAPCTL_IS_CONNECTED
+# ifndef SUPPORT_SNAPPED_CUPSD
/* If AppArmor is not enabled, then we can't identify the client */
- /* With cupsd running in a Snaps, the "mount-observe" interface
+ /* With cupsd running in a Snap, the "mount-observe" interface
needs to be plugged, therefore we do this only if not snapped. */
if (!aa_is_enabled())
{
goto snap_check_done;
}
-# endif /* !HAVE_SNAPCTL_IS_CONNECTED */
+# endif /* !SUPPORT_SNAPPED_CUPSD */
if (aa_getpeercon(peerfd, &context, NULL) < 0)
{
} else
cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: AppArmor profile of client process: %s", context);
-# ifdef HAVE_SNAPCTL_IS_CONNECTED
+# if defined(SUPPORT_SNAPPED_CUPSD) && defined(HAVE_SNAPCTL_IS_CONNECTED)
/*
* Run
free(context);
# else
-# ifdef HAVE_SNAPDGLIB
+# if !defined(SUPPORT_SNAPPED_CUPSD) && defined(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 AppArmor context and then 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
* 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
+ * This is the preferred method to run CUPS unsnapped, as this is
+ * the only way to check Snap status on clients from an unsnapped
* cupsd.
*/
if (context)
free(context);
-# endif /* HAVE_SNAPDGLIB */
-# endif /* HAVE_SNAPCTL_IS_CONNECTED */
+# endif /* !SUPPORT_SNAPPED_CUPSD && HAVE_SNAPDGLIB */
+# endif /* SUPPORT_SNAPPED_CUPSD && HAVE_SNAPCTL_IS_CONNECTED */
}
}
-#endif /* AF_LOCAL && BUILD_SNAP */
-
cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Access %s", ret == 1 ? "granted" : "denied");
+#else
+
+ cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Access granted (no extra checking)");
+
+#endif /* AF_LOCAL && SUPPORT_SNAPPED_CLIENTS */
+
return ret;
}
if (pid == 0)
{
-#ifdef HAVE_ACL_INIT
+#if defined(HAVE_ACL_INIT) && !defined(SUPPORT_SNAPPED_CUPSD)
acl_t acl; /* ACL information */
acl_entry_t entry; /* ACL entry */
acl_permset_t permset; /* Permissions */
# endif /* HAVE_MBR_UID_TO_UUID */
static int acls_not_supported = 0;
/* Only warn once */
-#endif /* HAVE_ACL_INIT */
+#endif /* HAVE_ACL_INIT && !SUPPORT_SNAPPED_CUPSD */
/*
*/
fchmod(fd, 0440);
+
+ /* ACLs do not work when cupsd is running in a Snap, and certificates
+ need root as group owner to be only accessible for CUPS and not the
+ unprivileged sub-processes */
+#ifdef SUPPORT_SNAPPED_CUPSD
+ fchown(fd, RunUser, 0);
+#else
fchown(fd, RunUser, SystemGroupIDs[0]);
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdAddCert: NumSystemGroups=%d", NumSystemGroups);
-#ifdef HAVE_ACL_INIT
+# ifdef HAVE_ACL_INIT
if (NumSystemGroups > 1)
{
/*
int j; /* Looping var */
-# ifdef HAVE_MBR_UID_TO_UUID
+# ifdef HAVE_MBR_UID_TO_UUID
/*
* On macOS, ACLs use UUIDs instead of GIDs...
*/
acl_set_permset(entry, permset);
}
-# else
+# else
/*
* POSIX ACLs need permissions for owner, group, other, and mask
* in addition to the rest of the system groups...
cupsdLogMessage(CUPSD_LOG_ERROR, "ACL: %s", text);
acl_free(text);
}
-# endif /* HAVE_MBR_UID_TO_UUID */
+# endif /* HAVE_MBR_UID_TO_UUID */
if (acl_set_fd(fd, acl))
{
acl_free(acl);
}
-#endif /* HAVE_ACL_INIT */
+# endif /* HAVE_ACL_INIT */
+#endif /* SUPPORT_SNAPPED_CUPSD */
RootCertTime = time(NULL);
}
Group, 1, 1) < 0 ||
cupsdCheckPermissions(StateDir, NULL, 0755, RunUser,
Group, 1, 1) < 0 ||
+ /* Inside a Snap cupsd is running as root without CAP_DAC_OVERRIDE
+ capability, so certs directory has to be root.root-owned so that
+ cupsd can access but not its unprivileged sub-processes. */
+#ifdef SUPPORT_SNAPPED_CUPSD
+ cupsdCheckPermissions(StateDir, "certs", 0711, RunUser,
+ 0, 1, 1) < 0 ||
+#else
cupsdCheckPermissions(StateDir, "certs", RunUser ? 0711 : 0511, User,
SystemGroupIDs[0], 1, 1) < 0 ||
+#endif /* SUPPORT_SNAPPED_CUPSD */
cupsdCheckPermissions(ServerRoot, NULL, 0755, RunUser,
Group, 1, 0) < 0 ||
cupsdCheckPermissions(ServerRoot, "ppd", 0755, RunUser,
{
int i; /* Looping var */
const char *argv[8], /* Command-line arguments */
+#ifdef SUPPORT_SNAPPED_CUPSD
+ *envp[21], /* Environment variables */
+#else
*envp[17], /* Environment variables */
+#endif /* SUPPORT_SNAPPED_CUPSD */
*temp; /* Temporary string */
char *optstr, /* Filter options */
content_type[1024], /* CONTENT_TYPE */
cups_fontpath[1024], /* CUPS_FONTPATH */
cups_serverbin[1024], /* CUPS_SERVERBIN */
cups_serverroot[1024], /* CUPS_SERVERROOT */
+#ifdef SUPPORT_SNAPPED_CUPSD
+ fontconfig_file[1024], /* FONTCONFIG_FILE */
+ fontconfig_path[1024], /* FONTCONFIG_PATH */
+ fontconfig_sysroot[1024], /* FONTCONFIG_SYSROOT */
+#endif /* SUPPORT_SNAPPED_CUPSD */
final_content_type[1024] = "",
/* FINAL_CONTENT_TYPE */
lang[1024], /* LANG */
+#ifdef SUPPORT_SNAPPED_CUPSD
+ ld_library_path[2048], /* LD_LIBRARY_PATH */
+#endif /* SUPPORT_SNAPPED_CUPSD */
path[1024], /* PATH */
ppd[1024], /* PPD */
printer_info[255], /* PRINTER_INFO env variable */
* Setup the filter environment and command-line...
*/
+ /* If we are running confined in a Snap, also pass on fontconfig-related
+ environment variables and LD_LIBRARY_PATH */
+
optstr = escape_options(num_options, options);
snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s",
ServerBin);
snprintf(cups_serverroot, sizeof(cups_serverroot), "CUPS_SERVERROOT=%s",
ServerRoot);
+#ifdef SUPPORT_SNAPPED_CUPSD
+ snprintf(fontconfig_file, sizeof(fontconfig_file), "FONTCONFIG_FILE=%s",
+ getenv("FONTCONFIG_FILE"));
+ snprintf(fontconfig_path, sizeof(fontconfig_path), "FONTCONFIG_PATH=%s",
+ getenv("FONTCONFIG_PATH"));
+ snprintf(fontconfig_sysroot, sizeof(fontconfig_sysroot),
+ "FONTCONFIG_SYSROOT=%s", getenv("FONTCONFIG_SYSROOT"));
+#endif /* SUPPORT_SNAPPED_CUPSD */
language = cupsLangDefault();
snprintf(lang, sizeof(lang), "LANG=%s.UTF8", language->language);
+#ifdef SUPPORT_SNAPPED_CUPSD
+ snprintf(ld_library_path, sizeof(ld_library_path), "LD_LIBRARY_PATH=%s",
+ getenv("LD_LIBRARY_PATH"));
+#endif /* SUPPORT_SNAPPED_CUPSD */
snprintf(path, sizeof(path), "PATH=%s", Path);
if (ppdfile)
snprintf(ppd, sizeof(ppd), "PPD=%s", ppdfile);
envp[5] = cups_serverroot;
envp[6] = lang;
envp[7] = path;
+#ifdef SUPPORT_SNAPPED_CUPSD
+ envp[8] = ld_library_path;
+ envp[9] = ppd;
+ envp[10] = printer_info;
+ envp[11] = printer_location;
+ envp[12] = printer_name;
+ envp[13] = rip_max_cache;
+ envp[14] = userenv;
+ envp[15] = "CHARSET=utf-8";
+ envp[16] = fontconfig_file;
+ envp[17] = fontconfig_path;
+ envp[18] = fontconfig_sysroot;
+ if (final_content_type[0])
+ {
+ envp[19] = final_content_type;
+ envp[20] = NULL;
+ }
+ else
+ envp[19] = NULL;
+#else
envp[8] = ppd;
envp[9] = printer_info;
envp[10] = printer_location;
}
else
envp[15] = NULL;
+#endif /* SUPPORT_SNAPPED_CUPSD */
for (i = 0; argv[i]; i ++)
fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]);
cupsFileClose(fp);
}
+ /* Set the PATH environment variable for external executables, pass
+ through the PATH from the environment in which cupsd was called
+ if we are running confined in a Snap */
+#ifdef SUPPORT_SNAPPED_CUPSD
+ snprintf(line, sizeof(line), "%s/filter:%s:" CUPS_BINDIR ":" CUPS_SBINDIR ":/bin:/usr/bin", ServerBin, getenv("PATH"));
+#else
snprintf(line, sizeof(line), "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR ":/bin:/usr/bin", ServerBin);
+#endif /* SUPPORT_SNAPPED_CUPSD */
set_string(&Path, line);
return (0);
return;
/*
- * Do not allow dynamic linker variables when running as root...
+ * Do not allow dynamic linker variables when running as root and
+ * not being confined in a Snap...
*/
+#ifndef SUPPORT_SNAPPED_CUPSD
if (!RunUser && (!strncmp(name, "DYLD_", 5) || !strncmp(name, "LD_", 3)))
return;
+#endif /* !SUPPORT_SNAPPED_CUPSD */
/*
* See if this variable has already been defined...
set_if_undefined("LD_LIBRARY_PATH", NULL);
set_if_undefined("LD_PRELOAD", NULL);
set_if_undefined("NLSPATH", NULL);
+ /* Only if cupsd is confined in a Snap we pass the PATH environment
+ variable on for external executables we call */
if (find_env("PATH") < 0)
- cupsdSetEnvf("PATH", "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR
- ":/bin:/usr/bin", ServerBin);
+ {
+#ifdef SUPPORT_SNAPPED_CUPSD
+ char *value;
+ if ((value = getenv("PATH")) != NULL)
+ cupsdSetEnvf("PATH", "%s/filter:%s", ServerBin, value);
+ else
+#endif /* SUPPORT_SNAPPED_CUPSD */
+ cupsdSetEnvf("PATH", "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR
+ ":/bin:/usr/bin", ServerBin);
+ }
set_if_undefined("SERVER_ADMIN", ServerAdmin);
set_if_undefined("SHLIB_PATH", NULL);
set_if_undefined("SOFTWARE", CUPS_MINIMAL);