// // 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] [SUB-COMMAND [ARGUMENT(S)]] // // Commands: // // authorize [RESOURCE] // clear [RESOURCE] // get-access-token [RESOURCE] // get-client-id // get-device-grant [RESOURCE] // get-device-token [RESOURCE] DEVICE-CODE // 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 #include // // 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_device_grant(const char *oauth_uri, const char *scopes, const char *resource_uri); static int do_get_device_token(const char *oauth_uri, const char *resource_uri, const char *device_code); 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-device-grant")) { // get-device-grant [RESOURCE] i ++; return (do_get_device_grant(oauth_uri, scopes, argv[i])); } else if (!strcmp(argv[i], "get-device-token")) { // get-access-token [RESOURCE] DEVICE-CODE i ++; if (i >= argc) { _cupsLangPuts(stderr, _("cups-oauth: Missing resource URI and/or device token.")); return (usage(stderr)); } if (argv[i + 1]) return (do_get_device_token(oauth_uri, argv[i], argv[i + 1])); else return (do_get_device_token(oauth_uri, /*resource_uri*/NULL, argv[i])); } 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)); } if (argv[i + 1]) return (do_set_access_token(oauth_uri, argv[i], argv[i + 1])); else return (do_set_access_token(oauth_uri, /*resource_uri*/NULL, argv[i])); } 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_device_grant()' - Start a device authorization workflow. // static int do_get_device_grant( 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 *grant = NULL; // Device authorization grant const char *device_code, // Device code *user_code, // User code *verification_url; // Verification URL // 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 ((grant = cupsOAuthGetDeviceGrant(oauth_uri, metadata, resource_uri, scopes)) == NULL) { _cupsLangPrintf(stderr, _("cups-oauth: Unable to get device authorization from '%s': %s"), oauth_uri, cupsGetErrorString()); goto done; } // Show grant... if ((device_code = cupsJSONGetString(cupsJSONFind(grant, CUPS_ODEVGRANT_DEVICE_CODE))) != NULL) _cupsLangPrintf(stdout, _("Device Code: %s"), device_code); if ((user_code = cupsJSONGetString(cupsJSONFind(grant, CUPS_ODEVGRANT_USER_CODE))) != NULL) _cupsLangPrintf(stdout, _("User Code: %s"), user_code); if ((verification_url = cupsJSONGetString(cupsJSONFind(grant, CUPS_ODEVGRANT_VERIFICATION_URI))) != NULL) _cupsLangPrintf(stdout, _("Web Page: %s"), verification_url); status = 0; // Clean up and return... done: cupsJSONDelete(metadata); cupsJSONDelete(grant); return (status); } // // 'do_get_device_token()' - Wait to get a device access token. // static int // O - Exit status do_get_device_token( const char *oauth_uri, // I - Authorization Server URI const char *resource_uri, // I - Resource URI const char *device_code) // I - Device code { int status = 1; // Exit status cups_json_t *metadata; // Server metadata char *access_token; // Device access token time_t access_expires; // Expiration date int interval; // Seconds to sleep // 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); } // Loop until we have an access token or a hard error... while ((access_token = cupsOAuthGetTokens(oauth_uri, metadata, resource_uri, device_code, CUPS_OGRANT_DEVICE_CODE, CUPS_OAUTH_REDIRECT_URI, &access_expires)) == NULL) { if (!access_expires) { _cupsLangPrintf(stderr, _("cups-oauth: Unable to get device token from '%s': %s"), oauth_uri, cupsGetErrorString()); goto done; } if ((interval = access_expires - time(NULL)) < 5) interval = 5; sleep((unsigned)interval); } // Show access token puts(access_token); status = 0; // Clean up and return... done: cupsJSONDelete(metadata); free(access_token); return (status); } // // '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 { int ret = 0; // Exit status 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; } } else { ret = 1; } } else if ((json = cupsJSONExportString(metadata)) != NULL) { puts(json); free(json); } cupsJSONDelete(metadata); return (ret); } // // '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] [SUB-COMMAND [ARGUMENT(S)]]")); _cupsLangPuts(out, ""); _cupsLangPuts(out, _("Sub-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-device-grant [RESOURCE] Authorize access through a device")); _cupsLangPuts(out, _("get-device-token [RESOURCE] DEVICE-CODE\n" " Wait for the device access token")); _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); }