]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
Add OAuth directives.
authorMichael R Sweet <msweet@msweet.org>
Tue, 15 Apr 2025 15:41:33 +0000 (11:41 -0400)
committerMichael R Sweet <msweet@msweet.org>
Tue, 15 Apr 2025 15:41:33 +0000 (11:41 -0400)
scheduler/auth.c
scheduler/auth.h
scheduler/conf.c

index 7f24f05c415d17e43899a5f8a61e04df6ed3430d..c767f10f22602607df92a354b8edb4999e4280f5 100644 (file)
@@ -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.
  *
  * information.
  */
 
-/*
- * Include necessary headers...
- */
-
 #include "cupsd.h"
 #include <grp.h>
 #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.
index 409c03061eeb9ac069322b23872cf97b06c3a5e7..ea9cb9fb69f52651dd501e097a9250028178ecfa 100644 (file)
@@ -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 <pwd.h>
+#include <cups/jwt.h>
 
 
 /*
@@ -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);
index b1fa3dc04c7789debac7f09ff560600eb649b644..390c8732ebd50035b58c2097f4a8dae54aa73655 100644 (file)
@@ -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)
     {
      /*