From d3ef3271af4e9004e2e8c6a424536525719a0f25 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 18 Jun 2024 11:09:04 -0400 Subject: [PATCH] Sync up with TLS/X.509 changes in libcups v3. Sync up with userconfig/sysconfig changes in libcups v3. --- backend/ipp.c | 2 +- cups/adminutil.c | 2 +- cups/cups-private.h | 6 +- cups/cups.h | 2 +- cups/dest.c | 49 ++------ cups/globals.c | 117 +++++++++++------ cups/ppd-util.c | 2 +- cups/snmp.c | 2 +- cups/testcreds.c | 2 +- cups/testhttp.c | 15 ++- cups/tls-gnutls.c | 58 ++++++--- cups/tls-openssl.c | 74 +++++++++-- cups/tls.c | 301 +++++++++++++++++++++++++++++++++++++++++++- cups/usersys.c | 10 +- systemv/lpstat.c | 4 +- 15 files changed, 515 insertions(+), 131 deletions(-) diff --git a/backend/ipp.c b/backend/ipp.c index 3b861be029..0e65869007 100644 --- a/backend/ipp.c +++ b/backend/ipp.c @@ -812,7 +812,7 @@ main(int argc, /* I - Number of command-line args */ if ((creds = httpCopyPeerCredentials(http)) != NULL) { - trust = cupsGetCredentialsTrust(NULL, hostname, creds); + trust = cupsGetCredentialsTrust(NULL, hostname, creds, /*require_ca*/false); cupsGetCredentialsInfo(creds, credinfo, sizeof(credinfo)); fprintf(stderr, "DEBUG: %s (%s)\n", trust_msgs[trust], cupsGetErrorString()); diff --git a/cups/adminutil.c b/cups/adminutil.c index 8c2ceb2594..448164a366 100644 --- a/cups/adminutil.c +++ b/cups/adminutil.c @@ -1368,7 +1368,7 @@ get_cupsd_conf( if (_cups_strcasecmp(cg->cupsd_hostname, host)) invalidate_cupsd_cache(cg); - snprintf(name, namesize, "%s/cupsd.conf", cg->cups_serverroot); + snprintf(name, namesize, "%s/cupsd.conf", cg->sysconfig); *remote = 0; #ifndef _WIN32 diff --git a/cups/cups-private.h b/cups/cups-private.h index bbf1c74ae2..b3275217c7 100644 --- a/cups/cups-private.h +++ b/cups/cups-private.h @@ -69,10 +69,9 @@ typedef struct _cups_globals_s // CUPS global state data // Multiple places... const char *cups_datadir, // CUPS_DATADIR environment var *cups_serverbin,// CUPS_SERVERBIN environment var - *cups_serverroot, - // CUPS_SERVERROOT environment var + *sysconfig, // System configuration directory (influenced by CUPS_SERVERROOT environment var) *cups_statedir, // CUPS_STATEDIR environment var - *home, // HOME environment var + *userconfig, // User configuration directory (influenced by various environment vars) *localedir; // LOCALDIR environment var // adminutil.c @@ -190,7 +189,6 @@ typedef struct _cups_globals_s // CUPS global state data char pw_buf[PW_BUF_SIZE]; // Big buffer for struct passwd buffers # endif // !_WIN32 - const char *userconfig; // User-specific config files } _cups_globals_t; typedef struct _cups_media_db_s // Media database diff --git a/cups/cups.h b/cups/cups.h index b0f15ca5f1..8e527a2e9f 100644 --- a/cups/cups.h +++ b/cups/cups.h @@ -430,7 +430,7 @@ extern void cupsFreeOptions(int num_options, cups_option_t *options) _CUPS_PUBL extern int cupsGetClasses(char ***classes) _CUPS_DEPRECATED_MSG("Use cupsEnumDests instead."); extern time_t cupsGetCredentialsExpiration(const char *credentials) _CUPS_PUBLIC; extern char *cupsGetCredentialsInfo(const char *credentials, char *buffer, size_t bufsize) _CUPS_PUBLIC; -extern http_trust_t cupsGetCredentialsTrust(const char *path, const char *common_name, const char *credentials) _CUPS_PUBLIC; +extern http_trust_t cupsGetCredentialsTrust(const char *path, const char *common_name, const char *credentials, bool require_ca) _CUPS_PUBLIC; extern const char *cupsGetDefault(void) _CUPS_PUBLIC; extern const char *cupsGetDefault2(http_t *http) _CUPS_PUBLIC; extern cups_dest_t *cupsGetDest(const char *name, const char *instance, int num_dests, cups_dest_t *dests) _CUPS_PUBLIC; diff --git a/cups/dest.c b/cups/dest.c index 8e0713f4ed..69708579e3 100644 --- a/cups/dest.c +++ b/cups/dest.c @@ -1694,17 +1694,13 @@ cupsGetNamedDest(http_t *http, // I - Connection to server or @code CUPS_HTT else instance = NULL; } - else if (cg->home) + else if (cg->userconfig) { /* * No default in the environment, try the user's lpoptions files... */ -#if _WIN32 - snprintf(filename, sizeof(filename), "%s/AppData/Local/cups/lpoptions", cg->home); -#else - snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", cg->home); -#endif // _WIN32 + snprintf(filename, sizeof(filename), "%s/lpoptions", cg->userconfig); dest_name = cups_get_default(filename, defname, sizeof(defname), &instance); @@ -1718,7 +1714,7 @@ cupsGetNamedDest(http_t *http, // I - Connection to server or @code CUPS_HTT * Still not there? Try the system lpoptions file... */ - snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); + snprintf(filename, sizeof(filename), "%s/lpoptions", cg->sysconfig); dest_name = cups_get_default(filename, defname, sizeof(defname), &instance); if (dest_name) @@ -1806,16 +1802,12 @@ cupsGetNamedDest(http_t *http, // I - Connection to server or @code CUPS_HTT // Then add local options... // - snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); + snprintf(filename, sizeof(filename), "%s/lpoptions", cg->sysconfig); cups_get_dests(filename, dest_name, instance, 0, 1, 1, &dest); - if (cg->home) + if (cg->userconfig) { -#if _WIN32 - snprintf(filename, sizeof(filename), "%s/AppData/Local/cups/lpoptions", cg->home); -#else - snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", cg->home); -#endif // _WIN32 + snprintf(filename, sizeof(filename), "%s/lpoptions", cg->userconfig); cups_get_dests(filename, dest_name, instance, 0, 1, 1, &dest); } @@ -1984,9 +1976,9 @@ cupsSetDests2(http_t *http, // I - Connection to server or @code CUPS_HTTP_ // Figure out which file to write to... // - snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); + snprintf(filename, sizeof(filename), "%s/lpoptions", cg->sysconfig); - if (cg->home + if (cg->userconfig #ifndef _WIN32 && getuid() != 0 #endif // !_WIN32 @@ -1996,19 +1988,9 @@ cupsSetDests2(http_t *http, // I - Connection to server or @code CUPS_HTTP_ * Create ~/.cups subdirectory... */ -#if _WIN32 - snprintf(filename, sizeof(filename), "%s/AppData/Local/cups", cg->home); -#else - snprintf(filename, sizeof(filename), "%s/.cups", cg->home); -#endif // _WIN32 - if (access(filename, 0)) - mkdir(filename, 0700); + mkdir(cg->userconfig, 0700); -#if _WIN32 - snprintf(filename, sizeof(filename), "%s/AppData/Local/cups/lpoptions", cg->home); -#else - snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", cg->home); -#endif // _WIN32 + snprintf(filename, sizeof(filename), "%s/lpoptions", cg->userconfig); } // @@ -3109,17 +3091,12 @@ cups_enum_dests( user_default = _cupsGetUserDefault(data.def_name, sizeof(data.def_name)); - snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); + snprintf(filename, sizeof(filename), "%s/lpoptions", cg->sysconfig); data.num_dests = cups_get_dests(filename, NULL, NULL, 1, user_default != NULL, data.num_dests, &data.dests); - if (cg->home) + if (cg->userconfig) { - // TODO: Use cg->userconfig -#if _WIN32 - snprintf(filename, sizeof(filename), "%s/AppData/Local/cups/lpoptions", cg->home); -#else - snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", cg->home); -#endif // _WIN32 + snprintf(filename, sizeof(filename), "%s/lpoptions", cg->userconfig); data.num_dests = cups_get_dests(filename, NULL, NULL, 1, user_default != NULL, data.num_dests, &data.dests); } diff --git a/cups/globals.c b/cups/globals.c index a72220a020..185b634f54 100644 --- a/cups/globals.c +++ b/cups/globals.c @@ -174,14 +174,6 @@ cups_globals_alloc(void) { _cups_globals_t *cg = calloc(1, sizeof(_cups_globals_t)); /* Pointer to global data */ -#ifdef _WIN32 - HKEY key; /* Registry key */ - DWORD size; /* Size of string */ - static char homedir[1024] = "", /* Home directory */ - installdir[1024] = "", /* Install directory */ - confdir[1024] = "", /* Server root directory */ - localedir[1024] = ""; /* Locale directory */ -#endif /* _WIN32 */ if (!cg) @@ -212,6 +204,13 @@ cups_globals_alloc(void) */ #ifdef _WIN32 + HKEY key; /* Registry key */ + DWORD size; /* Size of string */ + static char installdir[1024] = "", /* Install directory */ + localedir[1024] = "", /* Locale directory */ + sysconfig[1024] = "", /* Server configuration directory */ + userconfig[1024] = ""; /* User configuration directory */ + if (!installdir[0]) { /* @@ -248,7 +247,7 @@ cups_globals_alloc(void) } } - snprintf(confdir, sizeof(confdir), "%s/conf", installdir); + snprintf(sysconfig, sizeof(sysconfig), "%s/conf", installdir); snprintf(localedir, sizeof(localedir), "%s/locale", installdir); } @@ -258,8 +257,8 @@ cups_globals_alloc(void) if ((cg->cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL) cg->cups_serverbin = installdir; - if ((cg->cups_serverroot = getenv("CUPS_SERVERROOT")) == NULL) - cg->cups_serverroot = confdir; + if ((cg->sysconfig = getenv("CUPS_SERVERROOT")) == NULL) + cg->sysconfig = confdir; if ((cg->cups_statedir = getenv("CUPS_STATEDIR")) == NULL) cg->cups_statedir = confdir; @@ -267,31 +266,36 @@ cups_globals_alloc(void) if ((cg->localedir = getenv("LOCALEDIR")) == NULL) cg->localedir = localedir; - if (!homedir[0]) + if (!userconfig[0]) { const char *userprofile = getenv("USERPROFILE"); // User profile (home) directory - char *homeptr; // Pointer into homedir + char *userptr; // Pointer into userconfig DEBUG_printf("cups_globals_alloc: USERPROFILE=\"%s\"", userprofile); - if (!strncmp(userprofile, "C:\\", 3)) - userprofile += 2; - - cupsCopyString(homedir, userprofile, sizeof(homedir)); - for (homeptr = homedir; *homeptr; homeptr ++) + snprintf(userconfig, sizeof(userconfig), "%s/AppData/Local/cups", userprofile); + for (userptr = userconfig; *userptr; userptr ++) { // Convert back slashes to forward slashes - if (*homeptr == '\\') - *homeptr = '/'; + if (*userptr == '\\') + *userptr = '/'; } - DEBUG_printf("cups_globals_alloc: homedir=\"%s\"", homedir); + DEBUG_printf("cups_globals_alloc: userconfig=\"%s\"", userconfig); } - cg->home = homedir; + cg->userconfig = userconfig; #else + const char *home = getenv("HOME"); // HOME environment variable + char homedir[1024], // Home directory from account + temp[1024]; // Temporary directory string +# ifndef __APPLE__ + const char *snap_common = getenv("SNAP_COMMON"), + *xdg_config_home = getenv("XDG_CONFIG_HOME"); + // Environment variables +# endif // !__APPLE__ # ifdef HAVE_GETEUID if ((geteuid() != getuid() && getuid()) || getegid() != getgid()) # else @@ -305,7 +309,7 @@ cups_globals_alloc(void) cg->cups_datadir = CUPS_DATADIR; cg->cups_serverbin = CUPS_SERVERBIN; - cg->cups_serverroot = CUPS_SERVERROOT; + cg->sysconfig = CUPS_SERVERROOT; cg->cups_statedir = CUPS_STATEDIR; cg->localedir = CUPS_LOCALEDIR; } @@ -321,32 +325,73 @@ cups_globals_alloc(void) if ((cg->cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL) cg->cups_serverbin = CUPS_SERVERBIN; - if ((cg->cups_serverroot = getenv("CUPS_SERVERROOT")) == NULL) - cg->cups_serverroot = CUPS_SERVERROOT; + if ((cg->sysconfig = getenv("CUPS_SERVERROOT")) == NULL) + cg->sysconfig = CUPS_SERVERROOT; if ((cg->cups_statedir = getenv("CUPS_STATEDIR")) == NULL) cg->cups_statedir = CUPS_STATEDIR; if ((cg->localedir = getenv("LOCALEDIR")) == NULL) cg->localedir = CUPS_LOCALEDIR; - - cg->home = getenv("HOME"); - -# ifdef __APPLE__ /* Sandboxing now exposes the container as the home directory */ - if (cg->home && strstr(cg->home, "/Library/Containers/")) - cg->home = NULL; -# endif /* !__APPLE__ */ } - if (!cg->home) +# ifdef __APPLE__ + if (!home) +#else + if (!home && !xdg_config_home) +# endif // __APPLE__ + if (!home) { struct passwd pw; /* User info */ struct passwd *result; /* Auxiliary pointer */ getpwuid_r(getuid(), &pw, cg->pw_buf, PW_BUF_SIZE, &result); if (result) - cg->home = _cupsStrAlloc(pw.pw_dir); + { + cupsCopyString(homedir, pw.pw_dir, sizeof(homedir)); + home = homedir; + } + } + +# ifdef __APPLE__ + if (home) + { + // macOS uses ~/Library/Application Support/FOO + snprintf(temp, sizeof(temp), "%s/Library/Application Support/cups", home); } + else + { + // Something went wrong, use temporary directory... + snprintf(temp, sizeof(temp), "/private/tmp/cups%u", (unsigned)getuid()); + } + +# else + if (snap_common) + { + // Snaps use $SNAP_COMMON/FOO + snprintf(temp, sizeof(temp), "%s/cups", snap_common); + } + else if (xdg_config_home) + { + // XDG uses $XDG_CONFIG_HOME/FOO + snprintf(temp, sizeof(temp), "%s/cups", xdg_config_home); + } + else if (home) + { + // Use ~/.cups if it exists, otherwise ~/.config/cups (XDG standard) + snprintf(temp, sizeof(temp), "%s/.cups", home); + if (access(temp, 0)) + snprintf(temp, sizeof(temp), "%s/.config/cups", home); + } + else + { + // Something went wrong, use temporary directory... + snprintf(temp, sizeof(temp), "/tmp/cups%u", (unsigned)getuid()); + } +# endif // __APPLE__ + + // Can't use _cupsStrAlloc since it causes a loop with debug logging enabled + cg->userconfig = strdup(temp); #endif /* _WIN32 */ return (cg); @@ -388,8 +433,8 @@ cups_globals_free(_cups_globals_t *cg) /* I - Pointer to global data */ cupsFreeOptions(cg->cupsd_num_settings, cg->cupsd_settings); - if (cg->raster_error.start) - free(cg->raster_error.start); + free(cg->userconfig); + free(cg->raster_error.start); free(cg); } diff --git a/cups/ppd-util.c b/cups/ppd-util.c index 531db86314..ca55f97b1a 100644 --- a/cups/ppd-util.c +++ b/cups/ppd-util.c @@ -188,7 +188,7 @@ cupsGetPPD3(http_t *http, /* I - HTTP connection or @code CUPS_HTTP_DEFAUL struct stat ppdinfo; /* PPD file information */ - snprintf(ppdname, sizeof(ppdname), "%s/ppd/%s.ppd", cg->cups_serverroot, + snprintf(ppdname, sizeof(ppdname), "%s/ppd/%s.ppd", cg->sysconfig, name); if (!stat(ppdname, &ppdinfo) && !access(ppdname, R_OK)) { diff --git a/cups/snmp.c b/cups/snmp.c index 3d68a5e424..8aff0329f7 100644 --- a/cups/snmp.c +++ b/cups/snmp.c @@ -123,7 +123,7 @@ _cupsSNMPDefaultCommunity(void) { cupsCopyString(cg->snmp_community, "public", sizeof(cg->snmp_community)); - snprintf(line, sizeof(line), "%s/snmp.conf", cg->cups_serverroot); + snprintf(line, sizeof(line), "%s/snmp.conf", cg->sysconfig); if ((fp = cupsFileOpen(line, "r")) != NULL) { linenum = 0; diff --git a/cups/testcreds.c b/cups/testcreds.c index e393ee00f4..5018df874c 100644 --- a/cups/testcreds.c +++ b/cups/testcreds.c @@ -702,7 +702,7 @@ test_client(const char *uri) // I - URI puts("TLS Server Credentials:"); if ((hcreds = httpCopyPeerCredentials(http)) != NULL) { - trust = cupsGetCredentialsTrust(TEST_CERT_PATH, hostname, hcreds); + trust = cupsGetCredentialsTrust(TEST_CERT_PATH, hostname, hcreds, /*require_ca*/false); cupsGetCredentialsInfo(hcreds, hinfo, sizeof(hinfo)); diff --git a/cups/testhttp.c b/cups/testhttp.c index 77a4272771..98dbe4acab 100644 --- a/cups/testhttp.c +++ b/cups/testhttp.c @@ -1,7 +1,7 @@ // // HTTP test program for CUPS. // -// Copyright © 2020-2024 by OpenPrinting. +// Copyright © 2021-2024 by OpenPrinting. // Copyright © 2007-2018 by Apple Inc. // Copyright © 1997-2006 by Easy Software Products. // @@ -558,7 +558,7 @@ main(int argc, // I - Number of command-line arguments // Test HTTP GET requests... http = NULL; - out = stdout; + out = stdout; for (i = 1; i < argc; i ++) { @@ -600,7 +600,7 @@ main(int argc, // I - Number of command-line arguments if ((creds = httpCopyPeerCredentials(http)) != NULL) { char *lcreds; - http_trust_t trust = cupsGetCredentialsTrust(NULL, hostname, creds); + http_trust_t trust = cupsGetCredentialsTrust(NULL, hostname, creds, /*require_ca*/true); cupsGetCredentialsInfo(creds, info, sizeof(info)); @@ -637,8 +637,9 @@ main(int argc, // I - Number of command-line arguments if (trust != HTTP_TRUST_OK) { printf("SaveCredentials: %s\n", cupsSaveCredentials(NULL, hostname, creds, /*key*/NULL) ? "true" : "false"); - trust = cupsGetCredentialsTrust(NULL, hostname, creds); + trust = cupsGetCredentialsTrust(NULL, hostname, creds, /*require_ca*/true); printf("New Trust: %s\n", trusts[trust]); + printf("SaveCredentials (NULL): %s\n", cupsSaveCredentials(NULL, hostname, /*creds*/NULL, /*key*/NULL) ? "true" : "false"); } free(creds); @@ -656,6 +657,7 @@ main(int argc, // I - Number of command-line arguments do { + puts("Sending HEAD request..."); if (!_cups_strcasecmp(httpGetField(http, HTTP_FIELD_CONNECTION), "close")) { httpClearFields(http); @@ -673,7 +675,7 @@ main(int argc, // I - Number of command-line arguments httpSetField(http, HTTP_FIELD_AUTHORIZATION, httpGetAuthString(http)); httpSetField(http, HTTP_FIELD_ACCEPT_LANGUAGE, "en"); - if (httpWriteRequest(http, "HEAD", resource)) + if (!httpWriteRequest(http, "HEAD", resource)) { if (!httpReconnect2(http, 30000, NULL)) { @@ -749,6 +751,7 @@ main(int argc, // I - Number of command-line arguments do { + puts("Sending GET request..."); if (!_cups_strcasecmp(httpGetField(http, HTTP_FIELD_CONNECTION), "close")) { httpClearFields(http); @@ -767,7 +770,7 @@ main(int argc, // I - Number of command-line arguments httpSetField(http, HTTP_FIELD_ACCEPT_LANGUAGE, "en"); httpSetField(http, HTTP_FIELD_ACCEPT_ENCODING, encoding); - if (httpWriteRequest(http, "GET", resource)) + if (!httpWriteRequest(http, "GET", resource)) { if (!httpReconnect2(http, 30000, NULL)) { diff --git a/cups/tls-gnutls.c b/cups/tls-gnutls.c index 87d493b1ec..65e07b58ff 100644 --- a/cups/tls-gnutls.c +++ b/cups/tls-gnutls.c @@ -1,7 +1,9 @@ // // TLS support code for CUPS using GNU TLS. // -// Copyright © 2020-2023 by OpenPrinting +// Note: This file is included from tls.c +// +// Copyright © 2020-2024 by OpenPrinting // Copyright © 2007-2019 by Apple Inc. // Copyright © 1997-2007 by Easy Software Products, all rights reserved. // @@ -9,9 +11,6 @@ // information. // -//// This file is included from tls.c - - // // Local functions... // @@ -764,12 +763,37 @@ cupsGetCredentialsInfo( // // 'cupsGetCredentialsTrust()' - Return the trust of credentials. // +// This function determines the level of trust for the supplied credentials. +// The "path" parameter specifies the certificate/key store for known +// credentials and certificate authorities. The "common_name" parameter +// specifies the FQDN of the service being accessed such as +// "printer.example.com". The "credentials" parameter provides the credentials +// being evaluated, which are usually obtained with the +// @link httpCopyPeerCredentials@ function. The "require_ca" parameter +// specifies whether a CA-signed certificate is required for trust. +// +// The `AllowAnyRoot`, `AllowExpiredCerts`, `TrustOnFirstUse`, and +// `ValidateCerts` options in the "client.conf" file (or corresponding +// preferences file on macOS) control the trust policy, which defaults to +// AllowAnyRoot=Yes, AllowExpiredCerts=No, TrustOnFirstUse=Yes, and +// ValidateCerts=No. When the "require_ca" parameter is `true` the AllowAnyRoot +// and TrustOnFirstUse policies are turned off ("No"). +// +// The returned trust value can be one of the following: +// +// - `HTTP_TRUST_OK`: Credentials are OK/trusted +// - `HTTP_TRUST_INVALID`: Credentials are invalid +// - `HTTP_TRUST_EXPIRED`: Credentials are expired +// - `HTTP_TRUST_RENEWED`: Credentials have been renewed +// - `HTTP_TRUST_UNKNOWN`: Credentials are unknown/new +// http_trust_t // O - Level of trust cupsGetCredentialsTrust( const char *path, // I - Directory path for certificate/key store or `NULL` for default const char *common_name, // I - Common name for trust lookup - const char *credentials) // I - Credentials + const char *credentials, // I - Credentials + bool require_ca) // I - Require a CA-signed certificate? { http_trust_t trust = HTTP_TRUST_OK; // Trusted? @@ -817,7 +841,7 @@ cupsGetCredentialsTrust( { // Credentials don't match, let's look at the expiration date of the new // credentials and allow if the new ones have a later expiration... - if (!cg->trust_first) + if (!cg->trust_first || require_ca) { // Do not trust certificates on first use... _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Trust on first use is disabled."), 1); @@ -849,12 +873,12 @@ cupsGetCredentialsTrust( free(tcreds); } - else if (cg->validate_certs && !cupsAreCredentialsValidForName(common_name, credentials)) + else if ((cg->validate_certs || require_ca) && !cupsAreCredentialsValidForName(common_name, credentials)) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No stored credentials, not valid for name."), 1); trust = HTTP_TRUST_INVALID; } - else if (!cg->trust_first) + else if (num_certs > 1 && !http_check_roots(credentials)) { // See if we have a site CA certificate we can compare... if ((tcreds = cupsCopyCredentials(path, "_site_")) != NULL) @@ -877,11 +901,21 @@ cupsGetCredentialsTrust( free(tcreds); } - else + else if (require_ca) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Trust on first use is disabled."), 1); trust = HTTP_TRUST_INVALID; } + else if (!cg->trust_first) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Trust on first use is disabled."), 1); + trust = HTTP_TRUST_INVALID; + } + } + else if ((!cg->any_root || require_ca) && num_certs == 1) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Self-signed credentials are blocked."), 1); + trust = HTTP_TRUST_INVALID; } if (trust == HTTP_TRUST_OK && !cg->expired_certs) @@ -896,12 +930,6 @@ cupsGetCredentialsTrust( } } - if (trust == HTTP_TRUST_OK && !cg->any_root && num_certs == 1) - { - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Self-signed credentials are blocked."), 1); - trust = HTTP_TRUST_INVALID; - } - gnutls_free_certs(num_certs, certs); return (trust); diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index bf5819dc01..ff0eccf4b8 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -736,12 +736,37 @@ cupsGetCredentialsInfo( // // 'cupsGetCredentialsTrust()' - Return the trust of credentials. // +// This function determines the level of trust for the supplied credentials. +// The "path" parameter specifies the certificate/key store for known +// credentials and certificate authorities. The "common_name" parameter +// specifies the FQDN of the service being accessed such as +// "printer.example.com". The "credentials" parameter provides the credentials +// being evaluated, which are usually obtained with the +// @link httpCopyPeerCredentials@ function. The "require_ca" parameter +// specifies whether a CA-signed certificate is required for trust. +// +// The `AllowAnyRoot`, `AllowExpiredCerts`, `TrustOnFirstUse`, and +// `ValidateCerts` options in the "client.conf" file (or corresponding +// preferences file on macOS) control the trust policy, which defaults to +// AllowAnyRoot=Yes, AllowExpiredCerts=No, TrustOnFirstUse=Yes, and +// ValidateCerts=No. When the "require_ca" parameter is `true` the AllowAnyRoot +// and TrustOnFirstUse policies are turned off ("No"). +// +// The returned trust value can be one of the following: +// +// - `HTTP_TRUST_OK`: Credentials are OK/trusted +// - `HTTP_TRUST_INVALID`: Credentials are invalid +// - `HTTP_TRUST_EXPIRED`: Credentials are expired +// - `HTTP_TRUST_RENEWED`: Credentials have been renewed +// - `HTTP_TRUST_UNKNOWN`: Credentials are unknown/new +// http_trust_t // O - Level of trust cupsGetCredentialsTrust( const char *path, // I - Directory path for certificate/key store or `NULL` for default const char *common_name, // I - Common name for trust lookup - const char *credentials) // I - Credentials + const char *credentials, // I - Credentials + bool require_ca) // I - Require a CA-signed certificate? { http_trust_t trust = HTTP_TRUST_OK; // Trusted? @@ -790,7 +815,7 @@ cupsGetCredentialsTrust( { // Credentials don't match, let's look at the expiration date of the new // credentials and allow if the new ones have a later expiration... - if (!cg->trust_first) + if (!cg->trust_first || require_ca) { // Do not trust certificates on first use... _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Trust on first use is disabled."), 1); @@ -822,12 +847,12 @@ cupsGetCredentialsTrust( free(tcreds); } - else if (cg->validate_certs && !cupsAreCredentialsValidForName(common_name, credentials)) + else if ((cg->validate_certs || require_ca) && !cupsAreCredentialsValidForName(common_name, credentials)) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No stored credentials, not valid for name."), 1); trust = HTTP_TRUST_INVALID; } - else if (!cg->trust_first) + else if (sk_X509_num(certs) > 1 && !http_check_roots(credentials)) { // See if we have a site CA certificate we can compare... if ((tcreds = cupsCopyCredentials(path, "_site_")) != NULL) @@ -850,12 +875,22 @@ cupsGetCredentialsTrust( free(tcreds); } - else + else if (require_ca) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Trust on first use is disabled."), 1); + trust = HTTP_TRUST_INVALID; + } + else if (!cg->trust_first) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Trust on first use is disabled."), 1); trust = HTTP_TRUST_INVALID; } } + else if ((!cg->any_root || require_ca) && sk_X509_num(certs) == 1) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Self-signed credentials are blocked."), 1); + trust = HTTP_TRUST_INVALID; + } if (trust == HTTP_TRUST_OK && !cg->expired_certs) { @@ -869,12 +904,6 @@ cupsGetCredentialsTrust( } } - if (trust == HTTP_TRUST_OK && !cg->any_root && sk_X509_num(certs) == 1) - { - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Self-signed credentials are blocked."), 1); - trust = HTTP_TRUST_INVALID; - } - sk_X509_free(certs); return (trust); @@ -1320,14 +1349,14 @@ httpCopyPeerCredentials(http_t *http) // I - Connection to server STACK_OF(X509) *chain; // Certificate chain - DEBUG_printf("httpCopyCredentials(http=%p)", (void *)http); + DEBUG_printf("httpCopyPeerCredentials(http=%p)", (void *)http); if (http && http->tls) { // Get the chain of certificates for the remote end... chain = SSL_get_peer_cert_chain(http->tls); - DEBUG_printf("1httpCopyCredentials: chain=%p", (void *)chain); + DEBUG_printf("1httpCopyPeerCredentials: chain=%p", (void *)chain); if (chain) { @@ -1342,6 +1371,21 @@ httpCopyPeerCredentials(http_t *http) // I - Connection to server BIO *bio = BIO_new(BIO_s_mem()); // Memory buffer for cert + DEBUG_printf("1httpCopyPeerCredentials: chain[%d/%d]=%p", i + 1, count, cert); + +#ifdef DEBUG + char subjectName[256], issuerName[256]; + X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, subjectName, sizeof(subjectName)); + X509_NAME_get_text_by_NID(X509_get_issuer_name(cert), NID_commonName, issuerName, sizeof(issuerName)); + DEBUG_printf("1httpCopyPeerCredentials: subjectName=\"%s\", issuerName=\"%s\"", subjectName, issuerName); + + STACK_OF(GENERAL_NAME) *names; // subjectAltName values + names = X509_get_ext_d2i(cert, NID_subject_alt_name, /*crit*/NULL, /*idx*/NULL); + DEBUG_printf("1httpCopyPeerCredentials: subjectAltNames=%p(%d)", names, names ? sk_GENERAL_NAME_num(names) : 0); + if (names) + GENERAL_NAMES_free(names); +#endif // DEBUG + if (bio) { long bytes; // Number of bytes @@ -1371,6 +1415,8 @@ httpCopyPeerCredentials(http_t *http) // I - Connection to server } } + DEBUG_printf("1httpCopyPeerCredentials: Returning \"%s\".", credentials); + return (credentials); } @@ -2331,6 +2377,8 @@ openssl_load_x509( X509_free(cert); break; } + + cert = NULL; } BIO_free(bio); diff --git a/cups/tls.c b/cups/tls.c index 174c0bc8d4..c02b73570e 100644 --- a/cups/tls.c +++ b/cups/tls.c @@ -10,9 +10,13 @@ // #include "cups-private.h" +#include "dir.h" #include #include #include +#ifdef __APPLE__ +# include +#endif // __APPLE__ #ifdef _WIN32 # include #else @@ -38,15 +42,23 @@ static cups_mutex_t tls_mutex = CUPS_MUTEX_INITIALIZER; static int tls_options = -1,// Options for TLS connections tls_min_version = _HTTP_TLS_1_2, tls_max_version = _HTTP_TLS_MAX; +#ifndef __APPLE__ +static cups_array_t *tls_root_certs = NULL; + // List of known root CAs +#endif // __APPLE__ // // Local functions... // +static bool http_check_roots(const char *creds); static char *http_copy_file(const char *path, const char *common_name, const char *ext); static const char *http_default_path(char *buffer, size_t bufsize); static bool http_default_san_cb(const char *common_name, const char *subject_alt_name, void *data); +#ifdef _WIN32 +static char *http_der_to_pem(const unsigned char *der, size_t dersize); +#endif // _WIN32 static const char *http_make_path(char *buffer, size_t bufsize, const char *dirname, const char *filename, const char *ext); static bool http_save_file(const char *path, const char *common_name, const char *ext, const char *value); @@ -113,7 +125,7 @@ bool // O - `true` on success, `false` on failure cupsSaveCredentials( const char *path, // I - Directory path for certificate/key store or `NULL` for default const char *common_name, // I - Common name for certificate - const char *credentials, // I - PEM-encoded certificate chain + const char *credentials, // I - PEM-encoded certificate chain or `NULL` to remove const char *key) // I - PEM-encoded private key or `NULL` for none { if (http_save_file(path, common_name, "crt", credentials)) @@ -195,6 +207,200 @@ _httpTLSSetOptions(int options, // I - Options } +// +// 'http_check_roots()' - Check whether the supplied credentials use a trusted root CA. +// + +static bool // O - `true` if they use a trusted root, `false` otherwise +http_check_roots(const char *creds) // I - Credentials +{ + bool ret = false; // Return value + + +#ifdef __APPLE__ + // Apple hides all of the keychain stuff (all deprecated) so the best we can + // do is use the SecTrust API to evaluate the certificate... + CFMutableArrayRef certs = NULL; // Certificates from credentials + SecCertificateRef cert; + char *tcreds = NULL, // Copy of credentials string + *tstart, // Start of certificate data + *tend, // End of certificate data + *der = NULL; // DER-encoded fragment buffer + size_t dersize, // Size of DER buffer + derlen; // Length of DER data + SecPolicyRef policy; // X.509 policy + SecTrustRef trust; // Trust evaluator + + + // Convert PEM-encoded credentials to an array of DER-encoded certificates... + if ((tcreds = strdup(creds)) == NULL) + goto done; + + if ((certs = CFArrayCreateMutable(kCFAllocatorDefault, /*capacity*/0, &kCFTypeArrayCallBacks)) == NULL) + goto done; + + dersize = 3 * strlen(tcreds) / 4; + if ((der = malloc(dersize)) == NULL) + goto done; + + for (tstart = strstr(tcreds, "-----BEGIN CERTIFICATE-----\n"); tstart; tstart = strstr(tend, "-----BEGIN CERTIFICATE-----\n")) + { + // Find the end of the certificate data... + tstart += 28; // Skip "-----BEGIN CERTIFICATE-----\n" + if ((tend = strstr(tstart, "-----END CERTIFICATE-----\n")) == NULL) + break; // Missing end... + + // Nul-terminate the cert data... + *tend++ = '\0'; + + // Convert to DER format + derlen = dersize; + if (httpDecode64_3(der, &derlen, tstart, /*end*/NULL)) + { + // Create a CFData object for the data... + CFDataRef data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)der, (CFIndex)derlen); + + if (data) + { + // Create a certificate from the DER data... + if ((cert = SecCertificateCreateWithData(kCFAllocatorDefault, data)) != NULL) + { + // Add certificate to the array... + CFArrayAppendValue(certs, cert); + CFRelease(cert); + } + + CFRelease(data); + } + } + } + + // Test the certificate list against the macOS/iOS trust store... + if ((policy = SecPolicyCreateBasicX509()) != NULL) + { + if (SecTrustCreateWithCertificates(certs, policy, &trust) == noErr) + { + ret = SecTrustEvaluateWithError(trust, NULL); + CFRelease(trust); + } + + CFRelease(policy); + } + + done: + + free(tcreds); + free(der); + + if (certs) + CFRelease(certs); + +#else + size_t credslen; // Length of credentials string + const char *rcreds; // Current root credential + size_t rcredslen; // Length of current root credential + + + cupsMutexLock(&tls_mutex); + + // Load root certificates as needed... + if (!tls_root_certs) + { + // Load root certificates... + tls_root_certs = cupsArrayNew(/*cb*/NULL, /*cb_data*/NULL, /*hash_cb*/NULL, /*hash_size*/0, /*copy_cb*/NULL, /*free_cb*/NULL); + +# ifdef _WIN32 + int i; // Looping var + HCERTSTORE store; // Certificate store + CERT_CONTEXT *cert; // Current certificate + + // Add certificates in both the "ROOT" and "CA" stores... + for (i = 0; i < 2; i ++) + { + if ((store = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, i ? L"CA" : L"ROOT")) == NULL) + continue; + + // Loop through certificates... + for (cert = CertEnumCertificatesInStore(store, NULL); cert; cert = CertEnumCertificatesInStore(store, cert)) + { + if (cert->dwCertEncodingType == X509_ASN_ENCODING) + { + // Convert DER to PEM and add to the list... + char * pem = http_der_to_pem(cert->pbCertEncoded, cert->cbCertEncoded); + + if (pem) + cupsArrayAdd(tls_root_certs, pem); + } + } + + CertCloseStore(store, 0); + } + +# else + size_t i; // Looping var + cups_dir_t *dir; // Directory + cups_dentry_t *dent; // Directory entry + const char *ext; // Pointer to filename extension + static const char * const root_dirs[] = + { // Root certificate stores + "/etc/ssl/certs", + "/system/etc/security/cacerts/", + + }; + + for (i = 0, dir = NULL; i < (sizeof(root_dirs) / sizeof(root_dirs[0])); i ++) + { + if ((dir = cupsDirOpen(root_dirs[i])) != NULL) + break; + } + + if (dir) + { + while ((dent = cupsDirRead(dir)) != NULL) + { + if ((ext = strrchr(dent->filename, '.')) != NULL && !strcmp(ext, ".pem")) + { + char filename[1024], // Certificate filename + *cert; // Certificate data + int fd; // File descriptor + + snprintf(filename, sizeof(filename), "%s/%s", root_dirs[i], dent->filename); + if ((fd = open(filename, O_RDONLY)) >= 0) + { + if ((cert = calloc(1, (size_t)(dent->fileinfo.st_size + 1))) != NULL) + { + read(fd, cert, (size_t)dent->fileinfo.st_size); + cupsArrayAdd(tls_root_certs, cert); + } + + close(fd); + } + } + } + } +# endif // _WIN32 + } + + // Check all roots + credslen = strlen(creds); + + for (rcreds = (const char *)cupsArrayGetFirst(tls_root_certs); rcreds && !ret; rcreds = (const char *)cupsArrayGetNext(tls_root_certs)) + { + // Compare the root against the tail of the current credentials... + rcredslen = strlen(rcreds); + + if (credslen >= rcredslen && !strcmp(creds + (credslen - rcredslen), rcreds)) + ret = true; + } + + // Unlock access and return... + cupsMutexUnlock(&tls_mutex); +#endif // __APPLE__ + + return (ret); +} + + // // 'http_copy_file()' - Copy the contents of a file to a string. // @@ -280,13 +486,13 @@ http_default_path( } else { - if (mkdir(cg->cups_serverroot, 0755) && errno != EEXIST) + if (mkdir(cg->sysconfig, 0755) && errno != EEXIST) { - DEBUG_printf("1http_default_path: Failed to make directory '%s': %s", cg->cups_serverroot, strerror(errno)); + DEBUG_printf("1http_default_path: Failed to make directory '%s': %s", cg->sysconfig, strerror(errno)); return (NULL); } - snprintf(buffer, bufsize, "%s/ssl", cg->cups_serverroot); + snprintf(buffer, bufsize, "%s/ssl", cg->sysconfig); if (mkdir(buffer, 0700) && errno != EEXIST) { @@ -325,6 +531,81 @@ http_default_san_cb( } +#ifdef _WIN32 +// +// 'http_der_to_pem()' - Convert DER format certificate data to PEM. +// + +static char * // O - PEM string +http_der_to_pem( + const unsigned char *der, // I - DER-encoded data + size_t dersize) // I - Size of DER-encoded data +{ + char *pem, // PEM-encoded string + *pemptr; // Pointer into PEM-encoded string + int col; // Current column + size_t pemsize; // Size of PEM-encoded string + static const char *base64 = // Base64 alphabet + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + + // Calculate the size, accounting for Base64 expansion, line wrapping at + // column 64, and the BEGIN/END CERTIFICATE text... + pemsize = 65 * 4 * dersize / 3 / 64 + /*"-----BEGIN CERTIFICATE-----"*/28 + /*"-----END CERTIFICATE-----"*/26 + 2; + + if ((pem = calloc(1, pemsize)) == NULL) + return (NULL); + + cupsCopyString(pem, "-----BEGIN CERTIFICATE-----\n", pemsize); + for (pemptr = pem, col = 0; dersize > 0; der += 3) + { + // Encode the up to 3 characters as 4 Base64 numbers... + switch (dersize) + { + case 1 : + *pemptr ++ = base64[(der[0] & 255) >> 2]; + *pemptr ++ = base64[((der[0] & 255) << 4) & 63]; + *pemptr ++ = '='; + *pemptr ++ = '='; + dersize = 0; + break; + case 2 : + *pemptr ++ = base64[(der[0] & 255) >> 2]; + *pemptr ++ = base64[(((der[0] & 255) << 4) | ((der[1] & 255) >> 4)) & 63]; + *pemptr ++ = base64[((der[1] & 255) << 2) & 63]; + *pemptr ++ = '='; + dersize = 0; + break; + default : + *pemptr ++ = base64[(der[0] & 255) >> 2]; + *pemptr ++ = base64[(((der[0] & 255) << 4) | ((der[1] & 255) >> 4)) & 63]; + *pemptr ++ = base64[(((der[1] & 255) << 2) | ((der[2] & 255) >> 6)) & 63]; + *pemptr ++ = base64[der[2] & 63]; + dersize -= 3; + break; + } + + // Add a newline as needed... + col += 4; + if (col >= 64) + { + *pemptr++ = '\n'; + col = 0; + } + } + + if (col > 0) + *pemptr++ = '\n'; + *pemptr = '\0'; + + cupsConcatString(pem, "-----END CERTIFICATE-----\n", pemsize); + + // Return the encoded string... + return (pem); +} +#endif // _WIN32 + + // // 'http_make_path()' - Format a filename for a certificate or key file. // @@ -379,14 +660,22 @@ http_save_file(const char *path, // I - Directory path for certificate/key store // Range check input... - if (!common_name || !value) + if (!common_name) return (false); // Get default path as needed... if (!path) path = http_default_path(defpath, sizeof(defpath)); - if ((fd = open(http_make_path(filename, sizeof(filename), path, common_name, ext), O_CREAT | O_WRONLY | O_TRUNC, 0644)) < 0) + http_make_path(filename, sizeof(filename), path, common_name, ext); + + if (!value) + { + unlink(filename); + return (true); + } + + if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644)) < 0) return (false); if (write(fd, value, strlen(value)) < 0) diff --git a/cups/usersys.c b/cups/usersys.c index 29d5ad57b3..4c6e077572 100644 --- a/cups/usersys.c +++ b/cups/usersys.c @@ -1094,24 +1094,20 @@ _cupsSetDefaults(void) * present. */ - snprintf(filename, sizeof(filename), "%s/client.conf", cg->cups_serverroot); + snprintf(filename, sizeof(filename), "%s/client.conf", cg->sysconfig); if ((fp = cupsFileOpen(filename, "r")) != NULL) { cups_read_client_conf(fp, &cc); cupsFileClose(fp); } - if (cg->home) + if (cg->userconfig) { /* * Look for ~/.cups/client.conf... */ -#if _WIN32 - snprintf(filename, sizeof(filename), "%s/AppData/Local/cups/client.conf", cg->home); -#else - snprintf(filename, sizeof(filename), "%s/.cups/client.conf", cg->home); -#endif // _WIN32 + snprintf(filename, sizeof(filename), "%s/client.conf", cg->userconfig); if ((fp = cupsFileOpen(filename, "r")) != NULL) { diff --git a/systemv/lpstat.c b/systemv/lpstat.c index f304328e53..d2297109ff 100644 --- a/systemv/lpstat.c +++ b/systemv/lpstat.c @@ -1877,7 +1877,7 @@ show_printers(const char *printers, /* I - Destinations */ if (make_model && !strstr(make_model, "Raw Printer")) _cupsLangPrintf(stdout, _("\tInterface: %s/ppd/%s.ppd"), - cg->cups_serverroot, printer); + cg->sysconfig, printer); } _cupsLangPuts(stdout, _("\tOn fault: no alert")); _cupsLangPuts(stdout, _("\tAfter fault: continue")); @@ -1996,7 +1996,7 @@ show_printers(const char *printers, /* I - Destinations */ if (make_model && !strstr(make_model, "Raw Printer")) _cupsLangPrintf(stdout, _("\tInterface: %s/ppd/%s.ppd"), - cg->cups_serverroot, printer); + cg->sysconfig, printer); } _cupsLangPuts(stdout, _("\tOn fault: no alert")); _cupsLangPuts(stdout, _("\tAfter fault: continue")); -- 2.47.2