From 22e4cc0e52fa2abc9ff9d260760c57c8c4b0db41 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 5 Oct 2021 09:57:42 -0400 Subject: [PATCH 1/8] Save work on snap support changes. --- .../{cups-snap.m4 => cups-container.m4} | 8 ++++- config.h.in | 31 ++++++++++--------- configure.ac | 2 +- 3 files changed, 25 insertions(+), 16 deletions(-) rename config-scripts/{cups-snap.m4 => cups-container.m4} (93%) diff --git a/config-scripts/cups-snap.m4 b/config-scripts/cups-container.m4 similarity index 93% rename from config-scripts/cups-snap.m4 rename to config-scripts/cups-container.m4 index cff05f2590..2d865b99c8 100644 --- a/config-scripts/cups-snap.m4 +++ b/config-scripts/cups-container.m4 @@ -1,5 +1,5 @@ dnl -dnl Support for packaging CUPS in a Snap and have it work with client Snaps. +dnl Support for packaging CUPS in different kinds of containers. dnl dnl Copyright © 2021 by OpenPrinting dnl @@ -7,6 +7,12 @@ dnl Licensed under Apache License v2.0. See the file "LICENSE" for more dnl information. dnl +AC_ARG_WITH([container], AS_HELP_STRING([--with-container=...], [configure to use in container ('none', 'snap')])) +AC_ARG_WITH([container_name], AS_HELP_STRING([--with-container-name=...], [specify the container name (default='cups')])) + + + + AC_ARG_ENABLE([snapped_cupsd], AS_HELP_STRING([--enable-snapped-cupsd], [enable support for packaging CUPS in a Snap])) AC_ARG_ENABLE([snapped_clients], AS_HELP_STRING([--enable-snapped-clients], [enable support for CUPS controlling admin access from snapped clients])) AC_ARG_WITH([our-snap-name], AS_HELP_STRING([--with-our-snap-name], [Set name of the Snap we are snapped in, only needed with --enable-snapped-cupsd, default=cups]), [ diff --git a/config.h.in b/config.h.in index 7f91b82a6c..c9879ddba3 100644 --- a/config.h.in +++ b/config.h.in @@ -650,20 +650,6 @@ #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_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC -#undef HAVE_SNAPCTL_IS_CONNECTED -#undef OUR_SNAP_NAME -#undef SNAPCTL -#undef CUPS_CONTROL_SLOT -#undef SUPPORT_SNAPPED_CUPSD -#undef SUPPORT_SNAPPED_CLIENTS /* * Location of macOS localization bundle, if any. @@ -696,4 +682,21 @@ static __inline int _cups_abs(int i) { return (i < 0 ? -i : i); } # endif /* __GNUC__ || __STDC_VERSION__ */ #endif /* !HAVE_ABS && !abs */ + +/* + * Do we have snap container support enabled? + */ + +#undef CUPS_SNAP_NAME +#undef CUPS_SNAP_CONTROL_SLOT + +#undef HAVE_APPARMOR +#undef HAVE_SNAPDGLIB +#undef HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC +#undef HAVE_SNAPCTL_IS_CONNECTED +#undef SNAPCTL +#undef SUPPORT_SNAPPED_CUPSD +#undef SUPPORT_SNAPPED_CLIENTS + + #endif /* !_CUPS_CONFIG_H_ */ diff --git a/configure.ac b/configure.ac index 62aa6642e2..4c3c6fa3b9 100644 --- a/configure.ac +++ b/configure.ac @@ -36,7 +36,7 @@ sinclude(config-scripts/cups-tls.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-container.m4) sinclude(config-scripts/cups-startup.m4) sinclude(config-scripts/cups-defaults.m4) From 071c52772e420aae8ecb01e88651281d80c138e9 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 5 Oct 2021 10:45:47 -0400 Subject: [PATCH 2/8] Add support for CORS/HSTS/OSCORE/Interative Client security headers (Issue #266) All this does is add HTTP_FIELD_xxx enumerations and the corresponding strings to allow libcups implementations of HTTP clients/servers that send/receive them. For the subject bug we'll still need to do a bunch of work in cupsd (later). --- cups/http.c | 14 +++++++++++++- cups/http.h | 21 ++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/cups/http.c b/cups/http.c index 15881aed0b..2b5acae145 100644 --- a/cups/http.c +++ b/cups/http.c @@ -107,7 +107,19 @@ static const char * const http_fields[] = "Accept-Encoding", "Allow", "Server", - "Authentication-Info" + "Authentication-Info", + "Access-Control-Allow-Credentials", + "Access-Control-Allow-Headers", + "Access-Control-Allow-Methods", + "Access-Control-Allow-Origin", + "Access-Control-Expose-Headers", + "Access-Control-Max-Age", + "Access-Control-Request-Headers", + "Access-Control-Request-Method", + "Optional-WWW-Authenticate", + "Origin", + "OSCORE", + "Strict-Transport-Security" }; diff --git a/cups/http.h b/cups/http.h index 57dd2ebcfb..6c45301e5e 100644 --- a/cups/http.h +++ b/cups/http.h @@ -176,7 +176,26 @@ typedef enum http_field_e /**** HTTP field names ****/ HTTP_FIELD_ACCEPT_ENCODING, /* Accepting-Encoding field @since CUPS 1.7/macOS 10.9@ */ HTTP_FIELD_ALLOW, /* Allow field @since CUPS 1.7/macOS 10.9@ */ HTTP_FIELD_SERVER, /* Server field @since CUPS 1.7/macOS 10.9@ */ - HTTP_FIELD_AUTHENTICATION_INFO, /* Authentication-Info field (@since CUPS 2.2.9) */ + HTTP_FIELD_AUTHENTICATION_INFO, /* Authentication-Info field @since CUPS 2.2.9@ */ + HTTP_FIELD_ACCESS_CONTROL_ALLOW_CREDENTIALS, + /* CORS/Fetch Access-Control-Allow-Cresdentials field @since CUPS 2.4@ */ + HTTP_FIELD_ACCESS_CONTROL_ALLOW_HEADERS, + /* CORS/Fetch Access-Control-Allow-Headers field @since CUPS 2.4@ */ + HTTP_FIELD_ACCESS_CONTROL_ALLOW_METHODS, + /* CORS/Fetch Access-Control-Allow-Methods field @since CUPS 2.4@ */ + HTTP_FIELD_ACCESS_CONTROL_ALLOW_ORIGIN, + /* CORS/Fetch Access-Control-Allow-Origin field @since CUPS 2.4@ */ + HTTP_FIELD_ACCESS_CONTROL_EXPOSE_HEADERS, + /* CORS/Fetch Access-Control-Expose-Headers field @since CUPS 2.4@ */ + HTTP_FIELD_ACCESS_CONTROL_MAX_AGE, /* CORS/Fetch Access-Control-Max-Age field @since CUPS 2.4@ */ + HTTP_FIELD_ACCESS_CONTROL_REQUEST_HEADERS, + /* CORS/Fetch Access-Control-Request-Headers field @since CUPS 2.4@ */ + HTTP_FIELD_ACCESS_CONTROL_REQUEST_METHOD, + /* CORS/Fetch Access-Control-Request-Method field @since CUPS 2.4@ */ + HTTP_FIELD_OPTIONAL_WWW_AUTHENTICATE, /* RFC 8053 Optional-WWW-Authenticate field @since CUPS 2.4@ */ + HTTP_FIELD_ORIGIN, /* RFC 6454 Origin field @since CUPS 2.4@ */ + HTTP_FIELD_OSCORE, /* RFC 8613 OSCORE field @since CUPS 2.4@ */ + HTTP_FIELD_STRICT_TRANSPORT_SECURITY, /* HSTS Strict-Transport-Security field @since CUPS 2.4@ */ HTTP_FIELD_MAX /* Maximum field index */ } http_field_t; From ce7841733a3c56154966d89b2f72b35616b1777a Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 5 Oct 2021 16:50:39 -0400 Subject: [PATCH 3/8] Fix some clang-reported issues. --- backend/ipp.c | 6 +- cups/dest.c | 2 +- notifier/rss.c | 10 + ppdc/ppdc-import.cxx | 454 ++++---- ppdc/ppdc-source.cxx | 5 +- ppdc/ppdhtml.cxx | 3 +- ppdc/ppdi.cxx | 1 - ppdc/ppdmerge.cxx | 1 - ppdc/ppdpo.cxx | 1 - scheduler/auth.c | 1462 +++++++++++++------------- scheduler/cupsfilter.c | 1 - scheduler/ipp.c | 2 +- systemv/cupsctl.c | 1 - tools/ippeveps.c | 4 +- tools/ipptool.c | 50 +- xcode/CUPS.xcodeproj/project.pbxproj | 6 +- 16 files changed, 1006 insertions(+), 1003 deletions(-) diff --git a/backend/ipp.c b/backend/ipp.c index 3779c88fa4..6964e4535f 100644 --- a/backend/ipp.c +++ b/backend/ipp.c @@ -1431,7 +1431,7 @@ main(int argc, /* I - Number of command-line args */ */ if (version == 10) - create_job = send_document = 0; + create_job = 0; /* * Start monitoring the printer in the background... @@ -1497,9 +1497,7 @@ main(int argc, /* I - Number of command-line args */ * One or more options are not supported... */ - ipp_attribute_t *attr; /* Unsupported attribute */ - - if ((attr = ippFindAttribute(response, "sides", IPP_TAG_ZERO)) != NULL) + if (ippFindAttribute(response, "sides", IPP_TAG_ZERO)) { /* * The sides value is not supported, revert to one-sided as needed... diff --git a/cups/dest.c b/cups/dest.c index 60a9539307..fa6202c772 100644 --- a/cups/dest.c +++ b/cups/dest.c @@ -274,7 +274,7 @@ cupsAddDest(const char *name, /* I - Destination name */ if (instance && !cupsGetDest(name, NULL, num_dests, *dests)) { // Add destination first... - if ((dest = cups_add_dest(name, NULL, &num_dests, dests)) == NULL) + if (!cups_add_dest(name, NULL, &num_dests, dests)) return (num_dests); } diff --git a/notifier/rss.c b/notifier/rss.c index 516b615458..3efc3f9ece 100644 --- a/notifier/rss.c +++ b/notifier/rss.c @@ -566,7 +566,17 @@ new_message(int sequence_number, /* I - notify-sequence-number */ if ((msg = calloc(1, sizeof(_cups_rss_t))) == NULL) + { +#ifdef __clang_analyzer__ + // These free calls are really unnecessary (a failure here ultimately causes + // an exit, which frees all memory much faster) but it makes Clang happy... + free(subject); + free(text); + free(link_url); +#endif // __clang_analyzer__ + return (NULL); + } msg->sequence_number = sequence_number; msg->subject = subject; diff --git a/ppdc/ppdc-import.cxx b/ppdc/ppdc-import.cxx index b4a8341386..d12d0c244e 100644 --- a/ppdc/ppdc-import.cxx +++ b/ppdc/ppdc-import.cxx @@ -57,274 +57,276 @@ ppdcSource::import_ppd(const char *f) // I - Filename } // See if the driver has already been imported... - if ((driver = find_driver(ppd->pcfilename)) == NULL) + if (find_driver(ppd->pcfilename)) { - // Create a new PPD file... - if ((fp = cupsFileOpen(f, "r")) == NULL) - { - ppdClose(ppd); - return (0); - } - - driver = new ppdcDriver(); - driver->type = PPDC_DRIVER_PS; - - drivers->add(driver); + ppdClose(ppd); + return (1); + } - // Read the initial comments from the PPD file and use them as the - // copyright/license text... - cupsFileGets(fp, line, sizeof(line)); - // Skip *PPD-Adobe-M.m + // Create a new PPD file... + if ((fp = cupsFileOpen(f, "r")) == NULL) + { + ppdClose(ppd); + return (0); + } - while (cupsFileGets(fp, line, sizeof(line))) - if (strncmp(line, "*%", 2)) - break; - else if (strncmp(line, "*%%%% ", 6)) - { - for (ptr = line + 2; isspace(*ptr); ptr ++); + driver = new ppdcDriver(); + driver->type = PPDC_DRIVER_PS; - driver->add_copyright(ptr); - } + drivers->add(driver); - cupsFileClose(fp); + // Read the initial comments from the PPD file and use them as the + // copyright/license text... + cupsFileGets(fp, line, sizeof(line)); + // Skip *PPD-Adobe-M.m - // Then add the stuff from the PPD file... - if (ppd->modelname && ppd->manufacturer && - !_cups_strncasecmp(ppd->modelname, ppd->manufacturer, - strlen(ppd->manufacturer))) + while (cupsFileGets(fp, line, sizeof(line))) + if (strncmp(line, "*%", 2)) + break; + else if (strncmp(line, "*%%%% ", 6)) { - ptr = ppd->modelname + strlen(ppd->manufacturer); + for (ptr = line + 2; isspace(*ptr); ptr ++); - while (isspace(*ptr)) - ptr ++; + driver->add_copyright(ptr); } - else - ptr = ppd->modelname; - - if (ppd->nickname) - driver->add_attr(new ppdcAttr("NickName", NULL, NULL, ppd->nickname)); - - if (ppd->shortnickname) - driver->add_attr(new ppdcAttr("ShortNickName", NULL, NULL, - ppd->shortnickname)); - - driver->manufacturer = new ppdcString(ppd->manufacturer); - driver->model_name = new ppdcString(ptr); - driver->pc_file_name = new ppdcString(ppd->pcfilename); - attr = ppdFindAttr(ppd, "FileVersion", NULL); - driver->version = new ppdcString(attr ? attr->value : NULL); - driver->model_number = ppd->model_number; - driver->manual_copies = ppd->manual_copies; - driver->color_device = ppd->color_device; - driver->throughput = ppd->throughput; - driver->variable_paper_size = ppd->variable_sizes; - driver->max_width = ppd->custom_max[0]; - driver->max_length = ppd->custom_max[1]; - driver->min_width = ppd->custom_min[0]; - driver->min_length = ppd->custom_min[1]; - driver->left_margin = ppd->custom_margins[0]; - driver->bottom_margin = ppd->custom_margins[1]; - driver->right_margin = ppd->custom_margins[2]; - driver->top_margin = ppd->custom_margins[3]; - - for (i = 0; i < ppd->num_filters; i ++) - { - strlcpy(line, ppd->filters[i], sizeof(line)); - for (ptr = line; *ptr; ptr ++) - if (isspace(*ptr & 255)) - break; - *ptr++ = '\0'; + cupsFileClose(fp); + + // Then add the stuff from the PPD file... + if (ppd->modelname && ppd->manufacturer && + !_cups_strncasecmp(ppd->modelname, ppd->manufacturer, + strlen(ppd->manufacturer))) + { + ptr = ppd->modelname + strlen(ppd->manufacturer); - cost = strtol(ptr, &ptr, 10); + while (isspace(*ptr)) + ptr ++; + } + else + ptr = ppd->modelname; + + if (ppd->nickname) + driver->add_attr(new ppdcAttr("NickName", NULL, NULL, ppd->nickname)); + + if (ppd->shortnickname) + driver->add_attr(new ppdcAttr("ShortNickName", NULL, NULL, + ppd->shortnickname)); + + driver->manufacturer = new ppdcString(ppd->manufacturer); + driver->model_name = new ppdcString(ptr); + driver->pc_file_name = new ppdcString(ppd->pcfilename); + attr = ppdFindAttr(ppd, "FileVersion", NULL); + driver->version = new ppdcString(attr ? attr->value : NULL); + driver->model_number = ppd->model_number; + driver->manual_copies = ppd->manual_copies; + driver->color_device = ppd->color_device; + driver->throughput = ppd->throughput; + driver->variable_paper_size = ppd->variable_sizes; + driver->max_width = ppd->custom_max[0]; + driver->max_length = ppd->custom_max[1]; + driver->min_width = ppd->custom_min[0]; + driver->min_length = ppd->custom_min[1]; + driver->left_margin = ppd->custom_margins[0]; + driver->bottom_margin = ppd->custom_margins[1]; + driver->right_margin = ppd->custom_margins[2]; + driver->top_margin = ppd->custom_margins[3]; + + for (i = 0; i < ppd->num_filters; i ++) + { + strlcpy(line, ppd->filters[i], sizeof(line)); - while (isspace(*ptr & 255)) - ptr ++; + for (ptr = line; *ptr; ptr ++) + if (isspace(*ptr & 255)) + break; + *ptr++ = '\0'; - filter = new ppdcFilter(line, ptr, cost); - driver->add_filter(filter); - } + cost = strtol(ptr, &ptr, 10); - attr = ppdFindAttr(ppd, "DefaultFont", NULL); - driver->default_font = new ppdcString(attr ? attr->value : NULL); + while (isspace(*ptr & 255)) + ptr ++; - // Collect media sizes... - ppd_option_t *region_option, // PageRegion option - *size_option; // PageSize option - ppd_choice_t *region_choice, // PageRegion choice - *size_choice; // PageSize choice + filter = new ppdcFilter(line, ptr, cost); + driver->add_filter(filter); + } - region_option = ppdFindOption(ppd, "PageRegion"); - size_option = ppdFindOption(ppd, "PageSize"); + attr = ppdFindAttr(ppd, "DefaultFont", NULL); + driver->default_font = new ppdcString(attr ? attr->value : NULL); - for (i = ppd->num_sizes, size = ppd->sizes; i > 0; i --, size ++) - { - // Don't do custom size here... - if (!_cups_strcasecmp(size->name, "Custom")) - continue; + // Collect media sizes... + ppd_option_t *region_option, // PageRegion option + *size_option; // PageSize option + ppd_choice_t *region_choice, // PageRegion choice + *size_choice; // PageSize choice - // Get the code for the PageSize and PageRegion options... - region_choice = ppdFindChoice(region_option, size->name); - size_choice = ppdFindChoice(size_option, size->name); + region_option = ppdFindOption(ppd, "PageRegion"); + size_option = ppdFindOption(ppd, "PageSize"); - // Create a new media size record and add it to the driver... - csize = new ppdcMediaSize(size->name, size_choice->text, size->width, - size->length, size->left, size->bottom, - size->width - size->right, - size->length - size->top, - size_choice->code, region_choice->code); + for (i = ppd->num_sizes, size = ppd->sizes; i > 0; i --, size ++) + { + // Don't do custom size here... + if (!_cups_strcasecmp(size->name, "Custom")) + continue; - driver->add_size(csize); + // Get the code for the PageSize and PageRegion options... + region_choice = ppdFindChoice(region_option, size->name); + size_choice = ppdFindChoice(size_option, size->name); - if (!_cups_strcasecmp(size_option->defchoice, size->name)) - driver->set_default_size(csize); - } + // Create a new media size record and add it to the driver... + csize = new ppdcMediaSize(size->name, size_choice->text, size->width, + size->length, size->left, size->bottom, + size->width - size->right, + size->length - size->top, + size_choice->code, region_choice->code); - // Now all of the options... - for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++) - { - cgroup = new ppdcGroup(group->name, group->text); - driver->add_group(cgroup); + driver->add_size(csize); - for (j = group->num_options, option = group->options; j > 0; j --, option ++) - { - if (!strcmp(option->keyword, "PageSize") || !strcmp(option->keyword, "PageRegion")) - continue; + if (!_cups_strcasecmp(size_option->defchoice, size->name)) + driver->set_default_size(csize); + } - coption = new ppdcOption((ppdcOptType)option->ui, option->keyword, - option->text, (ppdcOptSection)option->section, - option->order); - cgroup->add_option(coption); + // Now all of the options... + for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++) + { + cgroup = new ppdcGroup(group->name, group->text); + driver->add_group(cgroup); - for (k = option->num_choices, choice = option->choices; k > 0; k --, choice ++) - { - if (!strcmp(choice->choice, "Custom")) - continue; + for (j = group->num_options, option = group->options; j > 0; j --, option ++) + { + if (!strcmp(option->keyword, "PageSize") || !strcmp(option->keyword, "PageRegion")) + continue; - cchoice = new ppdcChoice(choice->choice, choice->text, choice->code); - coption->add_choice(cchoice); + coption = new ppdcOption((ppdcOptType)option->ui, option->keyword, + option->text, (ppdcOptSection)option->section, + option->order); + cgroup->add_option(coption); - if (!_cups_strcasecmp(option->defchoice, choice->choice)) - coption->set_defchoice(cchoice); - } + for (k = option->num_choices, choice = option->choices; k > 0; k --, choice ++) + { + if (!strcmp(choice->choice, "Custom")) + continue; + + cchoice = new ppdcChoice(choice->choice, choice->text, choice->code); + coption->add_choice(cchoice); + + if (!_cups_strcasecmp(option->defchoice, choice->choice)) + coption->set_defchoice(cchoice); } } + } - // Now the constraints... - for (i = ppd->num_consts, constraint = ppd->consts; - i > 0; - i --, constraint ++) - { - // Look for mirrored constraints... - for (j = i - 1, constraint2 = constraint + 1; - j > 0; - j --, constraint2 ++) - if (!strcmp(constraint->option1, constraint2->option2) && - !strcmp(constraint->choice1, constraint2->choice2) && - !strcmp(constraint->option2, constraint2->option1) && - !strcmp(constraint->choice2, constraint2->choice1)) - break; - - if (j) - continue; - - cconstraint = new ppdcConstraint(constraint->option2, constraint->choice2, - constraint->option1, constraint->choice1); - driver->add_constraint(cconstraint); - } + // Now the constraints... + for (i = ppd->num_consts, constraint = ppd->consts; + i > 0; + i --, constraint ++) + { + // Look for mirrored constraints... + for (j = i - 1, constraint2 = constraint + 1; + j > 0; + j --, constraint2 ++) + if (!strcmp(constraint->option1, constraint2->option2) && + !strcmp(constraint->choice1, constraint2->choice2) && + !strcmp(constraint->option2, constraint2->option1) && + !strcmp(constraint->choice2, constraint2->choice1)) + break; + + if (j) + continue; + + cconstraint = new ppdcConstraint(constraint->option2, constraint->choice2, + constraint->option1, constraint->choice1); + driver->add_constraint(cconstraint); + } + + for (i = 0; i < ppd->num_attrs; i ++) + { + attr = ppd->attrs[i]; - for (i = 0; i < ppd->num_attrs; i ++) + if (!strcmp(attr->name, "Font")) { - attr = ppd->attrs[i]; + // Font... + char encoding[256], // Encoding string + version[256], // Version string + charset[256], // Charset string + status[256]; // Status string + ppdcFontStatus fstatus; // Status enumeration + - if (!strcmp(attr->name, "Font")) + if (sscanf(attr->value, "%s%*[^\"]\"%[^\"]\"%s%s", encoding, version, + charset, status) != 4) { - // Font... - char encoding[256], // Encoding string - version[256], // Version string - charset[256], // Charset string - status[256]; // Status string - ppdcFontStatus fstatus; // Status enumeration - - - if (sscanf(attr->value, "%s%*[^\"]\"%[^\"]\"%s%s", encoding, version, - charset, status) != 4) - { - _cupsLangPrintf(stderr, _("ppdc: Bad font attribute: %s"), - attr->value); - continue; - } + _cupsLangPrintf(stderr, _("ppdc: Bad font attribute: %s"), + attr->value); + continue; + } - if (!strcmp(status, "ROM")) - fstatus = PPDC_FONT_ROM; - else - fstatus = PPDC_FONT_DISK; + if (!strcmp(status, "ROM")) + fstatus = PPDC_FONT_ROM; + else + fstatus = PPDC_FONT_DISK; - font = new ppdcFont(attr->spec, encoding, version, charset, fstatus); + font = new ppdcFont(attr->spec, encoding, version, charset, fstatus); - driver->add_font(font); - } - else if (!strcmp(attr->name, "CustomPageSize")) - { - driver->set_custom_size_code(attr->value); - } - else if ((strncmp(attr->name, "Default", 7) || - !strcmp(attr->name, "DefaultColorSpace")) && - strcmp(attr->name, "ColorDevice") && - strcmp(attr->name, "Manufacturer") && - strcmp(attr->name, "ModelName") && - strcmp(attr->name, "MaxMediaHeight") && - strcmp(attr->name, "MaxMediaWidth") && - strcmp(attr->name, "NickName") && - strcmp(attr->name, "ParamCustomPageSize") && - strcmp(attr->name, "ShortNickName") && - strcmp(attr->name, "Throughput") && - strcmp(attr->name, "PCFileName") && - strcmp(attr->name, "FileVersion") && - strcmp(attr->name, "FormatVersion") && - strcmp(attr->name, "HWMargins") && - strcmp(attr->name, "VariablePaperSize") && - strcmp(attr->name, "LanguageEncoding") && - strcmp(attr->name, "LanguageVersion") && - strcmp(attr->name, "cupsFilter") && - strcmp(attr->name, "cupsFlipDuplex") && - strcmp(attr->name, "cupsLanguages") && - strcmp(attr->name, "cupsManualCopies") && - strcmp(attr->name, "cupsModelNumber") && - strcmp(attr->name, "cupsVersion")) - { - if ((ptr = strchr(attr->name, '.')) != NULL && - ((ptr - attr->name) == 2 || (ptr - attr->name) == 5)) - { - // Might be a localization attribute; test further... - if (isalpha(attr->name[0] & 255) && - isalpha(attr->name[1] & 255) && - (attr->name[2] == '.' || - (attr->name[2] == '_' && isalpha(attr->name[3] & 255) && - isalpha(attr->name[4] & 255)))) - continue; - } - - // Attribute... - driver->add_attr(new ppdcAttr(attr->name, attr->spec, attr->text, - attr->value)); - } - else if (!strncmp(attr->name, "Default", 7) && - !ppdFindOption(ppd, attr->name + 7) && - strcmp(attr->name, "DefaultFont") && - strcmp(attr->name, "DefaultImageableArea") && - strcmp(attr->name, "DefaultPaperDimension") && - strcmp(attr->name, "DefaultFont")) + driver->add_font(font); + } + else if (!strcmp(attr->name, "CustomPageSize")) + { + driver->set_custom_size_code(attr->value); + } + else if ((strncmp(attr->name, "Default", 7) || + !strcmp(attr->name, "DefaultColorSpace")) && + strcmp(attr->name, "ColorDevice") && + strcmp(attr->name, "Manufacturer") && + strcmp(attr->name, "ModelName") && + strcmp(attr->name, "MaxMediaHeight") && + strcmp(attr->name, "MaxMediaWidth") && + strcmp(attr->name, "NickName") && + strcmp(attr->name, "ParamCustomPageSize") && + strcmp(attr->name, "ShortNickName") && + strcmp(attr->name, "Throughput") && + strcmp(attr->name, "PCFileName") && + strcmp(attr->name, "FileVersion") && + strcmp(attr->name, "FormatVersion") && + strcmp(attr->name, "HWMargins") && + strcmp(attr->name, "VariablePaperSize") && + strcmp(attr->name, "LanguageEncoding") && + strcmp(attr->name, "LanguageVersion") && + strcmp(attr->name, "cupsFilter") && + strcmp(attr->name, "cupsFlipDuplex") && + strcmp(attr->name, "cupsLanguages") && + strcmp(attr->name, "cupsManualCopies") && + strcmp(attr->name, "cupsModelNumber") && + strcmp(attr->name, "cupsVersion")) + { + if ((ptr = strchr(attr->name, '.')) != NULL && + ((ptr - attr->name) == 2 || (ptr - attr->name) == 5)) { - // Default attribute... - driver->add_attr(new ppdcAttr(attr->name, attr->spec, attr->text, - attr->value)); + // Might be a localization attribute; test further... + if (isalpha(attr->name[0] & 255) && + isalpha(attr->name[1] & 255) && + (attr->name[2] == '.' || + (attr->name[2] == '_' && isalpha(attr->name[3] & 255) && + isalpha(attr->name[4] & 255)))) + continue; } + + // Attribute... + driver->add_attr(new ppdcAttr(attr->name, attr->spec, attr->text, + attr->value)); + } + else if (!strncmp(attr->name, "Default", 7) && + !ppdFindOption(ppd, attr->name + 7) && + strcmp(attr->name, "DefaultFont") && + strcmp(attr->name, "DefaultImageableArea") && + strcmp(attr->name, "DefaultPaperDimension") && + strcmp(attr->name, "DefaultFont")) + { + // Default attribute... + driver->add_attr(new ppdcAttr(attr->name, attr->spec, attr->text, + attr->value)); } } - if (ppd) - ppdClose(ppd); + ppdClose(ppd); return (1); } diff --git a/ppdc/ppdc-source.cxx b/ppdc/ppdc-source.cxx index d1e942662d..0a0e40592e 100644 --- a/ppdc/ppdc-source.cxx +++ b/ppdc/ppdc-source.cxx @@ -2792,7 +2792,7 @@ ppdcSource::scan_file(ppdcFile *fp, // I - File to read if (have_cutter <= 0 || cond_state) continue; - if ((o = d->find_option("CutMedia")) == NULL) + if (!d->find_option("CutMedia")) { o = new ppdcOption(PPDC_BOOLEAN, "CutMedia", "Cut Media", PPDC_SECTION_ANY, 10.0f); @@ -2805,9 +2805,8 @@ ppdcSource::scan_file(ppdcFile *fp, // I - File to read c = new ppdcChoice("True", NULL, "<>setpagedevice"); o->add_choice(c); + o = NULL; } - - o = NULL; } else if (!_cups_strcasecmp(temp, "Darkness")) { diff --git a/ppdc/ppdhtml.cxx b/ppdc/ppdhtml.cxx index 75636a9447..cb2a784b2b 100644 --- a/ppdc/ppdhtml.cxx +++ b/ppdc/ppdhtml.cxx @@ -80,7 +80,6 @@ main(int argc, // I - Number of command-line arguments default : // Unknown usage(); - break; } } else @@ -100,7 +99,7 @@ main(int argc, // I - Number of command-line arguments for (g = (ppdcGroup *)d->groups->first(); g; g = (ppdcGroup *)d->groups->next()) for (o = (ppdcOption *)g->options->first(); o; o = (ppdcOption *)g->options->next()) { - if ((compo = composite->find_option(o->name->value)) == NULL) + if (!composite->find_option(o->name->value)) composite->add_option(new ppdcOption(o)); } diff --git a/ppdc/ppdi.cxx b/ppdc/ppdi.cxx index a2f9648f28..f5aa4db520 100644 --- a/ppdc/ppdi.cxx +++ b/ppdc/ppdi.cxx @@ -71,7 +71,6 @@ main(int argc, // I - Number of command-line arguments default : // Unknown usage(); - break; } } else diff --git a/ppdc/ppdmerge.cxx b/ppdc/ppdmerge.cxx index 1ab804399d..cd83964fae 100644 --- a/ppdc/ppdmerge.cxx +++ b/ppdc/ppdmerge.cxx @@ -75,7 +75,6 @@ main(int argc, // I - Number of command-line arguments default : // Unknown usage(); - break; } } else diff --git a/ppdc/ppdpo.cxx b/ppdc/ppdpo.cxx index 498b34e3d7..01f765e442 100644 --- a/ppdc/ppdpo.cxx +++ b/ppdc/ppdpo.cxx @@ -100,7 +100,6 @@ main(int argc, // I - Number of command-line arguments default : // Unknown usage(); - break; } } else diff --git a/scheduler/auth.c b/scheduler/auth.c index 580dcde08e..f29ceb497d 100644 --- a/scheduler/auth.c +++ b/scheduler/auth.c @@ -67,6 +67,7 @@ typedef struct sockpeercred cupsd_ucred_t; * Local functions... */ +static int check_admin_task(cupsd_client_t *con); #ifdef HAVE_AUTHORIZATION_H static int check_authref(cupsd_client_t *con, const char *right); #endif /* HAVE_AUTHORIZATION_H */ @@ -1529,894 +1530,893 @@ cupsdFreeLocation(cupsd_location_t *loc)/* I - Location to free */ /* - * 'cupsdCheckAdminTask()' - Do additional checks on administrative tasks + * 'cupsdIsAuthorized()' - Check to see if the user is authorized... */ -int /* O - 1 if admin task authorized */ -cupsdCheckAdminTask(cupsd_client_t *con) /* I - Connection */ +http_status_t /* O - HTTP_OK if authorized or error code */ +cupsdIsAuthorized(cupsd_client_t *con, /* I - Connection */ + const char *owner)/* I - Owner of object */ { - int ret = 1; /* Return value */ - - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Administrative task"); + int i, /* Looping vars */ + auth, /* Authorization status */ + type; /* Type of authentication */ + http_addr_t *hostaddr = httpGetAddress(con->http); + /* Client address */ + const char *hostname = httpGetHostname(con->http, NULL, 0); + /* Client hostname */ + unsigned address[4]; /* Authorization address */ + cupsd_location_t *best; /* Best match for location so far */ + size_t hostlen; /* Length of hostname */ + char *name, /* Current username */ + username[256], /* Username to authorize */ + ownername[256], /* Owner name to authorize */ + *ptr; /* Pointer into username */ + struct passwd *pw; /* User password data */ + static const char * const levels[] = /* Auth levels */ + { + "ANON", + "USER", + "GROUP" + }; + static const char * const types[] = /* Auth types */ + { + "None", + "Basic", + "Negotiate" + }; - /* - * 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(SUPPORT_SNAPPED_CLIENTS) + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: con->uri=\"%s\", con->best=%p(%s)", con->uri, con->best, con->best ? con->best->location ? con->best->location : "(null)" : ""); + if (owner) + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: owner=\"%s\"", owner); /* - * Get the client's file descriptor and from this its AppArmor context + * If there is no "best" authentication rule for this request, then + * access is allowed from the local system and denied from other + * addresses... */ - if (httpAddrFamily(con->http->hostaddr) == AF_LOCAL) + if (!con->best) { - 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"); - } + if (httpAddrLocalhost(httpGetAddress(con->http)) || + !strcmp(hostname, ServerName) || + cupsArrayFind(ServerAlias, (void *)hostname)) + return (HTTP_OK); else - { - char *context = NULL; /* AppArmor profile name of client */ -# undef CHECK_METHOD_FOUND -# ifdef SUPPORT_SNAPPED_CUPSD - int status = 65535; /* Status of client Snap context check */ -# if defined(HAVE_SNAPDGLIB) && defined(HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC) -# define CHECK_METHOD_FOUND 1 - char *args[] = { "is-connected", "--apparmor-label", NULL, CUPS_CONTROL_SLOT, NULL }; /* snapctl arguments */ - SnapdClient *client = NULL; /* Data structure of snapd access */ - const char *cookie; /* snapd access cookie */ - GError *error = NULL; /* Glib error */ -# else -# ifdef HAVE_SNAPCTL_IS_CONNECTED -# define CHECK_METHOD_FOUND 1 - char *args[] = { SNAPCTL, "is-connected", "--apparmor-label", NULL, CUPS_CONTROL_SLOT, NULL }; /* 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 */ - char buf[1024]; /* Buffer for snapctl's stderr output */ - int wstatus; /* Wait result of forked snapctl process */ -# endif /* HAVE_SNAPCTL_IS_CONNECTED */ -# endif /* HAVE_SNAPDGLIB && HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC */ -# else -# if !defined(SUPPORT_SNAPPED_CUPSD) && defined(HAVE_SNAPDGLIB) -# define CHECK_METHOD_FOUND 1 - char *snap_name = NULL; /* Client Snap name */ - char *dot; /* Pointer to dot in AppArmor profile name */ - SnapdClient *snapd = NULL; /* Data structure of snapd access */ - GError *error = NULL; /* Glib error */ - SnapdSnap *snap = NULL; /* Data structure of client Snap */ - GPtrArray *plugs = NULL; /* Plug search result of client Snap */ -# endif /* !SUPPORT_SNAPPED_CUPSD && HAVE_SNAPDGLIB */ -# endif /* SUPPORT_SNAPPED_CUPSD */ - + return (HTTP_FORBIDDEN); + } -# ifndef SUPPORT_SNAPPED_CUPSD + best = con->best; - /* If AppArmor is not enabled, then we can't identify the client */ - /* 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()) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: No AppArmor in use"); - goto snap_check_done; - } + if ((type = best->type) == CUPSD_AUTH_DEFAULT) + type = cupsdDefaultAuthType(); -# endif /* !SUPPORT_SNAPPED_CUPSD */ + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: level=CUPSD_AUTH_%s, type=%s, satisfy=CUPSD_AUTH_SATISFY_%s, num_names=%d", levels[best->level], types[type], best->satisfy ? "ANY" : "ALL", cupsArrayCount(best->names)); - 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); + if (best->limit == CUPSD_AUTH_LIMIT_IPP) + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: op=%x(%s)", best->op, ippOpString(best->op)); -# ifdef OUR_SNAP_NAME - /* Is the client one of the utilities of our Snap? */ - if (!strncmp(context, "snap." OUR_SNAP_NAME ".", strlen(OUR_SNAP_NAME) + 6)) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap is the same Snap we are running in, access granted"); - goto snap_check_done; - } -# endif /* OUR_SNAP_NAME */ + /* + * Check host/ip-based accesses... + */ -# ifdef SUPPORT_SNAPPED_CUPSD +#ifdef AF_INET6 + if (httpAddrFamily(hostaddr) == AF_INET6) + { + /* + * Copy IPv6 address... + */ - /* - * Run - * - * snapctl is-connected --apparmor-label AA_CONTEXT CUPS_CONTROL_SLOT - * - * or an equivalent library function call using the - * snapd_client_run_snapctl2_sync() function of libsnapd-glib. - * - * Here AA_CONTEXT is 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/function call 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 snapctl/the function - * 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. - */ + address[0] = ntohl(hostaddr->ipv6.sin6_addr.s6_addr32[0]); + address[1] = ntohl(hostaddr->ipv6.sin6_addr.s6_addr32[1]); + address[2] = ntohl(hostaddr->ipv6.sin6_addr.s6_addr32[2]); + address[3] = ntohl(hostaddr->ipv6.sin6_addr.s6_addr32[3]); + } + else +#endif /* AF_INET6 */ + if (con->http->hostaddr->addr.sa_family == AF_INET) + { + /* + * Copy IPv4 address... + */ -# if defined(HAVE_SNAPDGLIB) && defined(HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC) + address[0] = 0; + address[1] = 0; + address[2] = 0; + address[3] = ntohl(hostaddr->ipv4.sin_addr.s_addr); + } + else + memset(address, 0, sizeof(address)); - /* - * Use the snapd_client_run_snapctl2_sync() function of libsnapd-glib - */ + hostlen = strlen(hostname); - /* Insert client Snap context in snapctl arguments */ - args[2] = context; + auth = cupsdCheckAccess(address, hostname, hostlen, best) + ? CUPSD_AUTH_ALLOW : CUPSD_AUTH_DENY; - /* Connect to snapd */ - client = snapd_client_new(); - if (!client) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Could not connect to snapd, permission denied"); - ret = 0; - goto snap_check_done; - } + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: auth=CUPSD_AUTH_%s...", auth ? "DENY" : "ALLOW"); - /* snapctl commands are sent over a this socket that is made - available within the snap sandbox */ - snapd_client_set_socket_path(client, "/run/snapd-snap.socket"); + if (auth == CUPSD_AUTH_DENY && best->satisfy == CUPSD_AUTH_SATISFY_ALL) + return (HTTP_FORBIDDEN); - /* Take cookie from the environment if available */ - cookie = g_getenv("SNAP_COOKIE"); - if (!cookie) - { - cookie = ""; - cupsdLogMessage(CUPSD_LOG_WARN, "cupsdCheckAdminTask: No SNAP_COOKIE set in the Snap environment, client Snap context check may not work"); - } +#ifdef HAVE_TLS + /* + * See if encryption is required... + */ - /* Do the client Snap context check */ - if (!snapd_client_run_snapctl2_sync(client, cookie, args, NULL, NULL, &status, NULL, &error)) { - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap context check error - %s", error->message); - ret = 0; - goto snap_check_done; - } + if ((best->encryption >= HTTP_ENCRYPT_REQUIRED && !con->http->tls && + _cups_strcasecmp(hostname, "localhost") && + !httpAddrLocalhost(hostaddr) && + best->satisfy == CUPSD_AUTH_SATISFY_ALL) && + !(type == CUPSD_AUTH_NEGOTIATE || + (type == CUPSD_AUTH_NONE && + cupsdDefaultAuthType() == CUPSD_AUTH_NEGOTIATE))) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, + "cupsdIsAuthorized: Need upgrade to TLS..."); + return (HTTP_UPGRADE_REQUIRED); + } +#endif /* HAVE_TLS */ -# else -# ifdef HAVE_SNAPCTL_IS_CONNECTED + /* + * Now see what access level is required... + */ - /* - * Call the snapctl executable using execv() in a fork - */ + if (best->level == CUPSD_AUTH_ANON || /* Anonymous access - allow it */ + (type == CUPSD_AUTH_NONE && cupsArrayCount(best->names) == 0)) + return (HTTP_OK); - /* Insert client Snap context in snapctl command line */ - args[3] = context; + if (!con->username[0] && type == CUPSD_AUTH_NONE && + best->limit == CUPSD_AUTH_LIMIT_IPP) + { + /* + * Check for unauthenticated username... + */ - /* 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; - } + ipp_attribute_t *attr; /* requesting-user-name attribute */ - /* 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); - } + attr = ippFindAttribute(con->request, "requesting-user-name", IPP_TAG_NAME); + if (attr) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, + "cupsdIsAuthorized: requesting-user-name=\"%s\"", + attr->values[0].string.text); + strlcpy(username, attr->values[0].string.text, sizeof(username)); + } + else if (best->satisfy == CUPSD_AUTH_SATISFY_ALL || auth == CUPSD_AUTH_DENY) + return (HTTP_UNAUTHORIZED); /* Non-anonymous needs user/pass */ + else + return (HTTP_OK); /* unless overridden with Satisfy */ + } + else + { + cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdIsAuthorized: username=\"%s\"", + con->username); - /* 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); +#ifdef HAVE_AUTHORIZATION_H + if (!con->username[0] && !con->authref) +#else + if (!con->username[0]) +#endif /* HAVE_AUTHORIZATION_H */ + { + if (best->satisfy == CUPSD_AUTH_SATISFY_ALL || auth == CUPSD_AUTH_DENY) + return (HTTP_UNAUTHORIZED); /* Non-anonymous needs user/pass */ + else + return (HTTP_OK); /* unless overridden with Satisfy */ + } - /* Execute snapctl command line ... */ - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Running command: " SNAPCTL " is-connected --apparmor-label %s " CUPS_CONTROL_SLOT, context); - execv(SNAPCTL, args); - 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]); + if (con->type != type && type != CUPSD_AUTH_NONE && +#ifdef HAVE_GSSAPI + (type != CUPSD_AUTH_NEGOTIATE || con->gss_uid <= 0) && +#endif /* HAVE_GSSAPI */ + con->type != CUPSD_AUTH_BASIC) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Authorized using %s, expected %s.", + types[con->type], types[type]); - /* 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]); + return (HTTP_UNAUTHORIZED); + } - /* 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); + strlcpy(username, con->username, sizeof(username)); + } - /* How did snapctl terminate */ - if (WIFEXITED(wstatus)) - { - /* Via regular exit */ - status = WEXITSTATUS(wstatus); - if (status == 100) - { - cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: snapctl not executed"); - ret = 0; - goto snap_check_done; - } - } - else if (WIFSIGNALED(wstatus)) - { - /* Via signal */ - cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: snapctl caught the signal %d", WTERMSIG(wstatus)); - ret = 0; - goto snap_check_done; - } + /* + * OK, got a username. See if we need normal user access, or group + * access... + */ -# endif /* HAVE_SNAPCTL_IS_CONNECTED */ -# endif /* HAVE_SNAPDGLIB && HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC */ + /* + * Strip any @domain or @KDC from the username and owner... + */ - switch (status) - { - case 0 : - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap connecting via \"cups-control\" interface, access granted"); - break; - case 1 : - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap does not connect via \"cups-control\" interface, permission denied"); - ret = 0; - break; - case 10 : - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap under classic confinement, access granted"); - break; - case 11 : - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client is not a Snap, access granted"); - break; - default : - cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdCheckAdminTask: snapctl exited with unknown status: %d", status); - ret = 0; - break; - } + if ((ptr = strchr(username, '@')) != NULL) + *ptr = '\0'; - snap_check_done: - if (context) - free(context); -# if defined(HAVE_SNAPDGLIB) && defined(HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC) - g_clear_object(&client); -# endif /* HAVE_SNAPDGLIB && HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC */ + if (owner) + { + strlcpy(ownername, owner, sizeof(ownername)); -# else -# if !defined(SUPPORT_SNAPPED_CUPSD) && defined(HAVE_SNAPDGLIB) + if ((ptr = strchr(ownername, '@')) != NULL) + *ptr = '\0'; + } + else + ownername[0] = '\0'; - /* - * If the client is a Snap, extract the client Snap's name from - * 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 - * - 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 this is - * the only way to check Snap status on clients from an unsnapped - * cupsd. - */ + /* + * Get the user info... + */ - /* 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; - } + if (username[0]) + { + pw = getpwnam(username); + endpwent(); + } + else + pw = NULL; - 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); + /* + * 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. + */ - /* 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; - } + if (best->level == CUPSD_AUTH_USER) + { + /* + * If there are no names associated with this location, then + * any valid user is OK... + */ - /* Check whether the client Snap is under classic confinement */ - snap = snapd_client_get_snap_sync(snapd, snap_name, NULL, &error); - if (!snap) + if (cupsArrayCount(best->names) == 0) + return (HTTP_OK); + + /* + * Otherwise check the user list and return OK if this user is + * allowed... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: Checking user membership..."); + +#ifdef HAVE_AUTHORIZATION_H + /* + * If an authorization reference was supplied it must match a right name... + */ + + if (con->authref) + { + for (name = (char *)cupsArrayFirst(best->names); + name; + name = (char *)cupsArrayNext(best->names)) { - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Could not obtain client Snap data: \"%s\", permission denied", error->message); - ret = 0; - goto snap_check_done; + if (!_cups_strncasecmp(name, "@AUTHKEY(", 9) && check_authref(con, name + 9)) + return (HTTP_OK); } - /* Snaps using classic confinement are granted access */ - if (snapd_snap_get_confinement(snap) == SNAPD_CONFINEMENT_CLASSIC) + for (name = (char *)cupsArrayFirst(best->names); + name; + name = (char *)cupsArrayNext(best->names)) { - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap under classic confinement, access granted"); - goto snap_check_done; + if (!_cups_strcasecmp(name, "@SYSTEM") && SystemGroupAuthKey && + check_authref(con, SystemGroupAuthKey)) + return (HTTP_OK); } - /* 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)) + return (HTTP_FORBIDDEN); + } +#endif /* HAVE_AUTHORIZATION_H */ + + for (name = (char *)cupsArrayFirst(best->names); + name; + name = (char *)cupsArrayNext(best->names)) + { + if (!_cups_strcasecmp(name, "@OWNER") && owner && + !_cups_strcasecmp(username, ownername)) + return (HTTP_OK); + else if (!_cups_strcasecmp(name, "@SYSTEM")) { - 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; + /* Do @SYSTEM later, when every other entry fails */ + continue; } - - if (plugs->len <= 0) + else if (name[0] == '@') { - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Client Snap does not connect via \"cups-control\" interface, permission denied"); - ret = 0; - goto snap_check_done; + if (cupsdCheckGroup(username, pw, name + 1)) + return (HTTP_OK); } + else if (!_cups_strcasecmp(username, name)) + return (HTTP_OK); + } - 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); - g_clear_object(&snapd); - g_clear_object(&snap); - if (plugs) - g_ptr_array_unref(plugs); + 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]) && check_admin_task(con)) + return (HTTP_OK); + } + } -# endif /* !SUPPORT_SNAPPED_CUPSD && HAVE_SNAPDGLIB */ -# endif /* SUPPORT_SNAPPED_CUPSD */ + return (con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED); + } -# ifndef CHECK_METHOD_FOUND + /* + * Check to see if this user is in any of the named groups... + */ - /* - * Issue warning if requirements for building Snap-related access control - * not fulfilled - */ + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: Checking group membership..."); - cupsdLogMessage(CUPSD_LOG_WARN, "cupsdCheckAdminTask: Compiling problem: none of the three access control methods (libsnapd-glib snapd access, \"snapctl is-connected\", libsnapd-glib-based snapctl call) available, no Snap-related access control built!"); + /* + * Check to see if this user is in any of the named groups... + */ - snap_check_done: - if (context) - free(context); + for (name = (char *)cupsArrayFirst(best->names); + name; + name = (char *)cupsArrayNext(best->names)) + { + if (!_cups_strcasecmp(name, "@SYSTEM")) + { + /* Do @SYSTEM later, when every other entry fails */ + continue; + } -# endif /* !CHECK_METHOD_FOUND */ + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: Checking group \"%s\" membership...", name); - } + if (cupsdCheckGroup(username, pw, name)) + return (HTTP_OK); } - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Access %s", ret == 1 ? "granted" : "denied"); + 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); -#else + for (i = 0; i < NumSystemGroups; i ++) + if (cupsdCheckGroup(username, pw, SystemGroups[i]) && check_admin_task(con)) + return (HTTP_OK); + } + } - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Access granted (no extra checking)"); + /* + * The user isn't part of the specified group, so deny access... + */ -#endif /* AF_LOCAL && SUPPORT_SNAPPED_CLIENTS */ + cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdIsAuthorized: User not in group(s)."); - return ret; + return (con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED); } /* - * 'cupsdIsAuthorized()' - Check to see if the user is authorized... + * 'cupsdNewLocation()' - Create a new location for authorization. + * + * Note: Still need to call cupsdAddLocation() to add it to the list of global + * locations. */ -http_status_t /* O - HTTP_OK if authorized or error code */ -cupsdIsAuthorized(cupsd_client_t *con, /* I - Connection */ - const char *owner)/* I - Owner of object */ +cupsd_location_t * /* O - Pointer to new location record */ +cupsdNewLocation(const char *location) /* I - Location path */ { - int i, /* Looping vars */ - auth, /* Authorization status */ - type; /* Type of authentication */ - http_addr_t *hostaddr = httpGetAddress(con->http); - /* Client address */ - const char *hostname = httpGetHostname(con->http, NULL, 0); - /* Client hostname */ - unsigned address[4]; /* Authorization address */ - cupsd_location_t *best; /* Best match for location so far */ - size_t hostlen; /* Length of hostname */ - char *name, /* Current username */ - username[256], /* Username to authorize */ - ownername[256], /* Owner name to authorize */ - *ptr; /* Pointer into username */ - struct passwd *pw; /* User password data */ - static const char * const levels[] = /* Auth levels */ - { - "ANON", - "USER", - "GROUP" - }; - static const char * const types[] = /* Auth types */ - { - "None", - "Basic", - "Negotiate" - }; + cupsd_location_t *temp; /* New location */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: con->uri=\"%s\", con->best=%p(%s)", con->uri, con->best, con->best ? con->best->location ? con->best->location : "(null)" : ""); - if (owner) - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: owner=\"%s\"", owner); + /* + * Try to allocate memory for the new location. + */ + + if ((temp = calloc(1, sizeof(cupsd_location_t))) == NULL) + return (NULL); /* - * If there is no "best" authentication rule for this request, then - * access is allowed from the local system and denied from other - * addresses... + * Initialize the record and copy the name over... */ - if (!con->best) + if ((temp->location = _cupsStrAlloc(location)) == NULL) { - if (httpAddrLocalhost(httpGetAddress(con->http)) || - !strcmp(hostname, ServerName) || - cupsArrayFind(ServerAlias, (void *)hostname)) - return (HTTP_OK); - else - return (HTTP_FORBIDDEN); + free(temp); + return (NULL); } - best = con->best; + temp->length = strlen(temp->location); - if ((type = best->type) == CUPSD_AUTH_DEFAULT) - type = cupsdDefaultAuthType(); + /* + * Return the new record... + */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: level=CUPSD_AUTH_%s, type=%s, satisfy=CUPSD_AUTH_SATISFY_%s, num_names=%d", levels[best->level], types[type], best->satisfy ? "ANY" : "ALL", cupsArrayCount(best->names)); + return (temp); +} + + +/* + * 'check_admin_task()' - Do additional checks on administrative tasks + */ + +static int /* O - 1 if admin task authorized */ +check_admin_task(cupsd_client_t *con) /* I - Connection */ +{ + int ret = 1; /* Return value */ - if (best->limit == CUPSD_AUTH_LIMIT_IPP) - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: op=%x(%s)", best->op, ippOpString(best->op)); + + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Administrative task"); /* - * Check host/ip-based accesses... + * 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. */ -#ifdef AF_INET6 - if (httpAddrFamily(hostaddr) == AF_INET6) - { - /* - * Copy IPv6 address... - */ +#if defined(AF_LOCAL) && defined(SUPPORT_SNAPPED_CLIENTS) - address[0] = ntohl(hostaddr->ipv6.sin6_addr.s6_addr32[0]); - address[1] = ntohl(hostaddr->ipv6.sin6_addr.s6_addr32[1]); - address[2] = ntohl(hostaddr->ipv6.sin6_addr.s6_addr32[2]); - address[3] = ntohl(hostaddr->ipv6.sin6_addr.s6_addr32[3]); - } - else -#endif /* AF_INET6 */ - if (con->http->hostaddr->addr.sa_family == AF_INET) + /* + * Get the client's file descriptor and from this its AppArmor context + */ + + if (httpAddrFamily(con->http->hostaddr) == AF_LOCAL) { - /* - * Copy IPv4 address... - */ + int peerfd; /* Peer's file descriptor */ - address[0] = 0; - address[1] = 0; - address[2] = 0; - address[3] = ntohl(hostaddr->ipv4.sin_addr.s_addr); - } - else - memset(address, 0, sizeof(address)); + peerfd = httpGetFd(con->http); - hostlen = strlen(hostname); + if (peerfd < 0) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: Unable to get peer file descriptor of client connecting via domain socket"); + } + else + { + char *context = NULL; /* AppArmor profile name of client */ +# undef CHECK_METHOD_FOUND +# ifdef SUPPORT_SNAPPED_CUPSD + int status = 65535; /* Status of client Snap context check */ +# if defined(HAVE_SNAPDGLIB) && defined(HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC) +# define CHECK_METHOD_FOUND 1 + char *args[] = { "is-connected", "--apparmor-label", NULL, CUPS_CONTROL_SLOT, NULL }; /* snapctl arguments */ + SnapdClient *client = NULL; /* Data structure of snapd access */ + const char *cookie; /* snapd access cookie */ + GError *error = NULL; /* Glib error */ +# else +# ifdef HAVE_SNAPCTL_IS_CONNECTED +# define CHECK_METHOD_FOUND 1 + char *args[] = { SNAPCTL, "is-connected", "--apparmor-label", NULL, CUPS_CONTROL_SLOT, NULL }; /* 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 */ + char buf[1024]; /* Buffer for snapctl's stderr output */ + int wstatus; /* Wait result of forked snapctl process */ +# endif /* HAVE_SNAPCTL_IS_CONNECTED */ +# endif /* HAVE_SNAPDGLIB && HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC */ +# else +# if !defined(SUPPORT_SNAPPED_CUPSD) && defined(HAVE_SNAPDGLIB) +# define CHECK_METHOD_FOUND 1 + char *snap_name = NULL; /* Client Snap name */ + char *dot; /* Pointer to dot in AppArmor profile name */ + SnapdClient *snapd = NULL; /* Data structure of snapd access */ + GError *error = NULL; /* Glib error */ + SnapdSnap *snap = NULL; /* Data structure of client Snap */ + GPtrArray *plugs = NULL; /* Plug search result of client Snap */ +# endif /* !SUPPORT_SNAPPED_CUPSD && HAVE_SNAPDGLIB */ +# endif /* SUPPORT_SNAPPED_CUPSD */ - auth = cupsdCheckAccess(address, hostname, hostlen, best) - ? CUPSD_AUTH_ALLOW : CUPSD_AUTH_DENY; - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: auth=CUPSD_AUTH_%s...", auth ? "DENY" : "ALLOW"); +# ifndef SUPPORT_SNAPPED_CUPSD - if (auth == CUPSD_AUTH_DENY && best->satisfy == CUPSD_AUTH_SATISFY_ALL) - return (HTTP_FORBIDDEN); + /* If AppArmor is not enabled, then we can't identify the client */ + /* 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()) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: No AppArmor in use"); + goto snap_check_done; + } -#ifdef HAVE_TLS - /* - * See if encryption is required... - */ +# endif /* !SUPPORT_SNAPPED_CUPSD */ + + if (aa_getpeercon(peerfd, &context, NULL) < 0) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: AppArmor profile could not be retrieved for client process - Error: %s", strerror(errno)); + goto snap_check_done; + } else + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: AppArmor profile of client process: %s", context); + +# ifdef OUR_SNAP_NAME + /* Is the client one of the utilities of our Snap? */ + if (!strncmp(context, "snap." OUR_SNAP_NAME ".", strlen(OUR_SNAP_NAME) + 6)) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap is the same Snap we are running in, access granted"); + goto snap_check_done; + } +# endif /* OUR_SNAP_NAME */ + +# ifdef SUPPORT_SNAPPED_CUPSD + + /* + * Run + * + * snapctl is-connected --apparmor-label AA_CONTEXT CUPS_CONTROL_SLOT + * + * or an equivalent library function call using the + * snapd_client_run_snapctl2_sync() function of libsnapd-glib. + * + * Here AA_CONTEXT is 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/function call 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 snapctl/the function + * 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. + */ - if ((best->encryption >= HTTP_ENCRYPT_REQUIRED && !con->http->tls && - _cups_strcasecmp(hostname, "localhost") && - !httpAddrLocalhost(hostaddr) && - best->satisfy == CUPSD_AUTH_SATISFY_ALL) && - !(type == CUPSD_AUTH_NEGOTIATE || - (type == CUPSD_AUTH_NONE && - cupsdDefaultAuthType() == CUPSD_AUTH_NEGOTIATE))) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "cupsdIsAuthorized: Need upgrade to TLS..."); - return (HTTP_UPGRADE_REQUIRED); - } -#endif /* HAVE_TLS */ +# if defined(HAVE_SNAPDGLIB) && defined(HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC) - /* - * Now see what access level is required... - */ + /* + * Use the snapd_client_run_snapctl2_sync() function of libsnapd-glib + */ - if (best->level == CUPSD_AUTH_ANON || /* Anonymous access - allow it */ - (type == CUPSD_AUTH_NONE && cupsArrayCount(best->names) == 0)) - return (HTTP_OK); + /* Insert client Snap context in snapctl arguments */ + args[2] = context; - if (!con->username[0] && type == CUPSD_AUTH_NONE && - best->limit == CUPSD_AUTH_LIMIT_IPP) - { - /* - * Check for unauthenticated username... - */ + /* Connect to snapd */ + client = snapd_client_new(); + if (!client) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Could not connect to snapd, permission denied"); + ret = 0; + goto snap_check_done; + } - ipp_attribute_t *attr; /* requesting-user-name attribute */ + /* snapctl commands are sent over a this socket that is made + available within the snap sandbox */ + snapd_client_set_socket_path(client, "/run/snapd-snap.socket"); + /* Take cookie from the environment if available */ + cookie = g_getenv("SNAP_COOKIE"); + if (!cookie) + { + cookie = ""; + cupsdLogMessage(CUPSD_LOG_WARN, "check_admin_task: No SNAP_COOKIE set in the Snap environment, client Snap context check may not work"); + } - attr = ippFindAttribute(con->request, "requesting-user-name", IPP_TAG_NAME); - if (attr) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "cupsdIsAuthorized: requesting-user-name=\"%s\"", - attr->values[0].string.text); - strlcpy(username, attr->values[0].string.text, sizeof(username)); - } - else if (best->satisfy == CUPSD_AUTH_SATISFY_ALL || auth == CUPSD_AUTH_DENY) - return (HTTP_UNAUTHORIZED); /* Non-anonymous needs user/pass */ - else - return (HTTP_OK); /* unless overridden with Satisfy */ - } - else - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdIsAuthorized: username=\"%s\"", - con->username); + /* Do the client Snap context check */ + if (!snapd_client_run_snapctl2_sync(client, cookie, args, NULL, NULL, &status, NULL, &error)) { + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap context check error - %s", error->message); + ret = 0; + goto snap_check_done; + } -#ifdef HAVE_AUTHORIZATION_H - if (!con->username[0] && !con->authref) -#else - if (!con->username[0]) -#endif /* HAVE_AUTHORIZATION_H */ - { - if (best->satisfy == CUPSD_AUTH_SATISFY_ALL || auth == CUPSD_AUTH_DENY) - return (HTTP_UNAUTHORIZED); /* Non-anonymous needs user/pass */ - else - return (HTTP_OK); /* unless overridden with Satisfy */ - } +# else +# ifdef HAVE_SNAPCTL_IS_CONNECTED + /* + * Call the snapctl executable using execv() in a fork + */ - if (con->type != type && type != CUPSD_AUTH_NONE && -#ifdef HAVE_GSSAPI - (type != CUPSD_AUTH_NEGOTIATE || con->gss_uid <= 0) && -#endif /* HAVE_GSSAPI */ - con->type != CUPSD_AUTH_BASIC) - { - cupsdLogMessage(CUPSD_LOG_ERROR, "Authorized using %s, expected %s.", - types[con->type], types[type]); + /* Insert client Snap context in snapctl command line */ + args[3] = context; - return (HTTP_UNAUTHORIZED); - } + /* Create a pipe to catch stderr output from snapctl */ + if (pipe(fds)) + { + fds[0] = -1; + fds[1] = -1; + cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: Unable to establish stderr pipe for snapctl call - %s", strerror(errno)); + ret = 0; + goto snap_check_done; + } - strlcpy(username, con->username, sizeof(username)); - } + /* 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, "check_admin_task: 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, "check_admin_task: 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; + } - /* - * OK, got a username. See if we need normal user access, or group - * access... - */ + 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, "check_admin_task: 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, "check_admin_task: Invalid pipe file descriptor to couple with stderr of snapctl process - %s", strerror(errno)); + exit(100); + } - /* - * Strip any @domain or @KDC from the username and owner... - */ + /* 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); - if ((ptr = strchr(username, '@')) != NULL) - *ptr = '\0'; + /* Execute snapctl command line ... */ + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Running command: " SNAPCTL " is-connected --apparmor-label %s " CUPS_CONTROL_SLOT, context); + execv(SNAPCTL, args); + cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: Unable to launch snapctl: %s", strerror(errno)); + exit(100); + } + else if (pid < 0) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: Unable to fork for snapctl call - %s", strerror(errno)); + ret = 0; + goto snap_check_done; + } + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Started snapctl (PID %d)", pid); - if (owner) - { - strlcpy(ownername, owner, sizeof(ownername)); + close(fds[1]); - if ((ptr = strchr(ownername, '@')) != NULL) - *ptr = '\0'; - } - else - ownername[0] = '\0'; + /* 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, "check_admin_task: Error message from snapctl: %s", buf); + ret = 0; + } + cupsFileClose(snapctl_stderr); + } + close(fds[0]); - /* - * Get the user info... - */ + /* Wait for snapctl to finish */ + retry_wait: + if (waitpid (pid, &wstatus, 0) == -1) + { + if (errno == EINTR) + goto retry_wait; + cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: 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, "check_admin_task: snapctl (PID %d) exited with no errors.", pid); - if (username[0]) - { - pw = getpwnam(username); - endpwent(); - } - else - pw = NULL; + /* How did snapctl terminate */ + if (WIFEXITED(wstatus)) + { + /* Via regular exit */ + status = WEXITSTATUS(wstatus); + if (status == 100) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: snapctl not executed"); + ret = 0; + goto snap_check_done; + } + } + else if (WIFSIGNALED(wstatus)) + { + /* Via signal */ + cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: snapctl caught the signal %d", WTERMSIG(wstatus)); + ret = 0; + goto snap_check_done; + } - /* - * 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. - */ +# endif /* HAVE_SNAPCTL_IS_CONNECTED */ +# endif /* HAVE_SNAPDGLIB && HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC */ - if (best->level == CUPSD_AUTH_USER) - { - /* - * If there are no names associated with this location, then - * any valid user is OK... - */ + switch (status) + { + case 0 : + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap connecting via \"cups-control\" interface, access granted"); + break; + case 1 : + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap does not connect via \"cups-control\" interface, permission denied"); + ret = 0; + break; + case 10 : + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap under classic confinement, access granted"); + break; + case 11 : + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client is not a Snap, access granted"); + break; + default : + cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: snapctl exited with unknown status: %d", status); + ret = 0; + break; + } - if (cupsArrayCount(best->names) == 0) - return (HTTP_OK); + snap_check_done: + if (context) + free(context); +# if defined(HAVE_SNAPDGLIB) && defined(HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC) + g_clear_object(&client); +# endif /* HAVE_SNAPDGLIB && HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC */ - /* - * Otherwise check the user list and return OK if this user is - * allowed... - */ +# else +# if !defined(SUPPORT_SNAPPED_CUPSD) && defined(HAVE_SNAPDGLIB) - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: Checking user membership..."); + /* + * If the client is a Snap, extract the client Snap's name from + * 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 + * - 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 this is + * the only way to check Snap status on clients from an unsnapped + * cupsd. + */ -#ifdef HAVE_AUTHORIZATION_H - /* - * If an authorization reference was supplied it must match a right name... - */ + /* 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, "check_admin_task: AppArmor context not from a Snap"); + goto snap_check_done; + } - if (con->authref) - { - for (name = (char *)cupsArrayFirst(best->names); - name; - name = (char *)cupsArrayNext(best->names)) + dot = strchr(context + 5, '.'); + if (dot == NULL) { - if (!_cups_strncasecmp(name, "@AUTHKEY(", 9) && check_authref(con, name + 9)) - return (HTTP_OK); + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: 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, "check_admin_task: Client is the Snap %s", snap_name); - for (name = (char *)cupsArrayFirst(best->names); - name; - name = (char *)cupsArrayNext(best->names)) + /* Connect to snapd */ + snapd = snapd_client_new(); + if (!snapd) { - if (!_cups_strcasecmp(name, "@SYSTEM") && SystemGroupAuthKey && - check_authref(con, SystemGroupAuthKey) && - cupsdCheckAdminTask(con)) - return (HTTP_OK); + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Could not connect to snapd, permission denied"); + ret = 0; + goto snap_check_done; } - return (HTTP_FORBIDDEN); - } -#endif /* HAVE_AUTHORIZATION_H */ + /* 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, "check_admin_task: Could not obtain client Snap data: \"%s\", permission denied", error->message); + ret = 0; + goto snap_check_done; + } - for (name = (char *)cupsArrayFirst(best->names); - name; - name = (char *)cupsArrayNext(best->names)) - { - if (!_cups_strcasecmp(name, "@OWNER") && owner && - !_cups_strcasecmp(username, ownername)) - return (HTTP_OK); - else if (!_cups_strcasecmp(name, "@SYSTEM")) + /* Snaps using classic confinement are granted access */ + if (snapd_snap_get_confinement(snap) == SNAPD_CONFINEMENT_CLASSIC) { - /* Do @SYSTEM later, when every other entry fails */ - continue; + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap under classic confinement, access granted"); + goto snap_check_done; } - else if (name[0] == '@') + + /* 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)) { - if (cupsdCheckGroup(username, pw, name + 1)) - return (HTTP_OK); + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Could not obtain the client Snap's interface connections: \"%s\", permission denied", error->message); + ret = 0; + goto snap_check_done; } - else if (!_cups_strcasecmp(username, name)) - return (HTTP_OK); - } - for (name = (char *)cupsArrayFirst(best->names); - name; - name = (char *)cupsArrayNext(best->names)) - { - if (!_cups_strcasecmp(name, "@SYSTEM")) + if (plugs->len <= 0) { - for (i = 0; i < NumSystemGroups; i ++) - if (cupsdCheckGroup(username, pw, SystemGroups[i]) && - cupsdCheckAdminTask(con)) - return (HTTP_OK); + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap does not connect via \"cups-control\" interface, permission denied"); + ret = 0; + goto snap_check_done; } - } - return (con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED); - } + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap connecting via \"cups-control\" interface, access granted"); - /* - * Check to see if this user is in any of the named groups... - */ + snap_check_done: + if (context) + free(context); + if (snap_name) + free(snap_name); + g_clear_object(&snapd); + g_clear_object(&snap); + if (plugs) + g_ptr_array_unref(plugs); - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: Checking group membership..."); +# endif /* !SUPPORT_SNAPPED_CUPSD && HAVE_SNAPDGLIB */ +# endif /* SUPPORT_SNAPPED_CUPSD */ - /* - * Check to see if this user is in any of the named groups... - */ +# ifndef CHECK_METHOD_FOUND - for (name = (char *)cupsArrayFirst(best->names); - name; - name = (char *)cupsArrayNext(best->names)) - { - if (!_cups_strcasecmp(name, "@SYSTEM")) - { - /* Do @SYSTEM later, when every other entry fails */ - continue; - } + /* + * Issue warning if requirements for building Snap-related access control + * not fulfilled + */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: Checking group \"%s\" membership...", name); + cupsdLogMessage(CUPSD_LOG_WARN, "check_admin_task: Compiling problem: none of the three access control methods (libsnapd-glib snapd access, \"snapctl is-connected\", libsnapd-glib-based snapctl call) available, no Snap-related access control built!"); - if (cupsdCheckGroup(username, pw, name)) - return (HTTP_OK); - } + snap_check_done: + if (context) + free(context); - 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); +# endif /* !CHECK_METHOD_FOUND */ - for (i = 0; i < NumSystemGroups; i ++) - if (cupsdCheckGroup(username, pw, SystemGroups[i]) && - cupsdCheckAdminTask(con)) - return (HTTP_OK); } } - /* - * The user isn't part of the specified group, so deny access... - */ - - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdIsAuthorized: User not in group(s)."); - - return (con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED); -} - - -/* - * 'cupsdNewLocation()' - Create a new location for authorization. - * - * Note: Still need to call cupsdAddLocation() to add it to the list of global - * locations. - */ - -cupsd_location_t * /* O - Pointer to new location record */ -cupsdNewLocation(const char *location) /* I - Location path */ -{ - cupsd_location_t *temp; /* New location */ - - - /* - * Try to allocate memory for the new location. - */ - - if ((temp = calloc(1, sizeof(cupsd_location_t))) == NULL) - return (NULL); - - /* - * Initialize the record and copy the name over... - */ + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Access %s", ret == 1 ? "granted" : "denied"); - if ((temp->location = _cupsStrAlloc(location)) == NULL) - { - free(temp); - return (NULL); - } +#else - temp->length = strlen(temp->location); + (void)con; + cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Access granted (no extra checking)"); - /* - * Return the new record... - */ +#endif /* AF_LOCAL && SUPPORT_SNAPPED_CLIENTS */ - return (temp); + return ret; } diff --git a/scheduler/cupsfilter.c b/scheduler/cupsfilter.c index 23993aaf93..d9192b4242 100644 --- a/scheduler/cupsfilter.c +++ b/scheduler/cupsfilter.c @@ -373,7 +373,6 @@ main(int argc, /* I - Number of command-line args */ default : /* Something we don't understand... */ _cupsLangPrintf(stderr, _("%s: Error - unknown option \"-%c\"."), argv[0], *opt); usage(NULL); - break; } } } diff --git a/scheduler/ipp.c b/scheduler/ipp.c index 189079f920..800ccff231 100644 --- a/scheduler/ipp.c +++ b/scheduler/ipp.c @@ -2207,7 +2207,7 @@ add_printer(cupsd_client_t *con, /* I - Client connection */ ipp_attribute_t *uri) /* I - URI of printer */ { http_status_t status; /* Policy status */ - int i; /* Looping var */ + int i = 0; /* Looping var */ char scheme[HTTP_MAX_URI], /* Method portion of URI */ username[HTTP_MAX_URI], /* Username portion of URI */ host[HTTP_MAX_URI], /* Host portion of URI */ diff --git a/systemv/cupsctl.c b/systemv/cupsctl.c index 3b5b4c58c4..4568fb9497 100644 --- a/systemv/cupsctl.c +++ b/systemv/cupsctl.c @@ -151,7 +151,6 @@ main(int argc, /* I - Number of command-line args */ default : usage(opt); - break; } } } diff --git a/tools/ippeveps.c b/tools/ippeveps.c index cdedc8899a..28631beecc 100644 --- a/tools/ippeveps.c +++ b/tools/ippeveps.c @@ -417,10 +417,8 @@ get_options(cups_option_t **options) /* O - Options */ * Load PPD file and the corresponding IPP <-> PPD cache data... */ - if ((ppd = ppdOpenFile(getenv("PPD"))) != NULL) + if ((ppd = ppdOpenFile(getenv("PPD"))) != NULL && (ppd_cache = _ppdCacheCreateWithPPD(ppd)) != NULL) { - ppd_cache = _ppdCacheCreateWithPPD(ppd); - /* TODO: Fix me - values are names, not numbers... Also need to support finishings-col */ if ((value = getenv("IPP_FINISHINGS")) == NULL) value = getenv("IPP_FINISHINGS_DEFAULT"); diff --git a/tools/ipptool.c b/tools/ipptool.c index e89fb5bcad..7c8bc6d96e 100644 --- a/tools/ipptool.c +++ b/tools/ipptool.c @@ -1645,36 +1645,38 @@ do_test(_ipp_file_t *f, /* I - IPP data file */ status_ok = 1; repeat_test = 1; } - - for (i = 0, status_ok = 0; i < data->num_statuses; i ++) + else { - if (data->statuses[i].if_defined && - !_ippVarsGet(data->vars, data->statuses[i].if_defined)) - continue; - - if (data->statuses[i].if_not_defined && - _ippVarsGet(data->vars, data->statuses[i].if_not_defined)) - continue; - - if (ippGetStatusCode(response) == data->statuses[i].status) + for (i = 0, status_ok = 0; i < data->num_statuses; i ++) { - status_ok = 1; - - if (data->statuses[i].repeat_match && repeat_count < data->statuses[i].repeat_limit) - repeat_test = 1; + if (data->statuses[i].if_defined && + !_ippVarsGet(data->vars, data->statuses[i].if_defined)) + continue; - if (data->statuses[i].define_match) - _ippVarsSet(data->vars, data->statuses[i].define_match, "1"); - } - else - { - if (data->statuses[i].repeat_no_match && repeat_count < data->statuses[i].repeat_limit) - repeat_test = 1; + if (data->statuses[i].if_not_defined && + _ippVarsGet(data->vars, data->statuses[i].if_not_defined)) + continue; - if (data->statuses[i].define_no_match) + if (ippGetStatusCode(response) == data->statuses[i].status) { - _ippVarsSet(data->vars, data->statuses[i].define_no_match, "1"); status_ok = 1; + + if (data->statuses[i].repeat_match && repeat_count < data->statuses[i].repeat_limit) + repeat_test = 1; + + if (data->statuses[i].define_match) + _ippVarsSet(data->vars, data->statuses[i].define_match, "1"); + } + else + { + if (data->statuses[i].repeat_no_match && repeat_count < data->statuses[i].repeat_limit) + repeat_test = 1; + + if (data->statuses[i].define_no_match) + { + _ippVarsSet(data->vars, data->statuses[i].define_no_match, "1"); + status_ok = 1; + } } } } diff --git a/xcode/CUPS.xcodeproj/project.pbxproj b/xcode/CUPS.xcodeproj/project.pbxproj index 253289e8fe..f4311664ae 100644 --- a/xcode/CUPS.xcodeproj/project.pbxproj +++ b/xcode/CUPS.xcodeproj/project.pbxproj @@ -901,12 +901,12 @@ 72D53A3015B4923F003F877F /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278C58E5136B64AF00836530 /* CoreFoundation.framework */; }; 72D53A3115B4923F003F877F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278C58E7136B64B000836530 /* Security.framework */; }; 72D53A3215B4923F003F877F /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278C58E8136B64B000836530 /* SystemConfiguration.framework */; }; - 72D53A3415B4925B003F877F /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72D53A3315B4925B003F877F /* ApplicationServices.framework */; }; + 72D53A3415B4925B003F877F /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72D53A3315B4925B003F877F /* ApplicationServices.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 72D53A3515B49270003F877F /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72D53A2C15B4913D003F877F /* IOKit.framework */; }; 72D53A3815B4929D003F877F /* colorman.c in Sources */ = {isa = PBXBuildFile; fileRef = 72D53A3615B4929D003F877F /* colorman.c */; }; 72D53A3A15B492FA003F877F /* libpam.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 72D53A3915B492FA003F877F /* libpam.dylib */; }; - 72D53A3B15B4930A003F877F /* GSS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72D53A2915B49110003F877F /* GSS.framework */; }; - 72D53A3C15B4930A003F877F /* Kerberos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278C58E6136B64B000836530 /* Kerberos.framework */; }; + 72D53A3B15B4930A003F877F /* GSS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72D53A2915B49110003F877F /* GSS.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 72D53A3C15B4930A003F877F /* Kerberos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278C58E6136B64B000836530 /* Kerberos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 72F75A5C1336F988004BB496 /* cupstestppd.c in Sources */ = {isa = PBXBuildFile; fileRef = 72F75A5B1336F988004BB496 /* cupstestppd.c */; }; 72F75A671336FA38004BB496 /* libcups.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 72220EAE1333047D00FCA411 /* libcups.dylib */; }; /* End PBXBuildFile section */ From 027dd5b9d6404d62dca982de4b0b39abced8f662 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Mon, 4 Oct 2021 17:37:41 -0400 Subject: [PATCH 4/8] Initial implementation of OAuth client callback (Issue #100) --- CHANGES.md | 1 + cups/auth.c | 47 ++++++++++++++++++++++++++++++++++++--------- cups/cups-private.h | 3 +++ cups/cups.h | 8 ++++++++ cups/usersys.c | 37 +++++++++++++++++++++++++++++++++++ cups/versioning.h | 3 +++ 6 files changed, 90 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 30012b3ea1..7d1f214724 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ CUPS v2.4-b1 (Pending) ---------------------- - Added support for CUPS running in a Snapcraft snap. +- Added basic OAuth 2.0 client support (Issue #100) - Added support for AirPrint and Mopria clients (Issue #105) - Added configure support for specifying systemd dependencies in the CUPS service file (Issue #144) diff --git a/cups/auth.c b/cups/auth.c index d70622ca5f..177eec8ce3 100644 --- a/cups/auth.c +++ b/cups/auth.c @@ -1,13 +1,15 @@ /* * Authentication functions for CUPS. * - * Copyright 2007-2019 by Apple Inc. - * Copyright 1997-2007 by Easy Software Products. + * Copyright © 2021 by OpenPrinting. + * Copyright © 2007-2019 by Apple Inc. + * Copyright © 1997-2007 by Easy Software Products. * * This file contains Kerberos support code, copyright 2006 by * Jelmer Vernooij. * - * Licensed under Apache License v2.0. See the file "LICENSE" for more information. + * Licensed under Apache License v2.0. See the file "LICENSE" for more + * information. */ /* @@ -115,7 +117,7 @@ cupsDoAuthentication( char scheme[256], /* Scheme name */ prompt[1024]; /* Prompt for user */ int localauth; /* Local authentication result */ - _cups_globals_t *cg; /* Global data */ + _cups_globals_t *cg = _cupsGlobals(); /* Global data */ DEBUG_printf(("cupsDoAuthentication(http=%p, method=\"%s\", resource=\"%s\")", (void *)http, method, resource)); @@ -202,9 +204,38 @@ cupsDoAuthentication( } else #endif /* HAVE_GSSAPI */ - if (_cups_strcasecmp(scheme, "Basic") && - _cups_strcasecmp(scheme, "Digest") && - _cups_strcasecmp(scheme, "Negotiate")) + if (!_cups_strcasecmp(scheme, "Bearer")) + { + // OAuth 2.0 (Bearer) authentication... + const char *bearer = NULL; /* Bearer token string, if any */ + + if (cg->oauth_cb) + { + // Try callback... + char scope[HTTP_MAX_VALUE]; /* scope="xyz" string */ + + cups_auth_param(schemedata, "realm", http->realm, sizeof(http->realm)); + + if (cups_auth_param(schemedata, "scope", scope, sizeof(scope))) + bearer = (cg->oauth_cb)(http, http->realm, scope, resource, cg->oauth_data); + else + bearer = (cg->oauth_cb)(http, http->realm, NULL, resource, cg->oauth_data); + } + + if (bearer) + { + // Use this access token... + httpSetAuthString(http, "Bearer", bearer); + break; + } + else + { + // No access token, try the next scheme... + DEBUG_puts("2cupsDoAuthentication: No OAuth access token to provide."); + continue; + } + } + else if (_cups_strcasecmp(scheme, "Basic") && _cups_strcasecmp(scheme, "Digest") && _cups_strcasecmp(scheme, "Negotiate")) { /* * Other schemes not yet supported... @@ -227,8 +258,6 @@ cupsDoAuthentication( char default_username[HTTP_MAX_VALUE]; /* Default username */ - cg = _cupsGlobals(); - if (!cg->lang_default) cg->lang_default = cupsLangDefault(); diff --git a/cups/cups-private.h b/cups/cups-private.h index 97734a539b..cf2559d958 100644 --- a/cups/cups-private.h +++ b/cups/cups-private.h @@ -1,6 +1,7 @@ /* * Private definitions for CUPS. * + * Copyright © 2021 by OpenPrinting. * Copyright © 2007-2019 by Apple Inc. * Copyright © 1997-2007 by Easy Software Products, all rights reserved. * @@ -172,6 +173,8 @@ typedef struct _cups_globals_s /**** CUPS global state data ****/ server[256], /* Server address */ servername[256],/* Server hostname */ password[128]; /* Password for default callback */ + cups_oauth_cb_t oauth_cb; /* OAuth callback */ + void *oauth_data; /* OAuth user data */ cups_password_cb2_t password_cb; /* Password callback */ void *password_data; /* Password user data */ http_tls_credentials_t tls_credentials; diff --git a/cups/cups.h b/cups/cups.h index 760a1a01c0..9949e2ee76 100644 --- a/cups/cups.h +++ b/cups/cups.h @@ -1,6 +1,7 @@ /* * API definitions for CUPS. * + * Copyright © 2021 by OpenPrinting. * Copyright © 2007-2020 by Apple Inc. * Copyright © 1997-2007 by Easy Software Products. * @@ -309,6 +310,9 @@ typedef int (^cups_dest_block_t)(unsigned flags, cups_dest_t *dest); * @exclude all@ */ # endif /* __BLOCKS__ */ +typedef const char *(*cups_oauth_cb_t)(http_t *http, const char *realm, const char *scope, const char *resource, void *user_data); + /* OAuth callback @since CUPS 2.4@ */ + typedef const char *(*cups_password_cb_t)(const char *prompt); /* Password callback @exclude all@ */ @@ -604,6 +608,10 @@ extern const char *cupsHashString(const unsigned char *hash, size_t hashsize, ch extern int cupsAddDestMediaOptions(http_t *http, cups_dest_t *dest, cups_dinfo_t *dinfo, unsigned flags, cups_size_t *size, int num_options, cups_option_t **options) _CUPS_API_2_3; extern ipp_attribute_t *cupsEncodeOption(ipp_t *ipp, ipp_tag_t group_tag, const char *name, const char *value) _CUPS_API_2_3; +/* New in CUPS 2.4 */ +extern void cupsSetOAuthCB(cups_oauth_cb_t cb, void *data) _CUPS_API_2_4; + + # ifdef __cplusplus } # endif /* __cplusplus */ diff --git a/cups/usersys.c b/cups/usersys.c index a5553ad44c..a9386e7f10 100644 --- a/cups/usersys.c +++ b/cups/usersys.c @@ -309,6 +309,43 @@ cupsSetEncryption(http_encryption_t e) /* I - New encryption preference */ } +/* + * 'cupsSetOAuthCB()' - Set the OAuth 2.0 callback for CUPS. + * + * This function sets the OAuth 2.0 callback for the various CUPS APIs that + * send HTTP requests. Pass @code NULL@ to restore the default (console-based) + * callback. + * + * The OAuth callback receives the HTTP connection, realm name, scope name (if + * any), resource path, and the "user_data" pointer for each request that + * requires an OAuth access token. The function then returns either the Bearer + * token string or `NULL` if no authorization could be obtained. + * + * Beyond reusing the Bearer token for subsequent requests on the same HTTP + * connection, no caching of the token is done by the CUPS library. The + * callback can determine whether to refresh a cached token by examining any + * existing token returned by the @link httpGetAuthString@ function. + * + * Note: The current OAuth callback is tracked separately for each thread in a + * program. Multi-threaded programs that override the callback need to do so in + * each thread for the same callback to be used. + * + * @since CUPS 2.4@ + */ + +void +cupsSetOAuthCB( + cups_oauth_cb_t cb, /* I - Callback function */ + void *user_data) /* I - User data pointer */ +{ + _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ + + + cg->oauth_cb = cb; + cg->oauth_data = user_data; +} + + /* * 'cupsSetPasswordCB()' - Set the password callback for CUPS. * diff --git a/cups/versioning.h b/cups/versioning.h index 2ad9879f92..ad398ed8b1 100644 --- a/cups/versioning.h +++ b/cups/versioning.h @@ -1,6 +1,7 @@ /* * API versioning definitions for CUPS. * + * Copyright © 2021 by OpenPrinting. * Copyright © 2007-2019 by Apple Inc. * * Licensed under Apache License v2.0. See the file "LICENSE" for more @@ -129,6 +130,7 @@ # define _CUPS_API_2_2_4 _CUPS_API_AVAILABLE(macos(10.13), ios(12.0)) _CUPS_PUBLIC # define _CUPS_API_2_2_7 _CUPS_API_AVAILABLE(macos(10.14), ios(13.0)) _CUPS_PUBLIC # define _CUPS_API_2_3 _CUPS_API_AVAILABLE(macos(10.14), ios(13.0)) _CUPS_PUBLIC +# define _CUPS_API_2_4 _CUPS_PUBLIC # else # define _CUPS_API_1_1_19 _CUPS_PUBLIC # define _CUPS_API_1_1_20 _CUPS_PUBLIC @@ -144,6 +146,7 @@ # define _CUPS_API_2_2_4 _CUPS_PUBLIC # define _CUPS_API_2_2_7 _CUPS_PUBLIC # define _CUPS_API_2_3 _CUPS_PUBLIC +# define _CUPS_API_2_4 _CUPS_PUBLIC # endif /* __APPLE__ && !_CUPS_SOURCE */ From 5d464d281f11ab4ba1ad52b6c1d2533697027d4c Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 5 Oct 2021 18:53:23 -0400 Subject: [PATCH 5/8] Add Coverity scan action. --- .github/workflows/coverity.yml | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/coverity.yml diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml new file mode 100644 index 0000000000..7f3d7f9587 --- /dev/null +++ b/.github/workflows/coverity.yml @@ -0,0 +1,41 @@ +name: Coverity Scan + +on: workflow_dispatch + +jobs: + coverity-scan: + runs-on: ubuntu-latest + environment: Coverity + steps: + - uses: actions/checkout@v2 + - name: update build environment + run: sudo apt-get update --fix-missing -y + - name: install prerequisites + run: sudo apt-get install -y avahi-daemon cppcheck libavahi-client-dev libcups2-dev libcupsimage2-dev libgnutls28-dev libjpeg-dev libpam-dev libpng-dev libusb-1.0-0-dev zlib1g-dev + - name: Download Coverity Build Tool + run: | + wget -q https://scan.coverity.com/download/linux64 --post-data token="$TOKEN&project=$GITHUB_REPOSITORY" -O cov-analysis-linux64.tar.gz + mkdir cov-analysis-linux64 + tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64 + env: + TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} + + - name: configure + run: ./configure --enable-debug --enable-maintainer + + - name: Build with cov-build + run: | + export PATH=`pwd`/cov-analysis-linux64/bin:$PATH + cov-build --dir cov-int make + - name: Submit the result to Coverity Scan + run: | + tar czvf cov.tgz cov-int + curl \ + --form token=$TOKEN \ + --form email=michael.r.sweet@gmail.com \ + --form file=@cov.tgz \ + --form version="$GITHUB_REF" \ + --form description="$GITHUB_SHA" \ + "https://scan.coverity.com/builds?project=$GITHUB_REPOSITORY" + env: + TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} From 9968d013215891afe23c581539ef895a0d9add40 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Wed, 6 Oct 2021 08:13:11 -0400 Subject: [PATCH 6/8] Save work on simplifying snap support. --- Makedefs.in | 4 +- config-scripts/cups-container.m4 | 116 +++++++------------------------ config.h.in | 30 +++++--- scheduler/Makefile | 5 +- scheduler/auth.c | 16 ++--- 5 files changed, 58 insertions(+), 113 deletions(-) diff --git a/Makedefs.in b/Makedefs.in index 70ee0e9a0c..07010f0656 100644 --- a/Makedefs.in +++ b/Makedefs.in @@ -172,8 +172,6 @@ DSOFLAGS = @DSOFLAGS@ DNSSDLIBS = @DNSSDLIBS@ IPPFIND_BIN = @IPPFIND_BIN@ IPPFIND_MAN = @IPPFIND_MAN@ -APPARMORLIBS = @APPARMORLIBS@ -SNAPDGLIBLIBS = @SNAPDGLIBLIBS@ LDFLAGS = @LDFLAGS@ LINKCUPS = @LINKCUPS@ LINKCUPSSTATIC = ../cups/$(LIBCUPSSTATIC) $(LIBS) @@ -183,7 +181,7 @@ ONDEMANDLIBS = @ONDEMANDLIBS@ OPTIM = @OPTIM@ OPTIONS = @WARNING_OPTIONS@ PAMLIBS = @PAMLIBS@ -SERVERLIBS = @SERVERLIBS@ +SERVERLIBS = @SERVERLIBS@ @APPARMORLIBS@ @SNAPDGLIBLIBS@ TLSFLAGS = @TLSFLAGS@ TLSLIBS = @TLSLIBS@ UNITTESTS = @UNITTESTS@ diff --git a/config-scripts/cups-container.m4 b/config-scripts/cups-container.m4 index 2d865b99c8..2190103fdd 100644 --- a/config-scripts/cups-container.m4 +++ b/config-scripts/cups-container.m4 @@ -7,114 +7,52 @@ dnl Licensed under Apache License v2.0. See the file "LICENSE" for more dnl information. dnl -AC_ARG_WITH([container], AS_HELP_STRING([--with-container=...], [configure to use in container ('none', 'snap')])) -AC_ARG_WITH([container_name], AS_HELP_STRING([--with-container-name=...], [specify the container name (default='cups')])) +dnl Specify a container mode +CONTAINER="none" - - - -AC_ARG_ENABLE([snapped_cupsd], AS_HELP_STRING([--enable-snapped-cupsd], [enable support for packaging CUPS in a Snap])) -AC_ARG_ENABLE([snapped_clients], AS_HELP_STRING([--enable-snapped-clients], [enable support for CUPS controlling admin access from snapped clients])) -AC_ARG_WITH([our-snap-name], AS_HELP_STRING([--with-our-snap-name], [Set name of the Snap we are snapped in, only needed with --enable-snapped-cupsd, default=cups]), [ - OUR_SNAP_NAME="$withval" -], [ - OUR_SNAP_NAME="cups" +AC_ARG_WITH([container], AS_HELP_STRING([--with-container=...], [configure to use in container ('none', 'snap')]), [ + CONTAINER="$withval" ]) -AC_DEFINE_UNQUOTED([OUR_SNAP_NAME], ["$OUR_SNAP_NAME"], [Name of the Snap we are snapped in.]) -AC_ARG_WITH([snapctl], AS_HELP_STRING([--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"], [Location of snapctl program.]) -AC_ARG_WITH([cups_control_slot], AS_HELP_STRING([--with-cups-control-slot], [Name for cups-control slot as defined in snapcraft.yaml, only needed with --enable-snapped-cupsd, default=cups-control]), [ - CUPS_CONTROL_SLOT="$withval" -], [ - CUPS_CONTROL_SLOT="cups-control" + +AS_CASE(["$CONTAINER"], [none], [ + # No container in use +], [snap], [ + # Building as a snap + AC_DEFINE([CUPS_SNAP], [1], [Building as a snap (snapcraft.io)?]) +], [*], [ + AC_MSG_ERROR([Unsupported container '$CONTAINER' specified.]) ]) -AC_DEFINE_UNQUOTED([CUPS_CONTROL_SLOT], ["$CUPS_CONTROL_SLOT"], ["cups-control" slot name for snap.]) + +dnl Supporting libraries for different containers... APPARMORLIBS="" SNAPDGLIBLIBS="" -ENABLE_SNAPPED_CUPSD="NO" -ENABLE_SNAPPED_CLIENTS="NO" - -dnl Both --enable-snapped-cupsd and --enable-snapped-clients are about -dnl additional access control for clients, allowing clients which are confined -dnl Snaps only to do adminstrative tasks (create queues, delete someone else's -dnl jobs, ...) if they plug the "cups-control" interface, so -dnl --enable-snapped-cupsd implies --enable-snapped-clients. The difference is -dnl only the method how to determine whether a client Snap is confined and plugs -dnl "cups-control". -AS_IF([test x$enable_snapped_cupsd = xyes], [ - enable_snapped_clients="yes" -]) +AC_SUBST([APPARMORLIBS]) +AC_SUBST([SNAPDGLIBLIBS]) -AS_IF([test "x$PKGCONFIG" != x -a x$enable_snapped_clients = xyes], [ +AS_IF(["x$PKGCONFIG" != x], [ AC_MSG_CHECKING([for libapparmor]) AS_IF([$PKGCONFIG --exists libapparmor], [ AC_MSG_RESULT([yes]) - CFLAGS="$CFLAGS $($PKGCONFIG --cflags libapparmor)" - APPARMORLIBS="$($PKGCONFIG --libs libapparmor)" - AC_DEFINE([HAVE_APPARMOR], [1], [Have the apparmor library?]) + CFLAGS="$CFLAGS $($PKGCONFIG --cflags libapparmor)" + APPARMORLIBS="$($PKGCONFIG --libs libapparmor)" + AC_DEFINE([HAVE_LIBAPPARMOR], [1], [Have the apparmor library?]) AC_MSG_CHECKING([for libsnapd-glib]) - AS_IF([$PKGCONFIG --exists snapd-glib glib-2.0 gio-2.0], [ 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], [1], [Have the snapd-glib library?]) - ], [ - AC_MSG_RESULT([no]) - ]) - - AS_IF([test x$enable_snapped_cupsd = xyes], [ - AC_CHECK_LIB([snapd-glib], [snapd_client_run_snapctl2_sync], [ - AC_DEFINE([HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC], [1], [Have snapd_client_run_snapctl2_sync function?]) - AC_DEFINE([SUPPORT_SNAPPED_CUPSD], [1], [Support snapped cupsd?]) - AC_DEFINE([SUPPORT_SNAPPED_CLIENTS], [1], [Support snapped CUPS clients?]) - ENABLE_SNAPPED_CUPSD="YES" - ENABLE_SNAPPED_CLIENTS="YES" - ], [ - AS_IF([test "x$SNAPDGLIBLIBS" != "x"], [ - SNAPDGLIBLIBS="" - ]) - AC_PATH_TOOL([SNAPCTL], [snapctl]) - AC_MSG_CHECKING([for snapctl is-connected support]) - AS_IF([test "x$SNAPCTL" != x && $SNAPCTL is-connected --help >/dev/null 2>&1], [ - AC_MSG_RESULT([yes]) - AC_DEFINE([HAVE_SNAPCTL_IS_CONNECTED], [1], [Have snapctl is-connected command?]) - AC_DEFINE([SUPPORT_SNAPPED_CUPSD], [1], [Support snapped cupsd?]) - AC_DEFINE([SUPPORT_SNAPPED_CLIENTS], [1], [Support snapped CUPS clients?]) - ENABLE_SNAPPED_CUPSD="YES" - ENABLE_SNAPPED_CLIENTS="YES" - ], [ - AC_MSG_RESULT([no]) - ]) + AC_DEFINE([HAVE_LIBSNAPDGLIB], [1], [Have the snapd-glib library?]) + SAVELIBS="$LIBS" + LIBS="$SNAPDGLIBLIBS $LIBS" + AC_CHECK_FUNC([snapd_client_run_snapctl2_sync], [ + AC_DEFINE([HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC], [1], [Have the snapd_client_run_snapctl2_sync function?]) ]) + LIBS="$SAVELIBS" ], [ - AS_IF([test "x$SNAPDGLIBLIBS" != "x"], [ - AC_DEFINE([SUPPORT_SNAPPED_CLIENTS], [1], [Support snapped CUPS clients?]) - ENABLE_SNAPPED_CLIENTS="YES" - ]) + AC_MSG_RESULT([no]) ]) - ], [ - AC_MSG_RESULT([no]) ]) ]) - -AC_MSG_CHECKING([for Snap support]) -AS_IF([test "x$ENABLE_SNAPPED_CLIENTS" != "xNO"], [ - AS_IF([test "x$ENABLE_SNAPPED_CUPSD" != "xNO"], [ - AC_MSG_RESULT([yes: cupsd + clients]) - ], [ - AC_MSG_RESULT([yes: clients only]) - ]) -], [ - AC_MSG_RESULT([no]) -]) - -AC_SUBST([APPARMORLIBS]) -AC_SUBST([SNAPDGLIBLIBS]) diff --git a/config.h.in b/config.h.in index c9879ddba3..41ec7fa220 100644 --- a/config.h.in +++ b/config.h.in @@ -684,19 +684,31 @@ static __inline int _cups_abs(int i) { return (i < 0 ? -i : i); } /* - * Do we have snap container support enabled? + * Building as a snap (snapcraft.io)? */ -#undef CUPS_SNAP_NAME -#undef CUPS_SNAP_CONTROL_SLOT +#undef CUPS_SNAP + + +/* + * Have the apparmor library? + */ + +#undef HAVE_LIBAPPARMOR + + +/* + * Have the snapd-glib library? + */ + +#undef HAVE_LIBSNAPDGLIB + + +/* + * Have the snapd_client_run_snapctl2_sync function? + */ -#undef HAVE_APPARMOR -#undef HAVE_SNAPDGLIB #undef HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC -#undef HAVE_SNAPCTL_IS_CONNECTED -#undef SNAPCTL -#undef SUPPORT_SNAPPED_CUPSD -#undef SUPPORT_SNAPPED_CLIENTS #endif /* !_CUPS_CONFIG_H_ */ diff --git a/scheduler/Makefile b/scheduler/Makefile index 3d5ed065df..41b45390fa 100644 --- a/scheduler/Makefile +++ b/scheduler/Makefile @@ -318,15 +318,14 @@ 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) \ - $(APPARMORLIBS) $(SNAPDGLIBLIBS) + $(ONDEMANDLIBS) $(LIBWRAP) $(LIBGSSAPI) $(COMMONLIBS) $(LINKCUPS) $(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) $(APPARMORLIBS) $(SNAPDGLIBLIBS) $(LINKCUPSSTATIC) + $(LIBWRAP) $(LINKCUPSSTATIC) $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@ diff --git a/scheduler/auth.c b/scheduler/auth.c index f29ceb497d..d124bfd1d5 100644 --- a/scheduler/auth.c +++ b/scheduler/auth.c @@ -52,15 +52,13 @@ typedef struct sockpeercred cupsd_ucred_t; # endif # define CUPSD_UCRED_UID(c) (c).uid #endif /* HAVE_SYS_UCRED_H */ -#ifdef SUPPORT_SNAPPED_CLIENTS -# ifdef HAVE_APPARMOR -# include -# endif -# ifdef HAVE_SNAPDGLIB -# include -# include -# endif -#endif /* SUPPORT_SNAPPED_CLIENTS */ +#ifdef HAVE_LIBAPPARMOR +# include +#endif /* HAVE_LIBAPPARMOR */ +#ifdef HAVE_LIBSNAPDGLIB +# include +# include +#endif /* HAVE_LIBSNAPDGLIB */ /* From 8e46966dd352dff5306de5fbd38086c5ba3bdb3d Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Wed, 6 Oct 2021 10:42:34 -0400 Subject: [PATCH 7/8] Clean up and simplify snap support. --- config-scripts/cups-container.m4 | 2 +- configure | 348 ++---------------- scheduler/auth.c | 612 +++++++++---------------------- scheduler/cert.c | 16 +- scheduler/conf.c | 13 +- scheduler/cups-exec.c | 10 +- scheduler/cupsfilter.c | 131 +++---- scheduler/env.c | 21 +- scheduler/process.c | 12 +- 9 files changed, 294 insertions(+), 871 deletions(-) diff --git a/config-scripts/cups-container.m4 b/config-scripts/cups-container.m4 index 2190103fdd..278f0d9a24 100644 --- a/config-scripts/cups-container.m4 +++ b/config-scripts/cups-container.m4 @@ -10,7 +10,7 @@ dnl dnl Specify a container mode CONTAINER="none" -AC_ARG_WITH([container], AS_HELP_STRING([--with-container=...], [configure to use in container ('none', 'snap')]), [ +AC_ARG_WITH([container], AS_HELP_STRING([--with-container=...], [configure to use in container (none, snap)]), [ CONTAINER="$withval" ]) diff --git a/configure b/configure index 43bcf2daa2..f2161c2536 100755 --- a/configure +++ b/configure @@ -694,7 +694,6 @@ ONDEMANDLIBS ONDEMANDFLAGS SNAPDGLIBLIBS APPARMORLIBS -SNAPCTL IPPFIND_MAN IPPFIND_BIN DNSSD_BACKEND @@ -904,11 +903,7 @@ enable_largefile with_dnssd with_dnssd_libs with_dnssd_includes -enable_snapped_cupsd -enable_snapped_clients -with_our_snap_name -with_snapctl -with_cups_control_slot +with_container with_ondemand with_systemd with_smfmanifestdir @@ -1599,10 +1594,6 @@ Optional Features: --enable-gssapi enable (deprecated) GSSAPI/Kerberos support --disable-pam disable PAM support --disable-largefile omit support for large files - --enable-snapped-cupsd enable support for packaging CUPS in a Snap - --enable-snapped-clients - enable support for CUPS controlling admin access - from snapped clients --enable-page-logging enable page_log by default --enable-sync-on-close enable SyncOnClose (off by default) --disable-browsing disable Browsing by default @@ -1641,14 +1632,7 @@ Optional Packages: mdnsresponder, no, yes) --with-dnssd-libs set directory for DNS Service Discovery library --with-dnssd-includes set directory for DNS Service Discovery header files - --with-our-snap-name Set name of the Snap we are snapped in, only needed - with --enable-snapped-cupsd, default=cups - --with-snapctl Set path for snapctl, only needed with - --enable-snapped-cupsd, default=/usr/bin/snapctl - --with-cups-control-slot - Name for cups-control slot as defined in - snapcraft.yaml, only needed with - --enable-snapped-cupsd, default=cups-control + --with-container=... configure to use in container (none, snap) --with-ondemand=... Specify the on-demand launch interface (launchd, systemd, upstart) --with-systemd set directory for systemd service files @@ -10763,80 +10747,45 @@ fi -# Check whether --enable-snapped_cupsd was given. -if test ${enable_snapped_cupsd+y} -then : - enableval=$enable_snapped_cupsd; -fi +CONTAINER="none" -# Check whether --enable-snapped_clients was given. -if test ${enable_snapped_clients+y} -then : - enableval=$enable_snapped_clients; -fi - -# Check whether --with-our-snap-name was given. -if test ${with_our_snap_name+y} +# Check whether --with-container was given. +if test ${with_container+y} then : - withval=$with_our_snap_name; - OUR_SNAP_NAME="$withval" - -else $as_nop - - OUR_SNAP_NAME="cups" + withval=$with_container; + CONTAINER="$withval" fi -printf "%s\n" "#define OUR_SNAP_NAME \"$OUR_SNAP_NAME\"" >>confdefs.h - - -# Check whether --with-snapctl was given. -if test ${with_snapctl+y} -then : - withval=$with_snapctl; - SNAPCTL="$withval" - -else $as_nop - - SNAPCTL="/usr/bin/snapctl" +case "$CONTAINER" in #( + none) : -fi - - -printf "%s\n" "#define SNAPCTL \"$SNAPCTL\"" >>confdefs.h - - -# Check whether --with-cups_control_slot was given. -if test ${with_cups_control_slot+y} -then : - withval=$with_cups_control_slot; - CUPS_CONTROL_SLOT="$withval" - -else $as_nop + # No container in use + ;; #( + snap) : - CUPS_CONTROL_SLOT="cups-control" + # Building as a snap -fi +printf "%s\n" "#define CUPS_SNAP 1" >>confdefs.h + ;; #( + *) : -printf "%s\n" "#define CUPS_CONTROL_SLOT \"$CUPS_CONTROL_SLOT\"" >>confdefs.h + as_fn_error $? "Unsupported container '$CONTAINER' specified." "$LINENO" 5 + ;; #( + *) : + ;; +esac APPARMORLIBS="" SNAPDGLIBLIBS="" -ENABLE_SNAPPED_CUPSD="NO" -ENABLE_SNAPPED_CLIENTS="NO" -if test x$enable_snapped_cupsd = xyes -then : - enable_snapped_clients="yes" -fi - -if test "x$PKGCONFIG" != x -a x$enable_snapped_clients = xyes +if "x$PKGCONFIG" != x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libapparmor" >&5 @@ -10847,15 +10796,14 @@ then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - CFLAGS="$CFLAGS $($PKGCONFIG --cflags libapparmor)" - APPARMORLIBS="$($PKGCONFIG --libs libapparmor)" + CFLAGS="$CFLAGS $($PKGCONFIG --cflags libapparmor)" + APPARMORLIBS="$($PKGCONFIG --libs libapparmor)" -printf "%s\n" "#define HAVE_APPARMOR 1" >>confdefs.h +printf "%s\n" "#define HAVE_LIBAPPARMOR 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libsnapd-glib" >&5 printf %s "checking for libsnapd-glib... " >&6; } - if $PKGCONFIG --exists snapd-glib glib-2.0 gio-2.0 then : @@ -10864,266 +10812,34 @@ printf "%s\n" "yes" >&6; } CFLAGS="$CFLAGS $($PKGCONFIG --cflags snapd-glib glib-2.0 gio-2.0)" SNAPDGLIBLIBS="$($PKGCONFIG --libs snapd-glib glib-2.0 gio-2.0)" -printf "%s\n" "#define HAVE_SNAPDGLIB 1" >>confdefs.h - - -else $as_nop - - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } - -fi - - if test x$enable_snapped_cupsd = xyes -then : +printf "%s\n" "#define HAVE_LIBSNAPDGLIB 1" >>confdefs.h - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for snapd_client_run_snapctl2_sync in -lsnapd-glib" >&5 -printf %s "checking for snapd_client_run_snapctl2_sync in -lsnapd-glib... " >&6; } -if test ${ac_cv_lib_snapd_glib_snapd_client_run_snapctl2_sync+y} -then : - printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS -LIBS="-lsnapd-glib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char snapd_client_run_snapctl2_sync (); -int -main (void) -{ -return snapd_client_run_snapctl2_sync (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO" -then : - ac_cv_lib_snapd_glib_snapd_client_run_snapctl2_sync=yes -else $as_nop - ac_cv_lib_snapd_glib_snapd_client_run_snapctl2_sync=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_snapd_glib_snapd_client_run_snapctl2_sync" >&5 -printf "%s\n" "$ac_cv_lib_snapd_glib_snapd_client_run_snapctl2_sync" >&6; } -if test "x$ac_cv_lib_snapd_glib_snapd_client_run_snapctl2_sync" = xyes + SAVELIBS="$LIBS" + LIBS="$SNAPDGLIBLIBS $LIBS" + ac_fn_c_check_func "$LINENO" "snapd_client_run_snapctl2_sync" "ac_cv_func_snapd_client_run_snapctl2_sync" +if test "x$ac_cv_func_snapd_client_run_snapctl2_sync" = xyes then : printf "%s\n" "#define HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC 1" >>confdefs.h -printf "%s\n" "#define SUPPORT_SNAPPED_CUPSD 1" >>confdefs.h - - -printf "%s\n" "#define SUPPORT_SNAPPED_CLIENTS 1" >>confdefs.h - - ENABLE_SNAPPED_CUPSD="YES" - ENABLE_SNAPPED_CLIENTS="YES" - -else $as_nop - - if test "x$SNAPDGLIBLIBS" != "x" -then : - - SNAPDGLIBLIBS="" - fi - if test -n "$ac_tool_prefix"; then - # Extract the first word of "${ac_tool_prefix}snapctl", so it can be a program name with args. -set dummy ${ac_tool_prefix}snapctl; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_path_SNAPCTL+y} -then : - printf %s "(cached) " >&6 -else $as_nop - case $SNAPCTL in - [\\/]* | ?:[\\/]*) - ac_cv_path_SNAPCTL="$SNAPCTL" # Let the user override the test with a path. - ;; - *) - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_path_SNAPCTL="$as_dir$ac_word$ac_exec_ext" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - ;; -esac -fi -SNAPCTL=$ac_cv_path_SNAPCTL -if test -n "$SNAPCTL"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SNAPCTL" >&5 -printf "%s\n" "$SNAPCTL" >&6; } -else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -fi - - -fi -if test -z "$ac_cv_path_SNAPCTL"; then - ac_pt_SNAPCTL=$SNAPCTL - # Extract the first word of "snapctl", so it can be a program name with args. -set dummy snapctl; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_path_ac_pt_SNAPCTL+y} -then : - printf %s "(cached) " >&6 -else $as_nop - case $ac_pt_SNAPCTL in - [\\/]* | ?:[\\/]*) - ac_cv_path_ac_pt_SNAPCTL="$ac_pt_SNAPCTL" # Let the user override the test with a path. - ;; - *) - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_path_ac_pt_SNAPCTL="$as_dir$ac_word$ac_exec_ext" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - - ;; -esac -fi -ac_pt_SNAPCTL=$ac_cv_path_ac_pt_SNAPCTL -if test -n "$ac_pt_SNAPCTL"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_pt_SNAPCTL" >&5 -printf "%s\n" "$ac_pt_SNAPCTL" >&6; } -else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -fi - - if test "x$ac_pt_SNAPCTL" = x; then - SNAPCTL="" - else - case $cross_compiling:$ac_tool_warned in -yes:) -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} -ac_tool_warned=yes ;; -esac - SNAPCTL=$ac_pt_SNAPCTL - fi -else - SNAPCTL="$ac_cv_path_SNAPCTL" -fi - - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for snapctl is-connected support" >&5 -printf %s "checking for snapctl is-connected support... " >&6; } - if test "x$SNAPCTL" != x && $SNAPCTL is-connected --help >/dev/null 2>&1 -then : - - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } - -printf "%s\n" "#define HAVE_SNAPCTL_IS_CONNECTED 1" >>confdefs.h - - -printf "%s\n" "#define SUPPORT_SNAPPED_CUPSD 1" >>confdefs.h - - -printf "%s\n" "#define SUPPORT_SNAPPED_CLIENTS 1" >>confdefs.h - - ENABLE_SNAPPED_CUPSD="YES" - ENABLE_SNAPPED_CLIENTS="YES" - -else $as_nop - - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } - -fi - -fi - - -else $as_nop - - if test "x$SNAPDGLIBLIBS" != "x" -then : - - -printf "%s\n" "#define SUPPORT_SNAPPED_CLIENTS 1" >>confdefs.h - - ENABLE_SNAPPED_CLIENTS="YES" - -fi - -fi + LIBS="$SAVELIBS" else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Snap support" >&5 -printf %s "checking for Snap support... " >&6; } -if test "x$ENABLE_SNAPPED_CLIENTS" != "xNO" -then : - - if test "x$ENABLE_SNAPPED_CUPSD" != "xNO" -then : - - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes: cupsd + clients" >&5 -printf "%s\n" "yes: cupsd + clients" >&6; } - -else $as_nop - - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes: clients only" >&5 -printf "%s\n" "yes: clients only" >&6; } - -fi - -else $as_nop - - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } - fi - - - ONDEMANDFLAGS="" ONDEMANDLIBS="" diff --git a/scheduler/auth.c b/scheduler/auth.c index d124bfd1d5..0de70e6b1b 100644 --- a/scheduler/auth.c +++ b/scheduler/auth.c @@ -65,7 +65,7 @@ typedef struct sockpeercred cupsd_ucred_t; * Local functions... */ -static int check_admin_task(cupsd_client_t *con); +static int check_admin_access(cupsd_client_t *con); #ifdef HAVE_AUTHORIZATION_H static int check_authref(cupsd_client_t *con, const char *right); #endif /* HAVE_AUTHORIZATION_H */ @@ -1841,7 +1841,7 @@ cupsdIsAuthorized(cupsd_client_t *con, /* I - Connection */ if (!_cups_strcasecmp(name, "@SYSTEM")) { for (i = 0; i < NumSystemGroups; i ++) - if (cupsdCheckGroup(username, pw, SystemGroups[i]) && check_admin_task(con)) + if (cupsdCheckGroup(username, pw, SystemGroups[i]) && check_admin_access(con)) return (HTTP_OK); } } @@ -1884,7 +1884,7 @@ cupsdIsAuthorized(cupsd_client_t *con, /* I - Connection */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdIsAuthorized: Checking group \"%s\" membership...", name); for (i = 0; i < NumSystemGroups; i ++) - if (cupsdCheckGroup(username, pw, SystemGroups[i]) && check_admin_task(con)) + if (cupsdCheckGroup(username, pw, SystemGroups[i]) && check_admin_access(con)) return (HTTP_OK); } } @@ -1940,481 +1940,209 @@ cupsdNewLocation(const char *location) /* I - Location path */ /* - * 'check_admin_task()' - Do additional checks on administrative tasks + * 'check_admin_access()' - Verify that the client has administrative access. */ -static int /* O - 1 if admin task authorized */ -check_admin_task(cupsd_client_t *con) /* I - Connection */ +static int // O - 1 if authorized, 0 otherwise +check_admin_access(cupsd_client_t *con) // I - Client connection { - int ret = 1; /* Return value */ - - - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Administrative task"); - +#if defined(HAVE_LIBAPPARMOR) && defined(HAVE_LIBSNAPDGLIB) /* * 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(SUPPORT_SNAPPED_CLIENTS) - - /* - * Get the client's file descriptor and from this its AppArmor context + * 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". Otherwise deny + * access. */ - if (httpAddrFamily(con->http->hostaddr) == AF_LOCAL) - { - int peerfd; /* Peer's file descriptor */ - - peerfd = httpGetFd(con->http); - - if (peerfd < 0) - { - cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: Unable to get peer file descriptor of client connecting via domain socket"); - } - else - { - char *context = NULL; /* AppArmor profile name of client */ -# undef CHECK_METHOD_FOUND -# ifdef SUPPORT_SNAPPED_CUPSD - int status = 65535; /* Status of client Snap context check */ -# if defined(HAVE_SNAPDGLIB) && defined(HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC) -# define CHECK_METHOD_FOUND 1 - char *args[] = { "is-connected", "--apparmor-label", NULL, CUPS_CONTROL_SLOT, NULL }; /* snapctl arguments */ - SnapdClient *client = NULL; /* Data structure of snapd access */ - const char *cookie; /* snapd access cookie */ - GError *error = NULL; /* Glib error */ -# else -# ifdef HAVE_SNAPCTL_IS_CONNECTED -# define CHECK_METHOD_FOUND 1 - char *args[] = { SNAPCTL, "is-connected", "--apparmor-label", NULL, CUPS_CONTROL_SLOT, NULL }; /* 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 */ - char buf[1024]; /* Buffer for snapctl's stderr output */ - int wstatus; /* Wait result of forked snapctl process */ -# endif /* HAVE_SNAPCTL_IS_CONNECTED */ -# endif /* HAVE_SNAPDGLIB && HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC */ -# else -# if !defined(SUPPORT_SNAPPED_CUPSD) && defined(HAVE_SNAPDGLIB) -# define CHECK_METHOD_FOUND 1 - char *snap_name = NULL; /* Client Snap name */ - char *dot; /* Pointer to dot in AppArmor profile name */ - SnapdClient *snapd = NULL; /* Data structure of snapd access */ - GError *error = NULL; /* Glib error */ - SnapdSnap *snap = NULL; /* Data structure of client Snap */ - GPtrArray *plugs = NULL; /* Plug search result of client Snap */ -# endif /* !SUPPORT_SNAPPED_CUPSD && HAVE_SNAPDGLIB */ -# endif /* SUPPORT_SNAPPED_CUPSD */ - - -# ifndef SUPPORT_SNAPPED_CUPSD - - /* If AppArmor is not enabled, then we can't identify the client */ - /* 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()) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: No AppArmor in use"); - goto snap_check_done; - } - -# endif /* !SUPPORT_SNAPPED_CUPSD */ - - if (aa_getpeercon(peerfd, &context, NULL) < 0) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: AppArmor profile could not be retrieved for client process - Error: %s", strerror(errno)); - goto snap_check_done; - } else - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: AppArmor profile of client process: %s", context); - -# ifdef OUR_SNAP_NAME - /* Is the client one of the utilities of our Snap? */ - if (!strncmp(context, "snap." OUR_SNAP_NAME ".", strlen(OUR_SNAP_NAME) + 6)) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap is the same Snap we are running in, access granted"); - goto snap_check_done; - } -# endif /* OUR_SNAP_NAME */ - -# ifdef SUPPORT_SNAPPED_CUPSD - - /* - * Run - * - * snapctl is-connected --apparmor-label AA_CONTEXT CUPS_CONTROL_SLOT - * - * or an equivalent library function call using the - * snapd_client_run_snapctl2_sync() function of libsnapd-glib. - * - * Here AA_CONTEXT is 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/function call 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 snapctl/the function - * 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. - */ - -# if defined(HAVE_SNAPDGLIB) && defined(HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC) - - /* - * Use the snapd_client_run_snapctl2_sync() function of libsnapd-glib - */ - - /* Insert client Snap context in snapctl arguments */ - args[2] = context; - - /* Connect to snapd */ - client = snapd_client_new(); - if (!client) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Could not connect to snapd, permission denied"); - ret = 0; - goto snap_check_done; - } - - /* snapctl commands are sent over a this socket that is made - available within the snap sandbox */ - snapd_client_set_socket_path(client, "/run/snapd-snap.socket"); + int fd = httpGetFd(con->http); + // Client socket file descriptor + char *context = NULL; // AppArmor profile name of client + SnapdClient *client = NULL; // Data structure of snapd access + GError *error = NULL; // Glib error + int ret = 1; // Return value - /* Take cookie from the environment if available */ - cookie = g_getenv("SNAP_COOKIE"); - if (!cookie) - { - cookie = ""; - cupsdLogMessage(CUPSD_LOG_WARN, "check_admin_task: No SNAP_COOKIE set in the Snap environment, client Snap context check may not work"); - } - - /* Do the client Snap context check */ - if (!snapd_client_run_snapctl2_sync(client, cookie, args, NULL, NULL, &status, NULL, &error)) { - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap context check error - %s", error->message); - ret = 0; - goto snap_check_done; - } - -# else -# ifdef HAVE_SNAPCTL_IS_CONNECTED - - /* - * Call the snapctl executable using execv() in a fork - */ - /* Insert client Snap context in snapctl command line */ - args[3] = context; - - /* Create a pipe to catch stderr output from snapctl */ - if (pipe(fds)) - { - fds[0] = -1; - fds[1] = -1; - cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: 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, "check_admin_task: 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, "check_admin_task: 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, "check_admin_task: 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, "check_admin_task: 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, "check_admin_task: Running command: " SNAPCTL " is-connected --apparmor-label %s " CUPS_CONTROL_SLOT, context); - execv(SNAPCTL, args); - cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: Unable to launch snapctl: %s", strerror(errno)); - exit(100); - } - else if (pid < 0) - { - cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: Unable to fork for snapctl call - %s", strerror(errno)); - ret = 0; - goto snap_check_done; - } - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: 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, "check_admin_task: Error message from snapctl: %s", buf); - ret = 0; - } - cupsFileClose(snapctl_stderr); - } - close(fds[0]); +# ifdef AF_LOCAL + // Only check domain sockets... + if (httpAddrFamily(con->http->hostaddr) != AF_LOCAL) + return (1); +# endif // AF_LOCAL - /* Wait for snapctl to finish */ - retry_wait: - if (waitpid (pid, &wstatus, 0) == -1) - { - if (errno == EINTR) - goto retry_wait; - cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: 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, "check_admin_task: snapctl (PID %d) exited with no errors.", pid); +# if !CUPS_SNAP + // If AppArmor is not enabled, then we can't identify the client... + if (!aa_is_enabled()) + { + cupsdLogClient(con, CUPSD_LOG_DEBUG, "AppArmor not in use."); + return (1); + } +# endif /* !CUPS_SNAP */ - /* How did snapctl terminate */ - if (WIFEXITED(wstatus)) - { - /* Via regular exit */ - status = WEXITSTATUS(wstatus); - if (status == 100) - { - cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: snapctl not executed"); - ret = 0; - goto snap_check_done; - } - } - else if (WIFSIGNALED(wstatus)) - { - /* Via signal */ - cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: snapctl caught the signal %d", WTERMSIG(wstatus)); - ret = 0; - goto snap_check_done; - } + // Get the client's AppArmor context using the socket... + if (aa_getpeercon(fd, &context, NULL) < 0) + { + cupsdLogClient(con, CUPSD_LOG_DEBUG, "AppArmor profile could not be retrieved: %s", strerror(errno)); + return (1); + } + else + { + cupsdLogClient(con, CUPSD_LOG_DEBUG, "AppArmor profile is '%s'.", context); + } -# endif /* HAVE_SNAPCTL_IS_CONNECTED */ -# endif /* HAVE_SNAPDGLIB && HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC */ + // Allow access from "cups" snap... + if (!strncmp(context, "snap.cups.", 10)) + goto done; - switch (status) - { - case 0 : - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap connecting via \"cups-control\" interface, access granted"); - break; - case 1 : - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap does not connect via \"cups-control\" interface, permission denied"); - ret = 0; - break; - case 10 : - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap under classic confinement, access granted"); - break; - case 11 : - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client is not a Snap, access granted"); - break; - default : - cupsdLogMessage(CUPSD_LOG_ERROR, "check_admin_task: snapctl exited with unknown status: %d", status); - ret = 0; - break; - } +# if CUPS_SNAP && defined(HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC) + /* + * CUPS is snapped, so check whether the client is also snapped. If so, + * determine whether the client snap has a "cups-control" plug which allows + * the application to perform CUPS administrative tasks. + */ - snap_check_done: - if (context) - free(context); -# if defined(HAVE_SNAPDGLIB) && defined(HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC) - g_clear_object(&client); -# endif /* HAVE_SNAPDGLIB && HAVE_SNAPD_CLIENT_RUN_SNAPCTL2_SYNC */ + const char *cookie; // snapd access cookie + int status = 65535; // Status of client Snap context check + const char *args[] = // snapctl arguments + { + "is-connected", + "--apparmor-label", + NULL, + "cups-control", + NULL + }; + + // Connect to snapd + if ((client = snapd_client_new()) == NULL) + { + cupsdLogClient(con, CUPSD_LOG_ERROR, "Unable to connect to snapd."); + ret = 0; + goto done; + } -# else -# if !defined(SUPPORT_SNAPPED_CUPSD) && defined(HAVE_SNAPDGLIB) + // snapctl commands are sent over a domain socket + snapd_client_set_socket_path(client, "/run/snapd-snap.socket"); - /* - * If the client is a Snap, extract the client Snap's name from - * 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 - * - 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 this is - * the only way to check Snap status on clients from an unsnapped - * cupsd. - */ + // Take cookie from the environment if available + if ((cookie = g_getenv("SNAP_COOKIE")) == NULL) + { + cookie = ""; + cupsdLogClient(CUPSD_LOG_WARN, "No SNAP_COOKIE set in the Snap environment."); + } - /* 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, "check_admin_task: AppArmor context not from a Snap"); - goto snap_check_done; - } + // Do the client Snap context check... + args[2] = context; - dot = strchr(context + 5, '.'); - if (dot == NULL) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: 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, "check_admin_task: Client is the Snap %s", snap_name); + if (!snapd_client_run_snapctl2_sync(client, cookie, args, NULL, NULL, &status, NULL, &error)) + { + cupsdLogClient(con, CUPSD_LOG_ERROR, "Unable to check snap context: %s", error->message); + ret = 0; + goto done; + } - /* Connect to snapd */ - snapd = snapd_client_new(); - if (!snapd) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Could not connect to snapd, permission denied"); + switch (status) + { + case 0 : // The client is a confined Snap and plugs cups-control + cupsdLogClient(con, CUPSD_LOG_DEBUG, "Snap with cups-control plug - allowed."); + break; + case 1 : // The client is a confined Snap and does not plug cups-control + cupsdLogClient(con, CUPSD_LOG_DEBUG, "Snap without cups-control plug - 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, "check_admin_task: Could not obtain client Snap data: \"%s\", permission denied", error->message); + break; + case 10 : // The client is a classic Snap + cupsdLogClient(con, CUPSD_LOG_DEBUG, "Classic snap - allowed."); + break; + case 11 : // The client is not a Snap + cupsdLogClient(con, CUPSD_LOG_DEBUG, "Not a snap - allowed."); + break; + default : // Unexpected status... + cupsdLogClient(con, CUPSD_LOG_ERROR, "Snap check returned unexpected status %d - denied.", status); ret = 0; - goto snap_check_done; - } + break; + } - /* Snaps using classic confinement are granted access */ - if (snapd_snap_get_confinement(snap) == SNAPD_CONFINEMENT_CLASSIC) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap under classic confinement, access granted"); - goto snap_check_done; - } +# elif !CUPS_SNAP + /* + * If CUPS is not snapped, check whether the client is snapped and if it has + * the "cups-control" plug. + */ - /* 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, "check_admin_task: Could not obtain the client Snap's interface connections: \"%s\", permission denied", error->message); - ret = 0; - goto snap_check_done; - } + // Is the client a snapped application? + if (strncmp(context, "snap.", 5)) + { + cupsdLogClient(con, CUPSD_LOG_DEBUG, "Not a snap - allowed."); + goto done; + } - if (plugs->len <= 0) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap does not connect via \"cups-control\" interface, permission denied"); - ret = 0; - goto snap_check_done; - } + // Extract the snap name from the context (snap.name.instance) + char *snap_name = strdup(context + 5);// Snap name follows "snap." + char *ptr = strchr(snap_name, '.'); // instance follows the name... + if (!ptr) + { + cupsdLogClient(con, CUPSD_LOG_DEBUG, "Malformed snapd AppArmor profile name '%s' - denied.", context); + free(snap_name); + ret = 0; + goto done; + } - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Client Snap connecting via \"cups-control\" interface, access granted"); + *ptr = '\0'; + cupsdLogClient(con, CUPSD_LOG_DEBUG, "Client snap is '%s'.", snap_name); - snap_check_done: - if (context) - free(context); - if (snap_name) - free(snap_name); - g_clear_object(&snapd); - g_clear_object(&snap); - if (plugs) - g_ptr_array_unref(plugs); + // Connect to snapd + if ((client = snapd_client_new()) == NULL) + { + cupsdLogClient(con, CUPSD_LOG_ERROR, "Unable to connect to snapd."); + free(snap_name); + ret = 0; + goto done; + } -# endif /* !SUPPORT_SNAPPED_CUPSD && HAVE_SNAPDGLIB */ -# endif /* SUPPORT_SNAPPED_CUPSD */ + // Check whether the client Snap is under classic confinement + GPtrArray *plugs = NULL; // List of plugs for snap -# ifndef CHECK_METHOD_FOUND + if ((snap = snapd_client_get_snap_sync(client, snap_name, NULL, &error)) == NULL) + { + cupsdLogClient(con, CUPSD_LOG_DEBUG, "Unable to get client Snap data: %s", error->message); + ret = 0; + } + // Snaps using classic confinement are granted access + else if (snapd_snap_get_confinement(snap) == SNAPD_CONFINEMENT_CLASSIC) + { + cupsdLogClient(con, CUPSD_LOG_DEBUG, "Classic snap - allowed."); + } + // Check whether the client Snap has the cups-control plug + else if (!snapd_client_get_connections2_sync(snapd, SNAPD_GET_CONNECTIONS_FLAGS_NONE, snap_name, "cups-control", NULL, NULL, &plugs, NULL, NULL, &error)) + { + cupsdLogClient(con, CUPSD_LOG_DEBUG, "Unable to get client Snap plugs: %s", error->message); + ret = 0; + } + else if (plugs->len <= 0) + { + cupsdLogClient(con, CUPSD_LOG_DEBUG, "Snap without cups-control plug - denied."); + ret = 0; + } + else + { + cupsdLogClient(con, CUPSD_LOG_DEBUG, "Snap with cups-control plug - allowed."); + } - /* - * Issue warning if requirements for building Snap-related access control - * not fulfilled - */ + if (plugs) + g_ptr_array_unref(plugs); - cupsdLogMessage(CUPSD_LOG_WARN, "check_admin_task: Compiling problem: none of the three access control methods (libsnapd-glib snapd access, \"snapctl is-connected\", libsnapd-glib-based snapctl call) available, no Snap-related access control built!"); + free(snap_name); + g_clear_object(&snap); - snap_check_done: - if (context) - free(context); +# endif // CUPS_SNAP -# endif /* !CHECK_METHOD_FOUND */ + done: - } - } + free(context); + g_clear_object(&client); - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Access %s", ret == 1 ? "granted" : "denied"); + return (ret); #else - - (void)con; - cupsdLogMessage(CUPSD_LOG_DEBUG, "check_admin_task: Access granted (no extra checking)"); - -#endif /* AF_LOCAL && SUPPORT_SNAPPED_CLIENTS */ - - return ret; + // No AppArmor/snapd to deal with... + return (1); +#endif // HAVE_LIBAPPARMOR && HAVE_LIBSNAPDGLIB } diff --git a/scheduler/cert.c b/scheduler/cert.c index 9716bbf355..a0c3941bd4 100644 --- a/scheduler/cert.c +++ b/scheduler/cert.c @@ -1,10 +1,12 @@ /* * Authentication certificate routines for the CUPS scheduler. * - * Copyright 2007-2016 by Apple Inc. - * Copyright 1997-2006 by Easy Software Products. + * Copyright © 2021 by OpenPrinting. + * Copyright © 2007-2016 by Apple Inc. + * Copyright © 1997-2006 by Easy Software Products. * - * Licensed under Apache License v2.0. See the file "LICENSE" for more information. + * Licensed under Apache License v2.0. See the file "LICENSE" for more + * information. */ /* @@ -83,7 +85,7 @@ cupsdAddCert(int pid, /* I - Process ID */ if (pid == 0) { -#if defined(HAVE_ACL_INIT) && !defined(SUPPORT_SNAPPED_CUPSD) +#if defined(HAVE_ACL_INIT) && !CUPS_SNAP acl_t acl; /* ACL information */ acl_entry_t entry; /* ACL entry */ acl_permset_t permset; /* Permissions */ @@ -92,7 +94,7 @@ cupsdAddCert(int pid, /* I - Process ID */ # endif /* HAVE_MBR_UID_TO_UUID */ static int acls_not_supported = 0; /* Only warn once */ -#endif /* HAVE_ACL_INIT && !SUPPORT_SNAPPED_CUPSD */ +#endif /* HAVE_ACL_INIT && !CUPS_SNAP */ /* @@ -104,7 +106,7 @@ cupsdAddCert(int pid, /* I - Process ID */ /* 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 +#if CUPS_SNAP fchown(fd, RunUser, 0); #else fchown(fd, RunUser, SystemGroupIDs[0]); @@ -238,7 +240,7 @@ cupsdAddCert(int pid, /* I - Process ID */ acl_free(acl); } # endif /* HAVE_ACL_INIT */ -#endif /* SUPPORT_SNAPPED_CUPSD */ +#endif /* CUPS_SNAP */ RootCertTime = time(NULL); } diff --git a/scheduler/conf.c b/scheduler/conf.c index 5d167e0fe8..e44736b77d 100644 --- a/scheduler/conf.c +++ b/scheduler/conf.c @@ -1121,16 +1121,11 @@ cupsdReadConfiguration(void) 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 || +#if CUPS_SNAP + 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(StateDir, "certs", RunUser ? 0711 : 0511, User, SystemGroupIDs[0], 1, 1) < 0 || +#endif /* CUPS_SNAP */ cupsdCheckPermissions(ServerRoot, NULL, 0755, RunUser, Group, 1, 0) < 0 || cupsdCheckPermissions(ServerRoot, "ppd", 0755, RunUser, diff --git a/scheduler/cups-exec.c b/scheduler/cups-exec.c index dff5055158..7b851d33c6 100644 --- a/scheduler/cups-exec.c +++ b/scheduler/cups-exec.c @@ -1,9 +1,11 @@ /* * Sandbox helper for CUPS. * - * Copyright 2007-2014 by Apple Inc. + * Copyright © 2021 by OpenPrinting. + * Copyright © 2007-2014 by Apple Inc. * - * Licensed under Apache License v2.0. See the file "LICENSE" for more information. + * Licensed under Apache License v2.0. See the file "LICENSE" for more + * information. * * Usage: * @@ -129,11 +131,11 @@ main(int argc, /* I - Number of command-line args */ if (setgid(gid)) exit(errno + 100); -# ifdef SUPPORT_SNAPPED_CUPSD +# if CUPS_SNAP if (setgroups(0, NULL)) # else if (setgroups(1, &gid)) -# endif /* SUPPORT_SNAPPED_CUPSD */ +# endif /* CUPS_SNAP */ exit(errno + 100); if (uid && setuid(uid)) diff --git a/scheduler/cupsfilter.c b/scheduler/cupsfilter.c index d9192b4242..c6dbb8debb 100644 --- a/scheduler/cupsfilter.c +++ b/scheduler/cupsfilter.c @@ -965,11 +965,7 @@ exec_filters(mime_type_t *srctype, /* I - Source type */ { 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 */ @@ -977,17 +973,9 @@ exec_filters(mime_type_t *srctype, /* I - Source 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 */ @@ -995,6 +983,13 @@ exec_filters(mime_type_t *srctype, /* I - Source type */ printer_name[255], /* PRINTER env variable */ rip_max_cache[1024], /* RIP_MAX_CACHE */ userenv[1024], /* USER */ +#if CUPS_SNAP + fontconfig_file[1024], /* FONTCONFIG_FILE */ + fontconfig_path[1024], /* FONTCONFIG_PATH */ + fontconfig_sysroot[1024], + /* FONTCONFIG_SYSROOT */ + ld_library_path[2048], /* LD_LIBRARY_PATH */ +#endif /* CUPS_SNAP */ program[1024]; /* Program to run */ mime_filter_t *filter, /* Current filter */ *next; /* Next filter */ @@ -1046,9 +1041,6 @@ exec_filters(mime_type_t *srctype, /* I - Source type */ * 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", @@ -1058,20 +1050,8 @@ exec_filters(mime_type_t *srctype, /* I - Source type */ 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); @@ -1090,8 +1070,9 @@ exec_filters(mime_type_t *srctype, /* I - Source type */ "Versions/A/Frameworks/PrintCore.framework/Versions/A/" "Resources/Generic.ppd", sizeof(ppd)); #else - snprintf(ppd, sizeof(ppd), "PPD=%s/model/laserjet.ppd", DataDir); + snprintf(ppd, sizeof(ppd), "PPD=%s/model/laserjet.ppd", DataDir); #endif /* __APPLE__ */ + snprintf(userenv, sizeof(userenv), "USER=%s", user); if (printer && @@ -1134,52 +1115,54 @@ exec_filters(mime_type_t *srctype, /* I - Source type */ if (!argv[4]) argv[4] = "1"; - envp[0] = ""; - envp[1] = content_type; - envp[2] = cups_datadir; - envp[3] = cups_fontpath; - envp[4] = cups_serverbin; - 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; + for (i = 0; argv[i]; i ++) + fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]); + + i = 0; +#ifdef __APPLE__ + envp[i ++] = ""; +#endif /* __APPLE__ */ + envp[i ++] = content_type; + envp[i ++] = cups_datadir; + envp[i ++] = cups_fontpath; + envp[i ++] = cups_serverbin; + envp[i ++] = cups_serverroot; + envp[i ++] = lang; + envp[i ++] = path; + envp[i ++] = ppd; + envp[i ++] = printer_info; + envp[i ++] = printer_location; + envp[i ++] = printer_name; + envp[i ++] = rip_max_cache; + envp[i ++] = userenv; + envp[i ++] = "CHARSET=utf-8"; if (final_content_type[0]) + envp[i ++] = final_content_type; + +#if CUPS_SNAP + if ((temp = getenv("FONTCONFIG_FILE")) != NULL) { - envp[19] = final_content_type; - envp[20] = NULL; + snprintf(fontconfig_file, sizeof(fontconfig_file), "FONTCONFIG_FILE=%s", temp); + envp[i ++] = fontconfig_file; } - else - envp[19] = NULL; -#else - envp[8] = ppd; - envp[9] = printer_info; - envp[10] = printer_location; - envp[11] = printer_name; - envp[12] = rip_max_cache; - envp[13] = userenv; - envp[14] = "CHARSET=utf-8"; - if (final_content_type[0]) + if ((temp = getenv("FONTCONFIG_PATH")) != NULL) { - envp[15] = final_content_type; - envp[16] = NULL; + snprintf(fontconfig_path, sizeof(fontconfig_path), "FONTCONFIG_PATH=%s", temp); + envp[i ++] = fontconfig_path; } - else - envp[15] = NULL; -#endif /* SUPPORT_SNAPPED_CUPSD */ + if ((temp = getenv("FONTCONFIG_SYSROOT")) != NULL) + { + snprintf(fontconfig_sysroot, sizeof(fontconfig_sysroot), "FONTCONFIG_SYSROOT=%s", temp); + envp[i ++] = fontconfig_sysroot; + } + if ((temp = getenv("LD_LIBRARY_PATH")) != NULL) + { + snprintf(ld_library_path, sizeof(ld_library_path), "LD_LIBRARY_PATH=%s", temp); + envp[i ++] = ld_library_path; + } +#endif /* CUPS_SNAP */ - for (i = 0; argv[i]; i ++) - fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]); + envp[i] = NULL; for (i = 0; envp[i]; i ++) fprintf(stderr, "DEBUG: envp[%d]=\"%s\"\n", i, envp[i]); @@ -1497,14 +1480,12 @@ read_cups_files_conf( 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 +#if CUPS_SNAP + if ((temp = getenv("PATH")) != NULL) + snprintf(line, sizeof(line), "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR ":%s", ServerBin, temp); + else +#endif /* CUPS_SNAP */ snprintf(line, sizeof(line), "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR ":/bin:/usr/bin", ServerBin); -#endif /* SUPPORT_SNAPPED_CUPSD */ set_string(&Path, line); return (0); diff --git a/scheduler/env.c b/scheduler/env.c index 09d79676c9..87b9cdd35b 100644 --- a/scheduler/env.c +++ b/scheduler/env.c @@ -116,14 +116,13 @@ cupsdSetEnv(const char *name, /* I - Name of variable */ return; /* - * Do not allow dynamic linker variables when running as root and - * not being confined in a Snap... + * Do not allow dynamic linker variables when running as root outside a Snap... */ -#ifndef SUPPORT_SNAPPED_CUPSD +#if !CUPS_SNAP if (!RunUser && (!strncmp(name, "DYLD_", 5) || !strncmp(name, "LD_", 3))) return; -#endif /* !SUPPORT_SNAPPED_CUPSD */ +#endif /* !CUPS_SNAP */ /* * See if this variable has already been defined... @@ -212,18 +211,16 @@ cupsdUpdateEnv(void) 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) { -#ifdef SUPPORT_SNAPPED_CUPSD - char *value; - if ((value = getenv("PATH")) != NULL) +#if CUPS_SNAP + const char *path; // PATH environment variable + + if ((path = 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); +#endif /* CUPS_SNAP */ + cupsdSetEnvf("PATH", "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR ":/bin:/usr/bin", ServerBin); } set_if_undefined("SERVER_ADMIN", ServerAdmin); set_if_undefined("SHLIB_PATH", NULL); diff --git a/scheduler/process.c b/scheduler/process.c index 3b09efe24c..d995be231e 100644 --- a/scheduler/process.c +++ b/scheduler/process.c @@ -1,10 +1,12 @@ /* * Process management routines for the CUPS scheduler. * - * Copyright 2007-2017 by Apple Inc. - * Copyright 1997-2007 by Easy Software Products, all rights reserved. + * Copyright © 2021 by OpenPrinting. + * Copyright © 2007-2017 by Apple Inc. + * Copyright © 1997-2007 by Easy Software Products, all rights reserved. * - * Licensed under Apache License v2.0. See the file "LICENSE" for more information. + * Licensed under Apache License v2.0. See the file "LICENSE" for more + * information. */ /* @@ -744,11 +746,11 @@ cupsdStartProcess( if (!RunUser && setgid(Group)) exit(errno + 100); -# ifdef SUPPORT_SNAPPED_CUPSD +# if CUPS_SNAP if (!RunUser && setgroups(0, NULL)) # else if (!RunUser && setgroups(1, &Group)) -# endif /* SUPPORT_SNAPPED_CUPSD */ +# endif /* CUPS_SNAP */ exit(errno + 100); /* From 3d93993cf5423e36e2d80bbda7ba77900ff2f4fb Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Wed, 6 Oct 2021 11:58:09 -0400 Subject: [PATCH 8/8] Fix Codacy-detected issue - not using path variable. --- scheduler/env.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scheduler/env.c b/scheduler/env.c index 87b9cdd35b..6e738c6e2b 100644 --- a/scheduler/env.c +++ b/scheduler/env.c @@ -217,7 +217,7 @@ cupsdUpdateEnv(void) const char *path; // PATH environment variable if ((path = getenv("PATH")) != NULL) - cupsdSetEnvf("PATH", "%s/filter:%s", ServerBin, value); + cupsdSetEnvf("PATH", "%s/filter:%s", ServerBin, path); else #endif /* CUPS_SNAP */ cupsdSetEnvf("PATH", "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR ":/bin:/usr/bin", ServerBin);