From: Michael R Sweet Date: Tue, 15 Apr 2025 15:41:33 +0000 (-0400) Subject: Add OAuth directives. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=732dcc06454df73cd1ea797b7f14f2ea3a3f9932;p=thirdparty%2Fcups.git Add OAuth directives. --- diff --git a/scheduler/auth.c b/scheduler/auth.c index 7f24f05c41..c767f10f22 100644 --- a/scheduler/auth.c +++ b/scheduler/auth.c @@ -1,7 +1,7 @@ /* * Authorization routines for the CUPS scheduler. * - * Copyright © 2020-2024 by OpenPrinting. + * Copyright © 2020-2025 by OpenPrinting. * Copyright © 2007-2019 by Apple Inc. * Copyright © 1997-2007 by Easy Software Products, all rights reserved. * @@ -12,10 +12,6 @@ * information. */ -/* - * Include necessary headers... - */ - #include "cupsd.h" #include #if HAVE_LIBPAM @@ -63,11 +59,12 @@ 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 */ -static int compare_locations(cupsd_location_t *a, - cupsd_location_t *b, - void *data); +static int compare_locations(cupsd_location_t *a, cupsd_location_t *b, void *data); +static int compare_ogroups(cupsd_ogroup_t *a, cupsd_ogroup_t *b, void *data); static cupsd_authmask_t *copy_authmask(cupsd_authmask_t *am, void *data); static void free_authmask(cupsd_authmask_t *am, void *data); +static void free_ogroup(cupsd_ogroup_t *og, void *data); +static int load_ogroup(cupsd_ogroup_t *og, struct stat *fileinfo); #if HAVE_LIBPAM static int pam_func(int, const struct pam_message **, struct pam_response **, void *); @@ -244,6 +241,72 @@ cupsdAddNameMask(cups_array_t **masks, /* IO - Masks array (created as needed) * } +/* + * 'cupsdAddOAuthGroup()' - Add an OAuth group file. + */ + +int /* O - 1 on success, 0 on error */ +cupsdAddOAuthGroup(const char *name, /* I - Group name */ + const char *filename)/* I - Group filename */ +{ + cupsd_ogroup_t *og; /* Group */ + struct stat fileinfo; /* File information */ + + + /* + * Check OAuth group file... + */ + + if (stat(filename, &fileinfo)) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to access OAuthGroup %s file \"%s\": %s", name, filename, strerror(errno)); + return (0); + } + + /* + * Create the new group... + */ + + if (!OAuthGroups) + { + /* + * Create groups array... + */ + + if ((OAuthGroups = cupsArrayNew3((cups_array_cb_t)compare_ogroups, /*d*/NULL, /*h*/NULL, /*hsize*/0, /*cf*/NULL, (cups_afree_cb_t)free_ogroup)) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to allocate memory for OAuthGroup array: %s", strerror(errno)); + return (0); + } + } + + if ((og = (cupsd_ogroup_t *)calloc(1, sizeof(cupsd_ogroup_t))) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to allocate memory for OAuthGroup %s: %s", name, strerror(errno)); + return (0); + } + + if ((og->name = strdup(name)) == NULL || (og->filename = strdup(filename)) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to allocate memory for OAuthGroup %s: %s", name, strerror(errno)); + free_ogroup(og, NULL); + return (0); + } + + /* + * Add the group to the array... + */ + + cupsArrayAdd(OAuthGroups, og); + + /* + * Load the group and return... + */ + + return (load_ogroup(og, &fileinfo)); +} + + /* * 'cupsdAuthorize()' - Validate any authorization credentials. */ @@ -1423,6 +1486,33 @@ cupsdFindLocation(const char *location) /* I - Connection */ } +/* + * 'cupsdFindOAuthGroup()' - Find an OAuth group. + */ + +cupsd_ogroup_t * /* O - Group or `NULL` */ +cupsdFindOAuthGroup(const char *name) /* I - Group name */ +{ + cupsd_ogroup_t key, /* Search key */ + *og; /* Matching group */ + struct stat fileinfo; /* Group file information */ + + + key.name = (char *)name; + if ((og = (cupsd_ogroup_t *)cupsArrayFind(OAuthGroups, &key)) != NULL) + { + /* + * See if we need to reload the group file... + */ + + if (!stat(og->filename, &fileinfo) && (fileinfo.st_size != og->fileinfo.st_size || fileinfo.st_mtime > og->fileinfo.st_mtime)) + load_ogroup(og, &fileinfo); + } + + return (og); +} + + /* * 'cupsdFreeLocation()' - Free all memory used by a location. */ @@ -2118,16 +2208,32 @@ check_authref(cupsd_client_t *con, /* I - Connection */ * 'compare_locations()' - Compare two locations. */ -static int /* O - Result of comparison */ -compare_locations(cupsd_location_t *a, /* I - First location */ - cupsd_location_t *b, /* I - Second location */ - void *data) /* Unused */ +static int /* O - Result of comparison */ +compare_locations(cupsd_location_t *a, /* I - First location */ + cupsd_location_t *b, /* I - Second location */ + void *data) /* I - Callback data (unused) */ { (void)data; + return (strcmp(b->location, a->location)); } +/* + * 'compare_ogroups()' - Compare two OAuth groups. + */ + +static int /* O - Result of comparison */ +compare_ogroups(cupsd_ogroup_t *a, /* I - First group */ + cupsd_ogroup_t *b, /* I - Second group */ + void *data) /* I - Callback data (unused) */ +{ + (void)data; + + return (_cups_strcasecmp(a->name, b->name)); +} + + /* * 'copy_authmask()' - Copy function for auth masks. */ @@ -2173,7 +2279,7 @@ copy_authmask(cupsd_authmask_t *mask, /* I - Existing auth mask */ static void free_authmask(cupsd_authmask_t *mask, /* I - Auth mask to free */ - void *data) /* I - User data (unused) */ + void *data) /* I - Callback data (unused) */ { (void)data; @@ -2184,6 +2290,67 @@ free_authmask(cupsd_authmask_t *mask, /* I - Auth mask to free */ } +/* + * 'free_ogroup()' - Free an OAuth group. + */ + +static void +free_ogroup(cupsd_ogroup_t *og, /* I - OAuth group */ + void *data) /* I - Callback data (unused) */ +{ + (void)data; + + free(og->name); + free(og->filename); + cupsArrayDelete(og->members); + free(og); +} + + +/* + * 'load_ogroup()' - Load an OAuth group file. + */ + +static int /* O - 1 on success, 0 on failure */ +load_ogroup(cupsd_ogroup_t *og, /* I - OAuth group */ + struct stat *fileinfo) /* I - File information */ +{ + cups_file_t *fp; /* File pointer */ + char line[1024]; /* Line from file */ + + + /* + * Make sure we have a fresh members array... + */ + + cupsArrayDelete(og->members); + if ((og->members = cupsArrayNewStrings(/*s*/NULL, /*delim*/'\0')) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to allocate members array for OAuth group %s: %s", og->name, strerror(errno)); + return (0); + } + + /* + * Load the members file... + */ + + if ((fp = cupsFileOpen(og->filename, "r")) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open OAuth group %s filename \"%s\": %s", og->name, og->filename, strerror(errno)); + return (0); + } + + while (cupsFileGets(fp, line, sizeof(line))) + cupsArrayAdd(og->members, line); + + cupsFileClose(fp); + + og->fileinfo = *fileinfo; + + return (1); +} + + #if HAVE_LIBPAM /* * 'pam_func()' - PAM conversation function. diff --git a/scheduler/auth.h b/scheduler/auth.h index 409c03061e..ea9cb9fb69 100644 --- a/scheduler/auth.h +++ b/scheduler/auth.h @@ -1,19 +1,16 @@ /* * Authorization definitions for the CUPS scheduler. * - * Copyright © 2020-2024 by OpenPrinting. - * Copyright 2007-2014 by Apple Inc. - * Copyright 1997-2006 by Easy Software Products, all rights reserved. + * Copyright © 2020-2025 by OpenPrinting. + * Copyright © 2007-2014 by Apple Inc. + * Copyright © 1997-2006 by Easy Software Products, all rights reserved. * * Licensed under Apache License v2.0. See the file "LICENSE" for more * information. */ -/* - * Include necessary headers... - */ - #include +#include /* @@ -23,8 +20,9 @@ #define CUPSD_AUTH_DEFAULT -1 /* Use DefaultAuthType */ #define CUPSD_AUTH_NONE 0 /* No authentication */ #define CUPSD_AUTH_BASIC 1 /* Basic authentication */ -#define CUPSD_AUTH_NEGOTIATE 2 /* Kerberos authentication */ -#define CUPSD_AUTH_AUTO 3 /* Kerberos or Basic, depending on configuration of server */ +#define CUPSD_AUTH_BEARER 2 /* OAuth/OpenID authentication */ +#define CUPSD_AUTH_NEGOTIATE 3 /* Kerberos authentication */ +#define CUPSD_AUTH_AUTO 4 /* Kerberos, OAuth, or Basic, depending on configuration of server */ #define CUPSD_AUTH_ANON 0 /* Anonymous access */ #define CUPSD_AUTH_USER 1 /* Must have a valid username/password */ @@ -82,7 +80,7 @@ typedef struct } mask; /* Mask data */ } cupsd_authmask_t; -typedef struct +typedef struct cupsd_location_s /* Location Policy */ { char *location; /* Location of resource */ size_t length; /* Length of location string */ @@ -98,37 +96,55 @@ typedef struct http_encryption_t encryption; /* To encrypt or not to encrypt... */ } cupsd_location_t; +typedef struct cupsd_ogroup_s /* OAuth Group */ +{ + char *name, /* Group name */ + *filename; /* Group filename */ + struct stat fileinfo; /* Group filename info */ + cups_array_t *members; /* Group members */ +} cupsd_ogroup_t; + /* * Globals... */ -VAR cups_array_t *Locations VALUE(NULL); - /* Authorization locations */ VAR http_encryption_t DefaultEncryption VALUE(HTTP_ENCRYPTION_REQUIRED); /* Default encryption for authentication */ +VAR cups_array_t *Locations VALUE(NULL); + /* Authorization locations */ + +VAR cups_array_t *OAuthGroups VALUE(NULL); + /* OAuthGroup entries */ +VAR http_t *OAuthHTTP VALUE(NULL); + /* Connection to server */ +VAR cups_json_t *OAuthMetadata VALUE(NULL); + /* Metadata from the server */ +VAR char *OAuthScopes VALUE(NULL), + /* OAuthScopes value */ + *OAuthServer VALUE(NULL); + /* OAuthServer URL */ + /* * Prototypes... */ -extern int cupsdAddIPMask(cups_array_t **masks, - const unsigned address[4], - const unsigned netmask[4]); +extern int cupsdAddIPMask(cups_array_t **masks, const unsigned address[4], const unsigned netmask[4]); extern void cupsdAddLocation(cupsd_location_t *loc); extern void cupsdAddName(cupsd_location_t *loc, char *name); extern int cupsdAddNameMask(cups_array_t **masks, char *name); +extern int cupsdAddOAuthGroup(const char *name, const char *filename); extern void cupsdAuthorize(cupsd_client_t *con); extern int cupsdCheckAccess(unsigned ip[4], const char *name, size_t namelen, cupsd_location_t *loc); extern int cupsdCheckAuth(unsigned ip[4], const char *name, size_t namelen, cups_array_t *masks); -extern int cupsdCheckGroup(const char *username, - struct passwd *user, - const char *groupname); +extern int cupsdCheckGroup(const char *username, struct passwd *user, const char *groupname); extern cupsd_location_t *cupsdCopyLocation(cupsd_location_t *loc); extern void cupsdDeleteAllLocations(void); extern cupsd_location_t *cupsdFindBest(const char *path, http_state_t state); extern cupsd_location_t *cupsdFindLocation(const char *location); +extern cupsd_ogroup_t *cupsdFindOAuthGroup(const char *name); extern void cupsdFreeLocation(cupsd_location_t *loc, void *data); extern http_status_t cupsdIsAuthorized(cupsd_client_t *con, const char *owner); extern cupsd_location_t *cupsdNewLocation(const char *location); diff --git a/scheduler/conf.c b/scheduler/conf.c index b1fa3dc04c..390c8732eb 100644 --- a/scheduler/conf.c +++ b/scheduler/conf.c @@ -139,6 +139,8 @@ static const cupsd_var_t cupsfiles_vars[] = { "ErrorLog", &ErrorLog, CUPSD_VARTYPE_STRING }, { "FileDevice", &FileDevice, CUPSD_VARTYPE_BOOLEAN }, { "LogFilePerm", &LogFilePerm, CUPSD_VARTYPE_PERM }, + { "OAuthScopes", &OAuthScopes, CUPSD_VARTYPE_STRING }, + { "OAuthServer", &OAuthServer, CUPSD_VARTYPE_STRING }, { "PageLog", &PageLog, CUPSD_VARTYPE_STRING }, { "Printcap", &Printcap, CUPSD_VARTYPE_STRING }, { "RemoteRoot", &RemoteRoot, CUPSD_VARTYPE_STRING }, @@ -384,10 +386,10 @@ cupsdCheckPermissions( /* * 'cupsdDefaultAuthType()' - Get the default AuthType. * - * When the default_auth_type is "auto", this function tries to get the GSS - * credentials for the server. If that succeeds we use Kerberos authentication, - * otherwise we do a fallback to Basic authentication against the local user - * accounts. + * When the default_auth_type is "auto", this function uses OAuth if the + * OAuthServer directive has been specified or Kerberos if we can get GSS + * credentials for the server. Otherwise we fallback to Basic authentication + * against the local user accounts. */ int /* O - Default AuthType value */ @@ -410,6 +412,13 @@ cupsdDefaultAuthType(void) if (default_auth_type != CUPSD_AUTH_AUTO) return (default_auth_type); + /* + * If the OAuthServer is set, use that... + */ + + if (OAuthServer) + return (default_auth_type = CUPSD_AUTH_BEARER); + #ifdef HAVE_GSSAPI # ifdef __APPLE__ /* @@ -594,6 +603,18 @@ cupsdReadConfiguration(void) CUPS_VERSION_MINOR); cupsdSetString(&StateDir, CUPS_STATEDIR); + cupsdClearString(&OAuthScopes); + cupsdClearString(&OAuthServer); + + cupsArrayDelete(OAuthGroups); + OAuthGroups = NULL; + + httpClose(OAuthHTTP); + OAuthHTTP = NULL; + + cupsJSONDelete(OAuthMetadata); + OAuthMetadata = NULL; + if (!strcmp(CUPS_DEFAULT_PRINTCAP, "/etc/printers.conf")) PrintcapFormat = PRINTCAP_SOLARIS; else if (!strcmp(CUPS_DEFAULT_PRINTCAP, @@ -2336,6 +2357,13 @@ parse_aaa(cupsd_location_t *loc, /* I - Location */ if (loc->level == CUPSD_AUTH_ANON) loc->level = CUPSD_AUTH_USER; } + else if (!_cups_strcasecmp(value, "bearer")) + { + loc->type = CUPSD_AUTH_BEARER; + + if (loc->level == CUPSD_AUTH_ANON) + loc->level = CUPSD_AUTH_USER; + } else if (!_cups_strcasecmp(value, "default")) { loc->type = CUPSD_AUTH_DEFAULT; @@ -3293,6 +3321,8 @@ read_cupsd_conf(cups_file_t *fp) /* I - File to read from */ default_auth_type = CUPSD_AUTH_NONE; else if (!_cups_strcasecmp(value, "basic")) default_auth_type = CUPSD_AUTH_BASIC; + else if (!_cups_strcasecmp(value, "bearer")) + default_auth_type = CUPSD_AUTH_BEARER; else if (!_cups_strcasecmp(value, "negotiate")) default_auth_type = CUPSD_AUTH_NEGOTIATE; else if (!_cups_strcasecmp(value, "auto")) @@ -3649,6 +3679,35 @@ read_cups_files_conf(cups_file_t *fp) /* I - File to read from */ } } } + else if (!_cups_strcasecmp(line, "OAuthGroup") && value) + { + /* + * OAuthGroup NAME FILENAME + */ + + char *filename; /* Filename on line */ + + for (filename = value; *filename; filename ++) + { + if (isspace(*filename & 255)) + break; + } + + while (*filename && isspace(*filename & 255)) + *filename++ = '\0'; + + if (*filename && !access(filename, R_OK)) + { + if (!cupsdAddOAuthGroup(value, filename) && (FatalErrors & CUPSD_FATAL_CONFIG)) + return (0); + } + else + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to read OAuthGroup file \"%s\" on line %d of %s: %s", filename, linenum, CupsFilesFile, strerror(errno)); + if (FatalErrors & CUPSD_FATAL_CONFIG) + return (0); + } + } else if (!_cups_strcasecmp(line, "PassEnv") && value) { /*