}
// 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);
}
* 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 */
/*
- * '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;
}