]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
Fix coverity-detected issues.
authorMichael R Sweet <msweet@msweet.org>
Wed, 16 Apr 2025 20:50:36 +0000 (16:50 -0400)
committerMichael R Sweet <msweet@msweet.org>
Wed, 16 Apr 2025 20:50:42 +0000 (16:50 -0400)
cups/dnssd.c
cups/http.c
cups/tls-gnutls.c
cups/tls-openssl.c
scheduler/ipp.c
scheduler/job.c
tools/ippevepcl.c
tools/ippeveps.c
xcode/CUPS.xcodeproj/project.pbxproj
xcode/cups-oauth.c [new file with mode: 0644]

index 7d99f1da094bd3a5e6599ab21b38e9d89484bc55..a269ab81d468cdd2579258dd0f2a822ead96031d 100644 (file)
@@ -3009,20 +3009,19 @@ avahi_resolve_cb(
 
   DEBUG_printf("3avahi_resolve_cb(resolver=%p, if_index=%d, protocol=%d, event=%s, name=\"%s\", type=\"%s\", domain=\"%s\", host=\"%s\", address=%p, port=%u, txtrec=%p, flags=%u, resolve=%p)", (void *)resolver, if_index, protocol, avahi_events[event], name, type, domain, host, (void *)address, (unsigned)port, (void *)txtrec, (unsigned)flags, (void *)resolve);
 
-  if (!resolver || event != AVAHI_RESOLVER_FOUND)
+  if (!resolver)
     return;
 
-  (void)resolver;
   (void)protocol;
   (void)flags;
 
   // Map the addresses "127.0.0.1" (IPv4) and "::1" (IPv6) to "localhost" to work around a well-known Avahi registration bug for local-only services (Issue #970)
-  if (address->proto == AVAHI_PROTO_INET && address->data.ipv4.address == htonl(0x7f000001))
+  if (address && address->proto == AVAHI_PROTO_INET && address->data.ipv4.address == htonl(0x7f000001))
   {
     DEBUG_puts("4avahi_resolve_cb: Mapping 127.0.0.1 to localhost.");
     host = "localhost";
   }
-  else if (address->proto == AVAHI_PROTO_INET6 && address->data.ipv6.address[0] == 0 && address->data.ipv6.address[1] == 0 && address->data.ipv6.address[2] == 0 && address->data.ipv6.address[3] == 0 && address->data.ipv6.address[4] == 0 && address->data.ipv6.address[5] == 0 && address->data.ipv6.address[6] == 0 && address->data.ipv6.address[7] == 0 && address->data.ipv6.address[8] == 0 && address->data.ipv6.address[9] == 0 && address->data.ipv6.address[10] == 0 && address->data.ipv6.address[11] == 0 && address->data.ipv6.address[12] == 0 && address->data.ipv6.address[13] == 0 && address->data.ipv6.address[14] == 0 && address->data.ipv6.address[15] == 1)
+  else if (address && address->proto == AVAHI_PROTO_INET6 && address->data.ipv6.address[0] == 0 && address->data.ipv6.address[1] == 0 && address->data.ipv6.address[2] == 0 && address->data.ipv6.address[3] == 0 && address->data.ipv6.address[4] == 0 && address->data.ipv6.address[5] == 0 && address->data.ipv6.address[6] == 0 && address->data.ipv6.address[7] == 0 && address->data.ipv6.address[8] == 0 && address->data.ipv6.address[9] == 0 && address->data.ipv6.address[10] == 0 && address->data.ipv6.address[11] == 0 && address->data.ipv6.address[12] == 0 && address->data.ipv6.address[13] == 0 && address->data.ipv6.address[14] == 0 && address->data.ipv6.address[15] == 1)
   {
     DEBUG_puts("4avahi_resolve_cb: Mapping ::1 to localhost.");
     host = "localhost";
index 6fdcec3064b7a7d4d47453e4083fdfc57d6289d3..e16c9ac01aefda56a9e6ea702214c774aa32548c 100644 (file)
@@ -1886,6 +1886,7 @@ httpPeek(http_t *http,                    // I - HTTP connection
     {
       DEBUG_puts("2httpPeek: Unable to copy decompressor stream.");
       http->error = ENOMEM;
+      inflateEnd(&stream);
       return (-1);
     }
 
index 78dbe60e31873b7658117c64013d4e49fc1c9c30..56957ba00974c3fbf54f37f4fe916fccfd0ed5d6 100644 (file)
@@ -1742,8 +1742,12 @@ _httpTLSStart(http_t *http)              // I - Connection to server
                *cnptr;                 // Pointer into common name
     bool       have_creds = false;     // Have credentials?
 
+    cupsMutexLock(&tls_mutex);
+
     if (!tls_common_name)
     {
+      cupsMutexUnlock(&tls_mutex);
+
       if (http->fields[HTTP_FIELD_HOST])
       {
        // Use hostname for TLS upgrade...
@@ -1777,9 +1781,9 @@ _httpTLSStart(http_t *http)               // I - Connection to server
 
       if (hostname[0])
         cn = hostname;
-    }
 
-    cupsMutexLock(&tls_mutex);
+      cupsMutexLock(&tls_mutex);
+    }
 
     if (!cn)
       cn = tls_common_name;
index 5474d57c913be3c62d6d4b8f288664fbf7fec168..128820b57d76ee702526b26b5796b5c4a7511f1b 100644 (file)
@@ -1715,8 +1715,12 @@ _httpTLSStart(http_t *http)              // I - Connection to server
     context = SSL_CTX_new(TLS_server_method());
 
     // Find the TLS certificate...
+    cupsMutexLock(&tls_mutex);
+
     if (!tls_common_name)
     {
+      cupsMutexUnlock(&tls_mutex);
+
       if (http->fields[HTTP_FIELD_HOST])
       {
        // Use hostname for TLS upgrade...
@@ -1753,9 +1757,9 @@ _httpTLSStart(http_t *http)               // I - Connection to server
 
       if (hostname[0])
        cn = hostname;
-    }
 
-    cupsMutexLock(&tls_mutex);
+      cupsMutexLock(&tls_mutex);
+    }
 
     if (!cn)
       cn = tls_common_name;
index a4997f1fe628752677879f29dbc396501d96e6f8..a00a7df19eed1880f6326e5331b9412ddb5e1fb5 100644 (file)
@@ -138,10 +138,13 @@ cupsdProcessIPPRequest(
   ipp_attribute_t      *username;      /* requesting-user-name attr */
   int                  sub_id;         /* Subscription ID */
   int                  valid = 1;      /* Valid request? */
-  int                  minor;          /* Minor version */
+  int                  major,          /* IPP major version */
+                       minor;          /* IPP minor version */
 
 
-  cupsdLogClient(con, CUPSD_LOG_DEBUG, "%s IPP/%d.%d request_id=%d", ippOpString(ippGetOperation(con->request)), ippGetVersion(con->request, &minor), minor, ippGetRequestId(con->request));
+  major = ippGetVersion(con->request, &minor);
+
+  cupsdLogClient(con, CUPSD_LOG_DEBUG, "%s IPP/%d.%d request_id=%d", ippOpString(ippGetOperation(con->request)), major, minor, ippGetRequestId(con->request));
 
   if (LogLevel >= CUPSD_LOG_DEBUG2)
   {
index 8a1b08117cfff69a695a9ee98c7ad42a4efeb247..7b1ab093c80907ef3db23908269f7475f1164a08 100644 (file)
@@ -4674,6 +4674,7 @@ load_request_root(void)
   */
 
   while ((dent = cupsDirRead(dir)) != NULL)
+  {
     if (strlen(dent->filename) >= 6 && dent->filename[0] == 'c')
     {
      /*
@@ -4730,6 +4731,7 @@ load_request_root(void)
         cupsdDeleteJob(job, CUPSD_JOB_FORCE);
       }
     }
+  }
 
   cupsDirClose(dir);
 }
index eb75884af4cb42b7631b52182466f42e972a9a17..bade57351fcb1e4e57a3532d2d6e09a9155f1906 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Generic HP PCL printer command for ippeveprinter/CUPS.
  *
- * Copyright © 2020-2024 by OpenPrinting.
+ * Copyright © 2020-2025 by OpenPrinting.
  * Copyright © 2019 by Apple Inc.
  *
  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
@@ -508,7 +508,11 @@ raster_to_pcl(const char *filename)        /* I - File to print (NULL for stdin) */
       break;
     }
 
-    line = malloc(header.cupsBytesPerLine);
+    if ((line = malloc(header.cupsBytesPerLine)) == NULL)
+    {
+      fprintf(stderr, "ERROR: Unable to allocate %u bytes for line, aborting.\n", header.cupsBytesPerLine);
+      break;
+    }
 
     pcl_start_page(&header, page);
     for (y = 0; y < header.cupsHeight; y ++)
index 75ba2b0e658ad6676714b63fb9675c65044a0ec8..9822696c6ea66ae6754f8692ef7c699f5b8bffa5 100644 (file)
@@ -1091,7 +1091,11 @@ raster_to_ps(const char *filename)       /* I - Filename */
       break;
     }
 
-    line = malloc(header.cupsBytesPerLine);
+    if ((line = malloc(header.cupsBytesPerLine)) == NULL)
+    {
+      fprintf(stderr, "ERROR: Unable to allocate %u bytes for line, aborting.\n", header.cupsBytesPerLine);
+      break;
+    }
 
     dsc_page(page);
 
index 878f557a7b513ec7003dfb176e572055d51d6236..44dcb088274779e1775da70c66bb061c897ee602 100644 (file)
@@ -71,6 +71,8 @@
                        buildPhases = (
                        );
                        dependencies = (
+                               27C3E96E2DB04B5800A6ABBF /* PBXTargetDependency */,
+                               27C3E9702DB04B5800A6ABBF /* PBXTargetDependency */,
                                273B1EC2226B3F2600428143 /* PBXTargetDependency */,
                                273B1EC4226B3F2600428143 /* PBXTargetDependency */,
                                271287061CC13F8F00E517C7 /* PBXTargetDependency */,
                27B493ED2C8FC36E004C7A73 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 2758FE1C2C8FBD2B0078480C /* libiconv.tbd */; };
                27B493EE2C8FC37B004C7A73 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2758FE182C8F9F5E0078480C /* CoreServices.framework */; };
                27B493EF2C8FC389004C7A73 /* GSS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27865F092C8FC0A5003D5606 /* GSS.framework */; };
+               27C3E9562DB04AC500A6ABBF /* libcups2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 72220EAE1333047D00FCA411 /* libcups2.dylib */; };
+               27C3E9622DB04ACE00A6ABBF /* libcups2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 72220EAE1333047D00FCA411 /* libcups2.dylib */; };
+               27C3E96B2DB04B2900A6ABBF /* cups-oauth.c in Sources */ = {isa = PBXBuildFile; fileRef = 27C3E9682DB04AFB00A6ABBF /* cups-oauth.c */; };
+               27C3E96C2DB04B3600A6ABBF /* cups-x509.c in Sources */ = {isa = PBXBuildFile; fileRef = 27C3E9692DB04AFB00A6ABBF /* cups-x509.c */; };
                27F515462AAFBECF0045EE21 /* raster-testpage.h in Headers */ = {isa = PBXBuildFile; fileRef = 27F515432AAFBECF0045EE21 /* raster-testpage.h */; };
                27F515472AAFBECF0045EE21 /* raster-testpage.h in Headers */ = {isa = PBXBuildFile; fileRef = 27F515432AAFBECF0045EE21 /* raster-testpage.h */; };
                27F515482AAFBECF0045EE21 /* raster-testpage.h in Headers */ = {isa = PBXBuildFile; fileRef = 27F515432AAFBECF0045EE21 /* raster-testpage.h */; };
                        remoteGlobalIDString = 27A0347A1A8BDB1200650675;
                        remoteInfo = lpadmin;
                };
+               27C3E9522DB04AC500A6ABBF /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 72BF96371333042100B1EAD7 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = 72220EAD1333047D00FCA411;
+                       remoteInfo = libcups;
+               };
+               27C3E95E2DB04ACE00A6ABBF /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 72BF96371333042100B1EAD7 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = 72220EAD1333047D00FCA411;
+                       remoteInfo = libcups;
+               };
+               27C3E96D2DB04B5800A6ABBF /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 72BF96371333042100B1EAD7 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = 27C3E95C2DB04ACE00A6ABBF;
+                       remoteInfo = "cups-oauth";
+               };
+               27C3E96F2DB04B5800A6ABBF /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 72BF96371333042100B1EAD7 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = 27C3E9502DB04AC500A6ABBF;
+                       remoteInfo = "cups-x509";
+               };
                720DD6CE1358FD790064AA82 /* PBXContainerItemProxy */ = {
                        isa = PBXContainerItemProxy;
                        containerPortal = 72BF96371333042100B1EAD7 /* Project object */;
                        );
                        runOnlyForDeploymentPostprocessing = 1;
                };
+               27C3E9572DB04AC500A6ABBF /* CopyFiles */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 2147483647;
+                       dstPath = /usr/share/man/man1/;
+                       dstSubfolderSpec = 0;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 1;
+               };
+               27C3E9632DB04ACE00A6ABBF /* CopyFiles */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 2147483647;
+                       dstPath = /usr/share/man/man1/;
+                       dstSubfolderSpec = 0;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 1;
+               };
                720DD6C01358FD5F0064AA82 /* CopyFiles */ = {
                        isa = PBXCopyFilesBuildPhase;
                        buildActionMask = 2147483647;
                278C58E8136B64B000836530 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = /System/Library/Frameworks/SystemConfiguration.framework; sourceTree = "<absolute>"; };
                279AE6F42395B80F004DD600 /* libpam.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libpam.tbd; path = usr/lib/libpam.tbd; sourceTree = SDKROOT; };
                27A0347B1A8BDB1300650675 /* lpadmin */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = lpadmin; sourceTree = BUILT_PRODUCTS_DIR; };
+               27C3E95B2DB04AC500A6ABBF /* cups-x509 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "cups-x509"; sourceTree = BUILT_PRODUCTS_DIR; };
+               27C3E9672DB04ACE00A6ABBF /* cups-oauth */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "cups-oauth"; sourceTree = BUILT_PRODUCTS_DIR; };
+               27C3E9682DB04AFB00A6ABBF /* cups-oauth.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "cups-oauth.c"; sourceTree = "<group>"; };
+               27C3E9692DB04AFB00A6ABBF /* cups-x509.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "cups-x509.c"; sourceTree = "<group>"; };
                27C89C902613E7C300A58F43 /* cups-tls.m4 */ = {isa = PBXFileReference; lastKnownFileType = text; name = "cups-tls.m4"; path = "../config-scripts/cups-tls.m4"; sourceTree = "<group>"; };
                27D3037D134148CB00F022B1 /* libcups2.def */ = {isa = PBXFileReference; lastKnownFileType = text; name = libcups2.def; path = ../cups/libcups2.def; sourceTree = "<group>"; };
                27F515432AAFBECF0045EE21 /* raster-testpage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "raster-testpage.h"; path = "../cups/raster-testpage.h"; sourceTree = "<group>"; };
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
+               27C3E9552DB04AC500A6ABBF /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               27C3E9562DB04AC500A6ABBF /* libcups2.dylib in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               27C3E9612DB04ACE00A6ABBF /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               27C3E9622DB04ACE00A6ABBF /* libcups2.dylib in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
                720DD6BF1358FD5F0064AA82 /* Frameworks */ = {
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                                276683681337AA00000D33D0 /* cupsctl.c */,
                                274FF68713333B6E00317ECB /* cupsfilter.c */,
                                72F75A5B1336F988004BB496 /* cupstestppd.c */,
+                               27C3E9682DB04AFB00A6ABBF /* cups-oauth.c */,
+                               27C3E9692DB04AFB00A6ABBF /* cups-x509.c */,
                                273B1EBD226B3EE300428143 /* ippevecommon.h */,
                                273B1EBE226B3EE300428143 /* ippevepcl.c */,
                                726AD701135E8A90002C930D /* ippeveprinter.c */,
                                2758FDF62C8F9C240078480C /* testoauth */,
                                2758FE072C8F9C5B0078480C /* testform */,
                                275CEA422CC840EE008FBB27 /* testclock */,
+                               27C3E95B2DB04AC500A6ABBF /* cups-x509 */,
+                               27C3E9672DB04ACE00A6ABBF /* cups-oauth */,
                        );
                        name = Products;
                        sourceTree = "<group>";
                        productReference = 27A0347B1A8BDB1300650675 /* lpadmin */;
                        productType = "com.apple.product-type.tool";
                };
+               27C3E9502DB04AC500A6ABBF /* cups-x509 */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = 27C3E9582DB04AC500A6ABBF /* Build configuration list for PBXNativeTarget "cups-x509" */;
+                       buildPhases = (
+                               27C3E9532DB04AC500A6ABBF /* Sources */,
+                               27C3E9552DB04AC500A6ABBF /* Frameworks */,
+                               27C3E9572DB04AC500A6ABBF /* CopyFiles */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                               27C3E9512DB04AC500A6ABBF /* PBXTargetDependency */,
+                       );
+                       name = "cups-x509";
+                       productName = cupsaddsmb;
+                       productReference = 27C3E95B2DB04AC500A6ABBF /* cups-x509 */;
+                       productType = "com.apple.product-type.tool";
+               };
+               27C3E95C2DB04ACE00A6ABBF /* cups-oauth */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = 27C3E9642DB04ACE00A6ABBF /* Build configuration list for PBXNativeTarget "cups-oauth" */;
+                       buildPhases = (
+                               27C3E95F2DB04ACE00A6ABBF /* Sources */,
+                               27C3E9612DB04ACE00A6ABBF /* Frameworks */,
+                               27C3E9632DB04ACE00A6ABBF /* CopyFiles */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                               27C3E95D2DB04ACE00A6ABBF /* PBXTargetDependency */,
+                       );
+                       name = "cups-oauth";
+                       productName = cupsaddsmb;
+                       productReference = 27C3E9672DB04ACE00A6ABBF /* cups-oauth */;
+                       productType = "com.apple.product-type.tool";
+               };
                720DD6C11358FD5F0064AA82 /* snmp */ = {
                        isa = PBXNativeTarget;
                        buildConfigurationList = 720DD6CB1358FD600064AA82 /* Build configuration list for PBXNativeTarget "snmp" */;
                                274FF6281333333600317ECB /* cups-deviced */,
                                274FF63D1333358B00317ECB /* cups-exec */,
                                274FF64E133339C400317ECB /* cups-lpd */,
+                               27C3E95C2DB04ACE00A6ABBF /* cups-oauth */,
+                               27C3E9502DB04AC500A6ABBF /* cups-x509 */,
                                274FF67713333B2F00317ECB /* cupsfilter */,
                                72F75A511336F950004BB496 /* cupstestppd */,
                                724379461333FEA9009631B9 /* dnssd */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
+               27C3E9532DB04AC500A6ABBF /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               27C3E96C2DB04B3600A6ABBF /* cups-x509.c in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               27C3E95F2DB04ACE00A6ABBF /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               27C3E96B2DB04B2900A6ABBF /* cups-oauth.c in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
                720DD6BE1358FD5F0064AA82 /* Sources */ = {
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        target = 27A0347A1A8BDB1200650675 /* lpadmin */;
                        targetProxy = 27A034861A8BDC6900650675 /* PBXContainerItemProxy */;
                };
+               27C3E9512DB04AC500A6ABBF /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = 72220EAD1333047D00FCA411 /* libcups2 */;
+                       targetProxy = 27C3E9522DB04AC500A6ABBF /* PBXContainerItemProxy */;
+               };
+               27C3E95D2DB04ACE00A6ABBF /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = 72220EAD1333047D00FCA411 /* libcups2 */;
+                       targetProxy = 27C3E95E2DB04ACE00A6ABBF /* PBXContainerItemProxy */;
+               };
+               27C3E96E2DB04B5800A6ABBF /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = 27C3E95C2DB04ACE00A6ABBF /* cups-oauth */;
+                       targetProxy = 27C3E96D2DB04B5800A6ABBF /* PBXContainerItemProxy */;
+               };
+               27C3E9702DB04B5800A6ABBF /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = 27C3E9502DB04AC500A6ABBF /* cups-x509 */;
+                       targetProxy = 27C3E96F2DB04B5800A6ABBF /* PBXContainerItemProxy */;
+               };
                720DD6CF1358FD790064AA82 /* PBXTargetDependency */ = {
                        isa = PBXTargetDependency;
                        target = 72220EAD1333047D00FCA411 /* libcups2 */;
                        };
                        name = Release;
                };
+               27C3E9592DB04AC500A6ABBF /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CODE_SIGN_IDENTITY = "-";
+                               DEAD_CODE_STRIPPING = YES;
+                               GCC_C_LANGUAGE_STANDARD = c99;
+                               INSTALL_PATH = /usr/bin;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                       };
+                       name = Debug;
+               };
+               27C3E95A2DB04AC500A6ABBF /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CODE_SIGN_IDENTITY = "-";
+                               DEAD_CODE_STRIPPING = YES;
+                               GCC_C_LANGUAGE_STANDARD = c99;
+                               INSTALL_PATH = /usr/bin;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                       };
+                       name = Release;
+               };
+               27C3E9652DB04ACE00A6ABBF /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CODE_SIGN_IDENTITY = "-";
+                               DEAD_CODE_STRIPPING = YES;
+                               GCC_C_LANGUAGE_STANDARD = c99;
+                               INSTALL_PATH = /usr/bin;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                       };
+                       name = Debug;
+               };
+               27C3E9662DB04ACE00A6ABBF /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CODE_SIGN_IDENTITY = "-";
+                               DEAD_CODE_STRIPPING = YES;
+                               GCC_C_LANGUAGE_STANDARD = c99;
+                               INSTALL_PATH = /usr/bin;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                       };
+                       name = Release;
+               };
                720DD6C91358FD5F0064AA82 /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
                        defaultConfigurationIsVisible = 0;
                        defaultConfigurationName = Release;
                };
+               27C3E9582DB04AC500A6ABBF /* Build configuration list for PBXNativeTarget "cups-x509" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               27C3E9592DB04AC500A6ABBF /* Debug */,
+                               27C3E95A2DB04AC500A6ABBF /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               27C3E9642DB04ACE00A6ABBF /* Build configuration list for PBXNativeTarget "cups-oauth" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               27C3E9652DB04ACE00A6ABBF /* Debug */,
+                               27C3E9662DB04ACE00A6ABBF /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
                720DD6CB1358FD600064AA82 /* Build configuration list for PBXNativeTarget "snmp" */ = {
                        isa = XCConfigurationList;
                        buildConfigurations = (
diff --git a/xcode/cups-oauth.c b/xcode/cups-oauth.c
new file mode 100644 (file)
index 0000000..aa128f0
--- /dev/null
@@ -0,0 +1,546 @@
+//
+// OAuth utility for CUPS.
+//
+// Copyright © 2024-2025 by OpenPrinting.
+//
+// Licensed under Apache License v2.0.  See the file "LICENSE" for more
+// information.
+//
+// Usage: cups-oauth [OPTIONS] [COMMAND [ARGUMENT(S)]]
+//
+// Commands:
+//
+//   authorize [RESOURCE]
+//   clear [RESOURCE]
+//   get-access-token [RESOURCE]
+//   get-client-id
+//   get-metadata [NAME]
+//   get-refresh-token [RESOURCE]
+//   get-user-id [RESOURCE] [NAME]
+//   set-access-token [RESOURCE] TOKEN
+//   set-client-data CLIENT-ID CLIENT-SECRET
+//
+// Options:
+//
+//   --help
+//   --version
+//   -a OAUTH-URI
+//   -s SCOPE(S)
+//
+
+#include <cups/cups-private.h>
+#include <cups/oauth.h>
+
+
+//
+// Macro for localized text...
+//
+
+#  define _(x) x
+
+
+//
+// Local functions...
+//
+
+static int     do_authorize(const char *oauth_uri, const char *scopes, const char *resource_uri);
+static int     do_clear(const char *oauth_uri, const char *resource_uri);
+static int     do_get_access_token(const char *oauth_uri, const char *resource_uri);
+static int     do_get_client_id(const char *oauth_uri);
+static int     do_get_metadata(const char *oauth_uri, const char *name);
+static int     do_get_user_id(const char *oauth_uri, const char *resource_uri, const char *name);
+static int     do_set_access_token(const char *oauth_uri, const char *resource_uri, const char *token);
+static int     do_set_client_data(const char *oauth_uri, const char *client_id, const char *client_secret);
+static int     usage(FILE *out);
+
+
+//
+// 'main()' - Main entry.
+//
+
+int                                    // O - Exit status
+main(int  argc,                                // I - Number of command-line arguments
+     char *argv[])                     // I - Command-line arguments
+{
+  int          i;                      // Looping var
+  const char   *opt,                   // Current option
+               *oauth_uri = getenv("CUPS_OAUTH_URI"),
+                                       // OAuth authorization server URI
+               *scopes = getenv("CUPS_OAUTH_SCOPES");
+                                       // Scopes
+
+
+  // Parse the command-line...
+  for (i = 1; i < argc; i ++)
+  {
+    if (!strcmp(argv[i], "--help"))
+    {
+      return (usage(stdout));
+    }
+    else if (!strcmp(argv[i], "--version"))
+    {
+      puts(CUPS_SVERSION);
+      exit(0);
+    }
+    else if (!strncmp(argv[i], "--", 2))
+    {
+      _cupsLangPrintf(stderr, _("%s: Unknown option '%s'."), "cups-oauth", argv[i]);
+      return (usage(stderr));
+    }
+    else if (argv[i][0] == '-' && argv[i][1] != '-')
+    {
+      for (opt = argv[i] + 1; *opt; opt ++)
+      {
+        switch (*opt)
+        {
+          case 'a' : // -a AUTH-URI
+              i ++;
+              if (i >= argc)
+              {
+                _cupsLangPuts(stderr, _("cups-oauth: Missing Authorization Server URI after '-a'."));
+                return (usage(stderr));
+              }
+
+              oauth_uri = argv[i];
+              break;
+
+          case 's' : // -s SCOPE(S)
+              i ++;
+              if (i >= argc)
+              {
+                _cupsLangPuts(stderr, _("cups-oauth: Missing scope(s) after '-s'."));
+                return (usage(stderr));
+              }
+
+              scopes = argv[i];
+              break;
+
+          default :
+              _cupsLangPrintf(stderr, _("%s: Unknown option '-%c'."), "cups-oauth", *opt);
+              return (usage(stderr));
+        }
+      }
+    }
+    else if (!oauth_uri)
+    {
+      _cupsLangPuts(stderr, _("cups-oauth: No authorization server specified."));
+      return (usage(stderr));
+    }
+    else if (!strcmp(argv[i], "authorize"))
+    {
+      // authorize [RESOURCE]
+      i ++;
+      return (do_authorize(oauth_uri, scopes, argv[i]));
+    }
+    else if (!strcmp(argv[i], "clear"))
+    {
+      // clear [RESOURCE]
+      i ++;
+      return (do_clear(oauth_uri, argv[i]));
+    }
+    else if (!strcmp(argv[i], "get-access-token"))
+    {
+      // get-access-token [RESOURCE]
+      i ++;
+      return (do_get_access_token(oauth_uri, argv[i]));
+    }
+    else if (!strcmp(argv[i], "get-client-id"))
+    {
+      // get-client-id
+      i ++;
+      return (do_get_client_id(oauth_uri));
+    }
+    else if (!strcmp(argv[i], "get-metadata"))
+    {
+      // get-metadata [NAME]
+      i ++;
+      return (do_get_metadata(oauth_uri, argv[i]));
+    }
+    else if (!strcmp(argv[i], "get-user-id"))
+    {
+      // get-user-id [RESOURCE] [NAME]
+      i ++;
+      if (i < argc)
+      {
+        if (!strncmp(argv[i], "ipp://", 6) || !strncmp(argv[i], "ipps://", 7) || !strncmp(argv[i], "http://", 7) || !strncmp(argv[i], "https://", 8))
+         return (do_get_user_id(oauth_uri, argv[i], argv[i + 1]));
+        else
+         return (do_get_user_id(oauth_uri, /*resource_uri*/NULL, argv[i]));
+      }
+      else
+      {
+        return (do_get_user_id(oauth_uri, /*resource_uri*/NULL, /*name*/NULL));
+      }
+    }
+    else if (!strcmp(argv[i], "set-access-token"))
+    {
+      // set-access-token [RESOURCE] TOKEN
+      i ++;
+      if (i >= argc)
+      {
+       _cupsLangPuts(stderr, _("cups-oauth: Missing resource URI and/or access token."));
+       return (usage(stderr));
+      }
+
+      return (do_set_access_token(oauth_uri, argv[i], argv[i + 1]));
+    }
+    else if (!strcmp(argv[i], "set-client-data"))
+    {
+      // set-client-data CLIENT-ID CLIENT-DATA
+      i ++;
+      if ((i + 1) >= argc)
+      {
+       _cupsLangPuts(stderr, _("cups-oauth: Missing client_id and/or client_secret."));
+       return (usage(stderr));
+      }
+
+      return (do_set_client_data(oauth_uri, argv[i], argv[i + 1]));
+    }
+    else
+    {
+      _cupsLangPrintf(stderr, _("cups-oauth: Unknown command '%s'."), argv[i]);
+      return (usage(stderr));
+    }
+  }
+
+  // If we get this far, show usage...
+  return (usage(argc == 1 ? stdout : stderr));
+}
+
+
+//
+// 'do_authorize()' - Authorize access.
+//
+
+static int                             // O - Exit status
+do_authorize(const char *oauth_uri,    // I - Authorization Server URI
+             const char *scopes,       // I - Scope(s)
+             const char *resource_uri) // I - Resource URI
+{
+  int          status = 1;             // Exit status
+  cups_json_t  *metadata;              // Server metadata
+  char         *auth_code = NULL,      // Authorization code
+               *access_token = NULL;   // Access token
+  time_t       access_expires;         // Expiration date
+
+
+  // Get the server metadata...
+  if ((metadata = cupsOAuthGetMetadata(oauth_uri)) == NULL)
+  {
+    _cupsLangPrintf(stderr, _("cups-oauth: Unable to get metadata for '%s': %s"), oauth_uri, cupsGetErrorString());
+    return (1);
+  }
+
+  // Authorize...
+  if ((auth_code = cupsOAuthGetAuthorizationCode(oauth_uri, metadata, resource_uri, scopes, /*redirect_uri*/NULL)) == NULL)
+  {
+    _cupsLangPrintf(stderr, _("cups-oauth: Unable to get authorization from '%s': %s"), oauth_uri, cupsGetErrorString());
+    goto done;
+  }
+
+  // Get the access token...
+  if ((access_token = cupsOAuthGetTokens(oauth_uri, metadata, resource_uri, auth_code, CUPS_OGRANT_AUTHORIZATION_CODE, CUPS_OAUTH_REDIRECT_URI, &access_expires)) == NULL)
+  {
+    _cupsLangPrintf(stderr, _("cups-oauth: Unable to get access token from '%s': %s"), oauth_uri, cupsGetErrorString());
+    goto done;
+  }
+
+  // Show access token
+  puts(access_token);
+
+  status = 0;
+
+  // Clean up and return...
+  done:
+
+  cupsJSONDelete(metadata);
+  free(auth_code);
+  free(access_token);
+
+  return (status);
+}
+
+
+//
+// 'do_clear()' - Clear authorization information.
+//
+
+static int                             // O - Exit status
+do_clear(const char *oauth_uri,                // I - Authorization Server URI
+         const char *resource_uri)     // I - Resource URI
+{
+  cupsOAuthClearTokens(oauth_uri, resource_uri);
+
+  return (0);
+}
+
+
+//
+// 'do_get_access_token()' - Get an access token.
+//
+
+static int                             // O - Exit status
+do_get_access_token(
+    const char *oauth_uri,             // I - Authorization Server URI
+    const char *resource_uri)          // I - Resource URI
+{
+  char         *access_token;          // Access token
+  time_t       access_expires;         // Expiration date
+
+
+  if ((access_token = cupsOAuthCopyAccessToken(oauth_uri, resource_uri, &access_expires)) != NULL)
+  {
+    puts(access_token);
+    free(access_token);
+    return (0);
+  }
+
+  return (1);
+}
+
+
+//
+// 'do_get_client_id()' - Get the client ID value.
+//
+
+static int                             // O - Exit status
+do_get_client_id(
+    const char *oauth_uri)             // I - Authorization Server URI
+{
+  char *client_id;                     // Client ID
+
+
+  if ((client_id = cupsOAuthCopyClientId(oauth_uri, CUPS_OAUTH_REDIRECT_URI)) != NULL)
+  {
+    puts(client_id);
+    free(client_id);
+    return (0);
+  }
+
+  return (1);
+}
+
+
+//
+// 'do_get_metadata()' - Get authorization server metadata.
+//
+
+static int                             // O - Exit status
+do_get_metadata(const char *oauth_uri, // I - Authorization Server URI
+                const char *name)      // I - Field name
+{
+  cups_json_t  *metadata;              // Metadata
+  char         *json;                  // JSON string
+
+
+  // Get the metadata...
+  if ((metadata = cupsOAuthGetMetadata(oauth_uri)) == NULL)
+  {
+    _cupsLangPrintf(stderr, _("cups-oauth: Unable to get metadata for '%s': %s"), oauth_uri, cupsGetErrorString());
+    return (1);
+  }
+
+  // Show metadata...
+  if (name)
+  {
+    cups_json_t        *value = cupsJSONFind(metadata, name);
+                                       // Metadata value
+
+    if (value)
+    {
+      switch (cupsJSONGetType(value))
+      {
+        case CUPS_JTYPE_NULL :
+            puts("null");
+            break;
+
+        case CUPS_JTYPE_FALSE :
+            puts("false");
+            break;
+
+        case CUPS_JTYPE_TRUE :
+            puts("true");
+            break;
+
+        case CUPS_JTYPE_NUMBER :
+            printf("%g\n", cupsJSONGetNumber(value));
+            break;
+
+        case CUPS_JTYPE_STRING :
+            puts(cupsJSONGetString(value));
+            break;
+
+        default :
+            if ((json = cupsJSONExportString(value)) != NULL)
+            {
+              puts(json);
+              free(json);
+            }
+            break;
+      }
+
+      return (0);
+    }
+    else
+    {
+      return (1);
+    }
+  }
+  else if ((json = cupsJSONExportString(metadata)) != NULL)
+  {
+    puts(json);
+    free(json);
+  }
+
+  return (0);
+}
+
+
+//
+// 'do_get_user_id()' - Get user identification.
+//
+
+static int                             // O - Exit status
+do_get_user_id(
+    const char *oauth_uri,             // I - Authorization Server URI
+    const char *resource_uri,          // I - Resource URI
+    const char *name)                  // I - Claim name
+{
+  cups_jwt_t   *user_id;               // User ID information
+  cups_json_t  *claims;                // Claims
+  char         *json,                  // JSON string
+               date[256];              // Date
+  int          ret = 0;                // Exit status
+
+
+  // Get the user_id...
+  if ((user_id = cupsOAuthCopyUserId(oauth_uri, resource_uri)) == NULL)
+  {
+    _cupsLangPrintf(stderr, _("cups-oauth: Unable to get user ID for '%s': %s"), oauth_uri, cupsGetErrorString());
+    return (1);
+  }
+
+  claims = cupsJWTGetClaims(user_id);
+
+  // Show user information...
+  if (name)
+  {
+    cups_json_t        *value = cupsJSONFind(claims, name);
+                                       // Claim value
+
+    if (value)
+    {
+      switch (cupsJSONGetType(value))
+      {
+        case CUPS_JTYPE_NULL :
+            puts("null");
+            break;
+
+        case CUPS_JTYPE_FALSE :
+            puts("false");
+            break;
+
+        case CUPS_JTYPE_TRUE :
+            puts("true");
+            break;
+
+        case CUPS_JTYPE_NUMBER :
+            if (!strcmp(name, "exp") || !strcmp(name, "iat") || !strcmp(name, "nbf"))
+              puts(httpGetDateString2((time_t)cupsJSONGetNumber(value), date, sizeof(date)));
+            else
+             printf("%g\n", cupsJSONGetNumber(value));
+            break;
+
+        case CUPS_JTYPE_STRING :
+            puts(cupsJSONGetString(value));
+            break;
+
+        default :
+            if ((json = cupsJSONExportString(value)) != NULL)
+            {
+              puts(json);
+              free(json);
+            }
+            break;
+      }
+    }
+    else
+    {
+      ret = 1;
+    }
+  }
+  else if ((json = cupsJSONExportString(claims)) != NULL)
+  {
+    puts(json);
+    free(json);
+  }
+
+  cupsJWTDelete(user_id);
+
+  return (ret);
+}
+
+
+//
+// 'do_set_access_token()' - Set the access token.
+//
+
+static int                             // O - Exit status
+do_set_access_token(
+    const char *oauth_uri,             // I - Authorization Server URI
+    const char *resource_uri,          // I - Resource URI
+    const char *token)                 // I - Access token
+{
+  cupsOAuthSaveTokens(oauth_uri, resource_uri, token, /*access_expires*/time(NULL) + 365 * 86400, /*user_id*/NULL, /*refresh_token*/NULL);
+
+  return (0);
+}
+
+
+//
+// 'do_set_client_data()' - Save client_id and client_secret values.
+//
+
+static int                             // O - Exit status
+do_set_client_data(
+    const char *oauth_uri,             // I - Authorization Server URI
+    const char *client_id,             // I - Client ID
+    const char *client_secret)         // I - Client secret
+{
+  cupsOAuthSaveClientData(oauth_uri, CUPS_OAUTH_REDIRECT_URI, client_id, client_secret);
+
+  return (0);
+}
+
+
+//
+// 'usage()' - Show usage.
+//
+
+static int                             // O - Exit status
+usage(FILE *out)                       // I - Output file
+{
+  _cupsLangPuts(out, _("Usage: cups-oauth [OPTIONS] [COMMAND [ARGUMENT(S)]]"));
+  _cupsLangPuts(out, "");
+  _cupsLangPuts(out, _("Commands:"));
+  _cupsLangPuts(out, "");
+  _cupsLangPuts(out, _("authorize [RESOURCE]           Authorize access to a resource"));
+  _cupsLangPuts(out, _("clear [RESOURCE]               Clear the authorization for a resource"));
+  _cupsLangPuts(out, _("get-access-token [RESOURCE]    Get the current access token"));
+  _cupsLangPuts(out, _("get-client-id                  Get the client ID for the authorization server"));
+  _cupsLangPuts(out, _("get-metadata [NAME]            Get metadata from the authorization server"));
+  _cupsLangPuts(out, _("get-user-id [RESOURCE] [NAME]  Get the authorized user ID"));
+  _cupsLangPuts(out, _("set-access-token [RESOURCE] TOKEN\n"
+                      "                               Set the current access token"));
+  _cupsLangPuts(out, _("set-client-data CLIENT-ID CLIENT-SECRET\n"
+                      "                               Set the client ID and secret for the authorization server."));
+  _cupsLangPuts(out, "");
+  _cupsLangPuts(out, _("Options:"));
+  _cupsLangPuts(out, "");
+  _cupsLangPuts(out, _("--help                         Show this help"));
+  _cupsLangPuts(out, _("--version                      Show the program version"));
+  _cupsLangPuts(out, _("-a OAUTH-URI                   Specify the OAuth authorization server URL"));
+  _cupsLangPuts(out, _("-s SCOPE(S)                    Specify the scope(s) to authorize"));
+
+  return (out == stdout ? 0 : 1);
+}