/*
* 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.
*
* information.
*/
-/*
- * Include necessary headers...
- */
-
#include "cupsd.h"
#include <grp.h>
#if HAVE_LIBPAM
#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 *);
}
+/*
+ * '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.
*/
}
+/*
+ * '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.
*/
* '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.
*/
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;
}
+/*
+ * '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.
/*
* 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 <pwd.h>
+#include <cups/jwt.h>
/*
#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 */
} 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 */
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);
{ "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 },
/*
* '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 */
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__
/*
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,
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;
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"))
}
}
}
+ 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)
{
/*