/conf/mime.convs
/conf/pam.std
/conf/snmp.conf
+/cups/.testssl
/cups/cachebench
/cups/fuzzipp
/cups/libcups.dylib
/templates/*/header.tmpl
/test/cups-str-*.html
/test/*_log-*
+/tools/cups-oauth
+/tools/cups-x509
/tools/ippevepcl
/tools/ippeveprinter
/tools/ippeveprinter-static
- Added support for specifying permissions with the `cupsFileOpen` API.
- Added new `cupsGetClock` API.
- Added new `cupsParseOptions2` API with "end" argument.
+- Added `cups-oauth` and `cups-x509` utilities (Issue #1184)
- Updated documentation (Issue #984, Issue #1086)
- Updated the configure script to default to installing to /usr/local.
- Updated CUPS to require TLS support - OpenSSL, GNUTLS and LibreSSL are
// names to request during authorization. The list of supported scope names are
// available from the Authorization Server metadata, for example:
//
-// The "redirect_uri" parameter specifies a 'http:' URL with a listen address,
-// port, and path to use. If `NULL`, 127.0.0.1 on a random port is used with a
-// path of "/".
-//
// ```
// cups_json_t *metadata = cupsOAuthGetMetadata(auth_uri);
// cups_json_t *scopes_supported = cupsJSONFind(metadata, "scopes_supported");
// ```
//
+// The "redirect_uri" parameter specifies a 'http:' URL with a listen address,
+// port, and path to use. If `NULL`, 127.0.0.1 on a random port is used with a
+// path of "/".
+//
// The returned authorization code must be freed using the `free` function.
//
help/man-cups.html \
help/man-cups-files.conf.html \
help/man-cups-lpd.html \
+ help/man-cups-oauth.html \
help/man-cups-snmp.html \
+ help/man-cups-x509.html \
help/man-cupsaccept.html \
help/man-cupsd.conf.html \
help/man-cupsd.html \
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta name="creator" content="mantohtml v2.0.2">
+ <title>cups-oauth(1)</title>
+ </head>
+ <body>
+ <h1 id="cups-oauth-1">cups-oauth(1)</h1>
+ <h2 id="cups-oauth-1.name">Name</h2>
+<p>cups-oauth - interact with an oauth/openid authorization server
+</p>
+ <h2 id="cups-oauth-1.synopsis">Synopsis</h2>
+<p><strong>cups-oauth</strong>
+<strong>--help</strong>
+<br>
+<strong>cups-oauth</strong>
+<strong>--version</strong>
+<br>
+<strong>cups-oauth</strong>
+[
+<strong>-a</strong>
+<em>OAUTH-URI</em>
+] [
+<strong>-s</strong>
+<em>SCOPE(S)</em>
+]
+<strong>authorize</strong>
+<em>[RESOURCE]</em>
+<br>
+<strong>cups-oauth</strong>
+[
+<strong>-a</strong>
+<em>OAUTH-URI</em>
+]
+<strong>clear</strong>
+<em>[RESOURCE]</em>
+<br>
+<strong>cups-oauth</strong>
+[
+<strong>-a</strong>
+<em>OAUTH-URI</em>
+]
+<strong>get-access-token</strong>
+<em>[RESOURCE]</em>
+<br>
+<strong>cups-oauth</strong>
+[
+<strong>-a</strong>
+<em>OAUTH-URI</em>
+]
+<strong>get-client-id</strong>
+<br>
+<strong>cups-oauth</strong>
+[
+<strong>-a</strong>
+<em>OAUTH-URI</em>
+]
+<strong>get-metadata</strong>
+<em>[NAME]</em>
+<br>
+<strong>cups-oauth</strong>
+[
+<strong>-a</strong>
+<em>OAUTH-URI</em>
+]
+<strong>get-user-id</strong>
+<em>[RESOURCE]</em>
+<em>[NAME]</em>
+<br>
+<strong>cups-oauth</strong>
+[
+<strong>-a</strong>
+<em>OAUTH-URI</em>
+]
+<strong>set-access-token</strong>
+<em>[RESOURCE]</em>
+<em>TOKEN</em>
+<br>
+<strong>cups-oauth</strong>
+[
+<strong>-a</strong>
+<em>OAUTH-URI</em>
+]
+<strong>set-client-data</strong>
+<em>CLIENT-ID</em>
+<em>CLIENT-SECRET</em>
+</p>
+ <h2 id="cups-oauth-1.description">Description</h2>
+<p>The
+<strong>cups-oauth</strong>
+utility interacts with an OAuth/OpenID authorization server.
+Authorizations are often linked to a resource (a printer URI, web page URL, etc.)
+</p>
+ <h2 id="cups-oauth-1.options">Options</h2>
+<p>The following options are recognized by
+<strong>cups-oauth:</strong>
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>--help</strong><br>
+Show program usage.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>--version</strong><br>
+Show the CUPS version.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-a </strong><em>OAUTH-URI</em><br>
+Specifies the OAuth/OpenID authorization server URL.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-s </strong><em>SCOPE(S)</em><br>
+Specifies a space-delimited list of scope names to use when authorizing access.
+The default is to request authorization for all supported scopes.
+</p>
+ <h2 id="cups-oauth-1.commands">Commands</h2>
+ <h3 id="cups-oauth-1.commands.authorize">Authorize</h3>
+<p>Starts an authorization workflow with the default web browser.
+If a resource URI is specified, the authorization is specific to that resource.
+The access token is send to the standard output on success.
+</p>
+ <h3 id="cups-oauth-1.commands.clear">Clear</h3>
+<p>Clears any authorization for the specified resource or for all resources if no resource URI is supplied.
+</p>
+ <h3 id="cups-oauth-1.commands.get-access-token">Get-Access-Token</h3>
+<p>Output the current, unexpired access token, if any, to the standard output.
+</p>
+ <h3 id="cups-oauth-1.commands.get-client-id">Get-Client-Id</h3>
+<p>Output the client ID string, if any, to the standard output.
+</p>
+ <h3 id="cups-oauth-1.commands.get-metadata">Get-Metadata</h3>
+<p>Get the OAuth/OpenID authorization server metadata and send it to the standard output.
+If a name is specified, the output is just the value for the specified metadata.
+</p>
+ <h3 id="cups-oauth-1.commands.get-user-id">Get-User-Id</h3>
+<p>Get the OpenID user ID information and send it to the standard output.
+If a name is specified, the output is just the named claim from the user ID.
+</p>
+ <h3 id="cups-oauth-1.commands.set-access-token">Set-Access-Token</h3>
+<p>Set the access token (which is sometimes also called an API key) for the specified resource or for all resources.
+</p>
+ <h3 id="cups-oauth-1.commands.set-client-data">Set-Client-Data</h3>
+<p>Set the client ID string and secret for an OAuth/OpenID authorization server.
+</p>
+ <h2 id="cups-oauth-1.environment-variables">Environment Variables</h2>
+<p>The
+<strong>CUPS_OAUTH_URI</strong>
+environment variable sets the default OAuth/OpenID authorization server URL.
+</p>
+ <p>The
+<strong>CUPS_OAUTH_SCOPES</strong>
+environment variable sets the default OAuth/OpenID scopes as a space-delimited list.
+</p>
+ <h2 id="cups-oauth-1.notes">Notes</h2>
+<p>CUPS uses a redirect URI of "<a href="http://127.0.0.1/"">http://127.0.0.1/"</a> for all authorization on the local system.
+</p>
+ <h2 id="cups-oauth-1.examples">Examples</h2>
+<p>TBD
+</p>
+ <h2 id="cups-oauth-1.see-also">See Also</h2>
+<a href="cups.html"><p><strong>cups</strong>(1)</a>
+
+</p>
+ <h2 id="cups-oauth-1.copyright">Copyright</h2>
+<p>Copyright © 2025 by OpenPrinting.
+ </body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta name="creator" content="mantohtml v2.0.2">
+ <title>cups-x509(1)</title>
+ </head>
+ <body>
+ <h1 id="cups-x509-1">cups-x509(1)</h1>
+ <h2 id="cups-x509-1.name">Name</h2>
+<p>cups-x509 - description
+</p>
+ <h2 id="cups-x509-1.synopsis">Synopsis</h2>
+<p><strong>cups-x509</strong>
+<strong>--help</strong>
+<br>
+<strong>cups-x509</strong>
+<strong>--version</strong>
+<br>
+<strong>cups-x509</strong>
+[
+<strong>--pin</strong>
+] [
+<strong>--require-ca</strong>
+] [
+<strong>-C</strong>
+<em>COUNTRY</em>
+] [
+<strong>-L</strong>
+<em>LOCALITY</em>
+] [
+<strong>-O</strong>
+<em>ORGANIZATION</em>
+] [
+<strong>-R</strong>
+<em>CSR-FILENAME</em>
+] [
+<strong>-S</strong>
+<em>STATE-PROVINCE</em>
+] [
+<strong>-U</strong>
+<em>ORGANIZATIONAL-UNIT</em>
+] [
+<strong>-a</strong>
+<em>SUBJECT-ALT-NAME</em>
+] [
+<strong>-d</strong>
+<em>DAYS</em>
+] [
+<strong>-p</strong>
+<em>PURPOSE</em>
+] [
+<strong>-r</strong>
+<em>ROOT-NAME</em>
+] [
+<strong>-t</strong>
+<em>TYPE</em>
+] [
+<strong>-u</strong>
+<em>USAGE</em>
+]
+<em>COMMAND</em>
+<em>[ARGUMENT(S)]</em>
+</p>
+ <h2 id="cups-x509-1.description">Description</h2>
+<p>The
+<strong>cups-x509</strong>
+utility manages X.509 certificates and certificate requests, and supports client and server tests.
+</p>
+ <h2 id="cups-x509-1.options">Options</h2>
+<p>The following options are recognized by
+<strong>cups-x509:</strong>
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>--help</strong><br>
+Show program usage.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>--pin</strong><br>
+Pin the server's X.509 certificate found by the client command.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>--require-ca</strong><br>
+Require the server's X.509 certificate found by the client command to be signed by a known CA.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>--version</strong><br>
+Show the CUPS version.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-C </strong><em>COUNTRY</em><br>
+Specify the country for new X.509 certificates and certificate requests.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-L </strong><em>LOCALITY</em><br>
+Specify the locality (city, town, etc.) for new X.509 certificates and certificate requests.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-O </strong><em>ORGANIZATION</em><br>
+Specify the organization name for new X.509 certificates and certificate requests.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-R </strong><em>CSR-FILENAME</em><br>
+Specify an X.509 certificate signing request in PEM format to be used when signing a certificate with the
+<strong>ca</strong>
+command.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-S </strong><em>STATE-PROVINCE</em><br>
+Specify the state/province name for new X.509 certificates and certificate requests.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-U </strong><em>ORGANIZATIONAL-UNIT</em><br>
+Specify the organizational unit name for new X.509 certificates and certificate requests.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-a </strong><em>SUBJECT-ALT-NAME</em><br>
+Specify an alternate name for new X.509 certificates and certificate requests.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-d </strong><em>DAYS</em><br>
+Specify the number of days before a new X.509 certificate will expire.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-p </strong><em>PURPOSE</em><br>
+Specify the purpose of the X.509 certificate or certificate request as a comma-delimited list of purposes.
+The supported purposes are "serverAuth" for TLS server authentication, "clientAuth" for TLS client authentication, "codeSigning" for executable code signing, "emailProtection" for S/MIME encryption and signing, "timeStamping" for secure timestamps, and "OCSPSigning" for Online Certificate Status Protocol services.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-r </strong><em>ROOT-NAME</em><br>
+Specify the common name of the X.509 root certificate to use.
+The default root certificate is named "_site_".
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-t </strong><em>TYPE</em><br>
+Specify the certificate type - "rsa-2048" for 2048-bit RSA, "rsa-3072" for 3072-bit RSA, "rsa-4096" for 4096-bit RSA, "ecdsa-p256" for 256-bit ECDSA, "ecdsa-p384" for 384-bit ECDSA, or "ecdsa-p521" for 521-bit ECDSA.
+</p>
+ <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>-u </strong><em>USAGE</em><br>
+Specify the usage for the certificate as a comma-delimited list of uses.
+The supported uses are "digitalSignature", "nonRepudiation", "keyEncipherment", "dataEncipherment", "keyAgreement", "keyCertSign", "cRLSign", "encipherOnly", and "decipherOnly".
+The preset "default-ca" specifies those uses required for a Certificate Authority, and the preset "default-tls" specifies those uses required for TLS.
+</p>
+ <h2 id="cups-x509-1.commands">Commands</h2>
+ <h3 id="cups-x509-1.commands.ca-common-name">Ca Common-Name</h3>
+<p>Sign a certificate request for the specified common name.
+</p>
+ <h3 id="cups-x509-1.commands.cacert-common-name">Cacert Common-Name</h3>
+<p>Create a CA certificate for the specified common name.
+</p>
+ <h3 id="cups-x509-1.commands.cert-common-name">Cert Common-Name</h3>
+<p>Create a certificate for the specified common name.
+</p>
+ <h3 id="cups-x509-1.commands.client-uri">Client Uri</h3>
+<p>Connect to the specified URI and validate the server's certificate.
+</p>
+ <h3 id="cups-x509-1.commands.csr-common-name">Csr Common-Name</h3>
+<p>Create a certificate signing request for the specified common name.
+</p>
+ <h3 id="cups-x509-1.commands.server-common-nameport">Server Common-Name[:Port]</h3>
+<p>Run a HTTPS test server that echos back the resource path for every GET request.
+If PORT is not specified, uses a port number from 8000 to 8999.
+</p>
+ <h3 id="cups-x509-1.commands.show-common-name">Show Common-Name</h3>
+<p>Shows any stored credentials for the specified common name.
+</p>
+ <h2 id="cups-x509-1.examples">Examples</h2>
+<p>TBD
+</p>
+ <h2 id="cups-x509-1.see-also">See Also</h2>
+<a href="cups.html"><p><strong>cups</strong>(1)</a>
+
+</p>
+ <h2 id="cups-x509-1.copyright">Copyright</h2>
+<p>Copyright © 2025 by OpenPrinting.
+ </body>
+</html>
# The "html" target depends on "mantohtml" from https://www.msweet.org/mantohtml
#
# Copyright © 2020-2025 by OpenPrinting.
-# Copyright © 2007-2019 by Apple Inc.
-# Copyright © 1993-2006 by Easy Software Products.
#
# Licensed under Apache License v2.0. See the file "LICENSE" for more
# information.
MAN1 = cancel.1 \
cups.1 \
+ cups-oauth.1 \
+ cups-x509.1 \
cupstestppd.1 \
ippeveprinter.1 \
$(IPPFIND_MAN) \
--- /dev/null
+.\"
+.\" cups-oauth man page for CUPS.
+.\"
+.\" Copyright © 2025 by OpenPrinting.
+.\"
+.\" Licensed under Apache License v2.0. See the file "LICENSE" for more
+.\" information.
+.\"
+.TH cups-oauth 1 "CUPS" "2025-03-04" "OpenPrinting"
+.SH NAME
+cups-oauth \- interact with an oauth/openid authorization server
+.SH SYNOPSIS
+.B cups-oauth
+.B \-\-help
+.br
+.B cups-oauth
+.B \-\-version
+.br
+.B cups-oauth
+[
+.B \-a
+.I OAUTH-URI
+] [
+.B \-s
+.I SCOPE(S)
+]
+.B authorize
+.I [RESOURCE]
+.br
+.B cups-oauth
+[
+.B \-a
+.I OAUTH-URI
+]
+.B clear
+.I [RESOURCE]
+.br
+.B cups-oauth
+[
+.B \-a
+.I OAUTH-URI
+]
+.B get-access-token
+.I [RESOURCE]
+.br
+.B cups-oauth
+[
+.B \-a
+.I OAUTH-URI
+]
+.B get-client-id
+.br
+.B cups-oauth
+[
+.B \-a
+.I OAUTH-URI
+]
+.B get-metadata
+.I [NAME]
+.br
+.B cups-oauth
+[
+.B \-a
+.I OAUTH-URI
+]
+.B get-user-id
+.I [RESOURCE]
+.I [NAME]
+.br
+.B cups-oauth
+[
+.B \-a
+.I OAUTH-URI
+]
+.B set-access-token
+.I [RESOURCE]
+.I TOKEN
+.br
+.B cups-oauth
+[
+.B \-a
+.I OAUTH-URI
+]
+.B set-client-data
+.I CLIENT-ID
+.I CLIENT-SECRET
+.SH DESCRIPTION
+The
+.B cups-oauth
+utility interacts with an OAuth/OpenID authorization server.
+Authorizations are often linked to a resource (a printer URI, web page URL, etc.)
+.SH OPTIONS
+The following options are recognized by
+.B cups-oauth:
+.TP 5
+.B \-\-help
+Show program usage.
+.TP 5
+.B \-\-version
+Show the CUPS version.
+.TP 5
+\fB\-a \fIOAUTH-URI\fR
+Specifies the OAuth/OpenID authorization server URL.
+.TP 5
+\fB\-s \fISCOPE(S)\fR
+Specifies a space-delimited list of scope names to use when authorizing access.
+The default is to request authorization for all supported scopes.
+.SH COMMANDS
+.SS authorize
+Starts an authorization workflow with the default web browser.
+If a resource URI is specified, the authorization is specific to that resource.
+The access token is send to the standard output on success.
+.SS clear
+Clears any authorization for the specified resource or for all resources if no resource URI is supplied.
+.SS get-access-token
+Output the current, unexpired access token, if any, to the standard output.
+.SS get-client-id
+Output the client ID string, if any, to the standard output.
+.SS get-metadata
+Get the OAuth/OpenID authorization server metadata and send it to the standard output.
+If a name is specified, the output is just the value for the specified metadata.
+.SS get-user-id
+Get the OpenID user ID information and send it to the standard output.
+If a name is specified, the output is just the named claim from the user ID.
+.SS set-access-token
+Set the access token (which is sometimes also called an API key) for the specified resource or for all resources.
+.SS set-client-data
+Set the client ID string and secret for an OAuth/OpenID authorization server.
+.SH ENVIRONMENT VARIABLES
+The
+.B CUPS_OAUTH_URI
+environment variable sets the default OAuth/OpenID authorization server URL.
+.PP
+The
+.B CUPS_OAUTH_SCOPES
+environment variable sets the default OAuth/OpenID scopes as a space-delimited list.
+.SH NOTES
+CUPS uses a redirect URI of "http://127.0.0.1/" for all authorization on the local system.
+.SH EXAMPLES
+TBD
+.SH SEE ALSO
+.BR cups (1)
+.SH COPYRIGHT
+Copyright \[co] 2025 by OpenPrinting.
--- /dev/null
+.\"
+.\" cups-x509 man page for CUPS.
+.\"
+.\" Copyright © 2025 by OpenPrinting.
+.\"
+.\" Licensed under Apache License v2.0. See the file "LICENSE" for more
+.\" information.
+.\"
+.TH cups-x509 1 "CUPS" "2025-03-04" "OpenPrinting"
+.SH NAME
+cups-x509 \- description
+.SH SYNOPSIS
+.B cups-x509
+.B \-\-help
+.br
+.B cups-x509
+.B \-\-version
+.br
+.B cups-x509
+[
+.B \-\-pin
+] [
+.B \-\-require\-ca
+] [
+.B \-C
+.I COUNTRY
+] [
+.B \-L
+.I LOCALITY
+] [
+.B \-O
+.I ORGANIZATION
+] [
+.B \-R
+.I CSR-FILENAME
+] [
+.B \-S
+.I STATE-PROVINCE
+] [
+.B \-U
+.I ORGANIZATIONAL-UNIT
+] [
+.B \-a
+.I SUBJECT-ALT-NAME
+] [
+.B \-d
+.I DAYS
+] [
+.B \-p
+.I PURPOSE
+] [
+.B \-r
+.I ROOT-NAME
+] [
+.B \-t
+.I TYPE
+] [
+.B \-u
+.I USAGE
+]
+.I COMMAND
+.I [ARGUMENT(S)]
+.SH DESCRIPTION
+The
+.B cups-x509
+utility manages X.509 certificates and certificate requests, and supports client and server tests.
+.SH OPTIONS
+The following options are recognized by
+.B cups-x509:
+.TP 5
+.B \-\-help
+Show program usage.
+.TP 5
+.B \-\-pin
+Pin the server's X.509 certificate found by the client command.
+.TP 5
+.B \-\-require\-ca
+Require the server's X.509 certificate found by the client command to be signed by a known CA.
+.TP 5
+.B \-\-version
+Show the CUPS version.
+.TP 5
+\fB-C \fICOUNTRY\fR
+Specify the country for new X.509 certificates and certificate requests.
+.TP 5
+\fB-L \fILOCALITY\fR
+Specify the locality (city, town, etc.) for new X.509 certificates and certificate requests.
+.TP 5
+\fB-O \fIORGANIZATION\fR
+Specify the organization name for new X.509 certificates and certificate requests.
+.TP 5
+\fB-R \fICSR-FILENAME\fR
+Specify an X.509 certificate signing request in PEM format to be used when signing a certificate with the
+.B ca
+command.
+.TP 5
+\fB-S \fISTATE-PROVINCE\fR
+Specify the state/province name for new X.509 certificates and certificate requests.
+.TP 5
+\fB-U \fIORGANIZATIONAL-UNIT\fR
+Specify the organizational unit name for new X.509 certificates and certificate requests.
+.TP 5
+\fB-a \fISUBJECT-ALT-NAME\fR
+Specify an alternate name for new X.509 certificates and certificate requests.
+.TP 5
+\fB-d \fIDAYS\fR
+Specify the number of days before a new X.509 certificate will expire.
+.TP 5
+\fB-p \fIPURPOSE\fR
+Specify the purpose of the X.509 certificate or certificate request as a comma-delimited list of purposes.
+The supported purposes are "serverAuth" for TLS server authentication, "clientAuth" for TLS client authentication, "codeSigning" for executable code signing, "emailProtection" for S/MIME encryption and signing, "timeStamping" for secure timestamps, and "OCSPSigning" for Online Certificate Status Protocol services.
+.TP 5
+\fB-r \fIROOT-NAME\fR
+Specify the common name of the X.509 root certificate to use.
+The default root certificate is named "_site_".
+.TP 5
+\fB-t \fITYPE\fR
+Specify the certificate type - "rsa-2048" for 2048-bit RSA, "rsa-3072" for 3072-bit RSA, "rsa-4096" for 4096-bit RSA, "ecdsa-p256" for 256-bit ECDSA, "ecdsa-p384" for 384-bit ECDSA, or "ecdsa-p521" for 521-bit ECDSA.
+.TP 5
+\fB-u \fIUSAGE\fR
+Specify the usage for the certificate as a comma-delimited list of uses.
+The supported uses are "digitalSignature", "nonRepudiation", "keyEncipherment", "dataEncipherment", "keyAgreement", "keyCertSign", "cRLSign", "encipherOnly", and "decipherOnly".
+The preset "default-ca" specifies those uses required for a Certificate Authority, and the preset "default-tls" specifies those uses required for TLS.
+.SH COMMANDS
+.SS ca COMMON-NAME
+Sign a certificate request for the specified common name.
+.SS cacert COMMON-NAME
+Create a CA certificate for the specified common name.
+.SS cert COMMON-NAME
+Create a certificate for the specified common name.
+.SS client URI
+Connect to the specified URI and validate the server's certificate.
+.SS csr COMMON-NAME
+Create a certificate signing request for the specified common name.
+.SS server COMMON-NAME[:PORT]
+Run a HTTPS test server that echos back the resource path for every GET request.
+If PORT is not specified, uses a port number from 8000 to 8999.
+.SS show COMMON-NAME
+Shows any stored credentials for the specified common name.
+.SH EXAMPLES
+TBD
+.SH SEE ALSO
+.BR cups (1)
+.SH COPYRIGHT
+Copyright \[co] 2025 by OpenPrinting.
+cups-oauth.o: cups-oauth.c ../cups/cups-private.h \
+ ../cups/string-private.h ../config.h ../cups/base.h \
+ ../cups/debug-internal.h ../cups/debug-private.h ../cups/ipp-private.h \
+ ../cups/cups.h ../cups/file.h ../cups/ipp.h ../cups/http.h \
+ ../cups/array.h ../cups/language.h ../cups/pwg.h \
+ ../cups/http-private.h \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ ../cups/language-private.h \
+ ../cups/transcode.h ../cups/pwg-private.h ../cups/thread.h \
+ ../cups/oauth.h ../cups/jwt.h ../cups/json.h
+cups-x509.o: cups-x509.c ../cups/cups-private.h ../cups/string-private.h \
+ ../config.h ../cups/base.h ../cups/debug-internal.h \
+ ../cups/debug-private.h ../cups/ipp-private.h ../cups/cups.h \
+ ../cups/file.h ../cups/ipp.h ../cups/http.h ../cups/array.h \
+ ../cups/language.h ../cups/pwg.h ../cups/http-private.h \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ ../cups/language-private.h \
+ ../cups/transcode.h ../cups/pwg-private.h ../cups/thread.h
ippevepcl.o: ippevepcl.c ippevecommon.h ../cups/cups.h ../cups/file.h \
../cups/base.h ../cups/ipp.h ../cups/http.h ../cups/array.h \
../cups/language.h ../cups/pwg.h ../cups/raster.h \
\
\
\
- \
- \
- \
- ../cups/language-private.h ../cups/transcode.h ../cups/pwg-private.h \
- ../cups/thread.h ../cups/dnssd.h printer-png.h printer-lg-png.h \
- printer-sm-png.h
+ ../cups/language-private.h \
+ ../cups/transcode.h ../cups/pwg-private.h ../cups/thread.h \
+ ../cups/dnssd.h printer-png.h printer-lg-png.h printer-sm-png.h
ippeveps.o: ippeveps.c ippevecommon.h ../cups/cups.h ../cups/file.h \
../cups/base.h ../cups/ipp.h ../cups/http.h ../cups/array.h \
../cups/language.h ../cups/pwg.h ../cups/raster.h \
\
\
\
- \
- \
- \
- ../cups/language-private.h ../cups/transcode.h ../cups/pwg-private.h \
- ../cups/thread.h ../cups/dnssd.h
+ ../cups/language-private.h \
+ ../cups/transcode.h ../cups/pwg-private.h ../cups/thread.h \
+ ../cups/dnssd.h
ipptool.o: ipptool.c ../cups/cups-private.h ../cups/string-private.h \
../config.h ../cups/base.h ../cups/debug-internal.h \
../cups/debug-private.h ../cups/ipp-private.h ../cups/cups.h \
\
\
\
- \
- \
- \
- ../cups/language-private.h ../cups/transcode.h ../cups/pwg-private.h \
- ../cups/thread.h ../cups/raster-testpage.h ../cups/raster-private.h \
- ../cups/raster.h
+ ../cups/language-private.h \
+ ../cups/transcode.h ../cups/pwg-private.h ../cups/thread.h \
+ ../cups/raster-testpage.h ../cups/raster-private.h ../cups/raster.h
#
# IPP tools makefile for CUPS.
#
-# Copyright © 2020-2024 by OpenPrinting.
-# Copyright © 2007-2019 by Apple Inc.
-# Copyright © 1997-2006 by Easy Software Products, all rights reserved.
+# Copyright © 2020-2025 by OpenPrinting.
#
# Licensed under Apache License v2.0. See the file "LICENSE" for more
# information.
OBJS = \
+ cups-oauth.o \
+ cups-x509.o \
ippevepcl.o \
ippeveprinter.o \
ippeveps.o \
ippfind.o \
ipptool.o
IPPTOOLS = \
+ cups-oauth \
+ cups-x509 \
ippeveprinter \
$(IPPFIND_BIN) \
ipptool
local: ippeveprinter-static ipptool-static
+#
+# cups-oauth
+#
+
+cups-oauth: cups-oauth.o ../cups/$(LIBCUPS)
+ echo Linking $@...
+ $(LD_CC) $(ALL_LDFLAGS) -o $@ cups-oauth.o $(DNSSDLIBS) $(LINKCUPS)
+ $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@
+
+
+#
+# cups-x509
+#
+
+cups-x509: cups-x509.o ../cups/$(LIBCUPS)
+ echo Linking $@...
+ $(LD_CC) $(ALL_LDFLAGS) -o $@ cups-x509.o $(DNSSDLIBS) $(LINKCUPS)
+ $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@
+
+
#
# ippeveprinter
#
--- /dev/null
+//
+// 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
+
+
+ // 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;
+ }
+
+ return (0);
+ }
+ else
+ {
+ return (1);
+ }
+ }
+ else if ((json = cupsJSONExportString(claims)) != NULL)
+ {
+ puts(json);
+ free(json);
+ }
+
+ return (0);
+}
+
+
+//
+// '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);
+}
--- /dev/null
+//
+// X.509 credentials utiltiy for CUPS.
+//
+// Copyright © 2022-2025 by OpenPrinting.
+//
+// Licensed under Apache License v2.0. See the file "LICENSE" for more
+// information.
+//
+// Usage: cups-x509 [OPTIONS] [COMMAND] [ARGUMENT(S)]
+//
+// Commands:
+//
+// ca COMMON-NAME Sign a CSR to produce a certificate.
+// cacert COMMON-NAME Create a CA certificate.
+// cert COMMON-NAME Create a certificate.
+// client URI Connect to URI.
+// csr COMMON-NAME Create a certificate signing request.
+// server COMMON-NAME[:PORT] Run a HTTPS server (default port 8NNN.)
+// show COMMON-NAME Show stored credentials for COMMON-NAME.
+//
+// Options:
+//
+// --help Show program help
+// --pin Pin the certificate found by the client command
+// --require-ca Require a CA-signed certificate for the client command
+// --version Show program version
+// -C COUNTRY Set country
+// -L LOCALITY Set locality name
+// -O ORGANIZATION Set organization name
+// -R CSR-FILENAME Specify certificate signing request filename
+// -S STATE Set state
+// -U ORGANIZATIONAL-UNIT Set organizational unit name
+// -a SUBJECT-ALT-NAME Add a subjectAltName
+// -d DAYS Set expiration date in days
+// -p PURPOSE Comma-delimited certificate purpose (serverAuth,
+// clientAuth, codeSigning, emailProtection,
+// timeStamping, OCSPSigning)
+// -r ROOT-NAME Name of root certificate
+// -t TYPE Certificate type (rsa-2048, rsa-3072, rsa-4096,
+// ecdsa-p256, ecdsa-p384, ecdsa-p521)
+// -u USAGE Comma-delimited key usage (digitalSignature,
+// nonRepudiation, keyEncipherment,
+// dataEncipherment, keyAgreement, keyCertSign,
+// cRLSign, encipherOnly, decipherOnly, default-ca,
+// default-tls)
+//
+
+#include <cups/cups-private.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <poll.h>
+
+
+//
+// Macro for localized text...
+//
+
+# define _(x) x
+
+
+//
+// Local functions...
+//
+
+static int do_ca(const char *common_name, const char *csrfile, const char *root_name, int days);
+static int do_cert(bool ca_cert, cups_credpurpose_t purpose, cups_credtype_t type, cups_credusage_t keyusage, const char *organization, const char *org_unit, const char *locality, const char *state, const char *country, const char *root_name, const char *common_name, size_t num_alt_names, const char **alt_names, int days);
+static int do_client(const char *uri);
+static int do_csr(cups_credpurpose_t purpose, cups_credtype_t type, cups_credusage_t keyusage, const char *organization, const char *org_unit, const char *locality, const char *state, const char *country, const char *common_name, size_t num_alt_names, const char **alt_names);
+static int do_server(const char *host_port);
+static int do_show(const char *common_name);
+static int usage(FILE *fp);
+
+
+//
+// '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 *command = NULL, // Command
+ *arg = NULL, // Argument for command
+ *opt, // Current option character
+ *csrfile = NULL, // Certificste signing request filename
+ *root_name = NULL, // Name of root certificate
+ *organization = NULL, // Organization
+ *org_unit = NULL, // Organizational unit
+ *locality = NULL, // Locality
+ *state = NULL, // State/province
+ *country = NULL, // Country
+ *alt_names[100]; // Subject alternate names
+ size_t num_alt_names = 0;
+ int days = 365; // Days until expiration
+ cups_credpurpose_t purpose = CUPS_CREDPURPOSE_SERVER_AUTH;
+ // Certificate purpose
+ cups_credtype_t type = CUPS_CREDTYPE_DEFAULT;
+ // Certificate type
+ cups_credusage_t keyusage = CUPS_CREDUSAGE_DEFAULT_TLS;
+ // Key usage
+
+
+ // Check 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-x509", argv[i]);
+ return (usage(stderr));
+ }
+ else if (argv[i][0] == '-')
+ {
+ for (opt = argv[i] + 1; *opt; opt ++)
+ {
+ switch (*opt)
+ {
+ case 'C' : // -C COUNTRY
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing country after '-C'."));
+ return (usage(stderr));
+ }
+ country = argv[i];
+ break;
+
+ case 'L' : // -L LOCALITY
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing locality/city/town after '-L'."));
+ return (usage(stderr));
+ }
+ locality = argv[i];
+ break;
+
+ case 'O' : // -O ORGANIZATION
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing organization after '-O'."));
+ return (usage(stderr));
+ }
+ organization = argv[i];
+ break;
+
+ case 'R' : // -R CSR-FILENAME
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing CSR filename after '-R'."));
+ return (usage(stderr));
+ }
+ csrfile = argv[i];
+ break;
+
+ case 'S' : // -S STATE
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing state/province after '-S'."));
+ return (usage(stderr));
+ }
+ state = argv[i];
+ break;
+
+ case 'U' : // -U ORGANIZATIONAL-UNIT
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing organizational unit after '-U'."));
+ return (usage(stderr));
+ }
+ org_unit = argv[i];
+ break;
+
+ case 'a' : // -a SUBJECT-ALT-NAME
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing subjectAltName after '-a'."));
+ return (usage(stderr));
+ }
+ if (num_alt_names >= (sizeof(alt_names) / sizeof(alt_names[0])))
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Too many subjectAltName values."));
+ return (1);
+ }
+ alt_names[num_alt_names ++] = argv[i];
+ break;
+
+ case 'd' : // -d DAYS
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing expiration days after '-d'."));
+ return (usage(stderr));
+ }
+ if ((days = atoi(argv[i])) <= 0)
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Bad DAYS value '%s' after '-d'."), argv[i]);
+ return (1);
+ }
+ break;
+
+ case 'p' : // -p PURPOSE
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing purpose after '-p'."));
+ return (usage(stderr));
+ }
+ purpose = 0;
+ if (strstr(argv[i], "serverAuth"))
+ purpose |= CUPS_CREDPURPOSE_SERVER_AUTH;
+ if (strstr(argv[i], "clientAuth"))
+ purpose |= CUPS_CREDPURPOSE_CLIENT_AUTH;
+ if (strstr(argv[i], "codeSigning"))
+ purpose |= CUPS_CREDPURPOSE_CODE_SIGNING;
+ if (strstr(argv[i], "emailProtection"))
+ purpose |= CUPS_CREDPURPOSE_EMAIL_PROTECTION;
+ if (strstr(argv[i], "timeStamping"))
+ purpose |= CUPS_CREDPURPOSE_TIME_STAMPING;
+ if (strstr(argv[i], "OCSPSigning"))
+ purpose |= CUPS_CREDPURPOSE_OCSP_SIGNING;
+ if (purpose == 0)
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Bad purpose '%s'."), argv[i]);
+ return (usage(stderr));
+ }
+ break;
+
+ case 'r' : // -r ROOT-NAME
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing root name after '-r'."));
+ return (usage(stderr));
+ }
+ root_name = argv[i];
+ break;
+
+ case 't' : // -t TYPE
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing certificate type after '-t'."));
+ return (usage(stderr));
+ }
+ if (!strcmp(argv[i], "default"))
+ {
+ type = CUPS_CREDTYPE_DEFAULT;
+ }
+ else if (!strcmp(argv[i], "rsa-2048"))
+ {
+ type = CUPS_CREDTYPE_RSA_2048_SHA256;
+ }
+ else if (!strcmp(argv[i], "rsa-3072"))
+ {
+ type = CUPS_CREDTYPE_RSA_3072_SHA256;
+ }
+ else if (!strcmp(argv[i], "rsa-4096"))
+ {
+ type = CUPS_CREDTYPE_RSA_4096_SHA256;
+ }
+ else if (!strcmp(argv[i], "ecdsa-p256"))
+ {
+ type = CUPS_CREDTYPE_ECDSA_P256_SHA256;
+ }
+ else if (!strcmp(argv[i], "ecdsa-p384"))
+ {
+ type = CUPS_CREDTYPE_ECDSA_P384_SHA256;
+ }
+ else if (!strcmp(argv[i], "ecdsa-p521"))
+ {
+ type = CUPS_CREDTYPE_ECDSA_P521_SHA256;
+ }
+ else
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Bad certificate type '%s'."), argv[i]);
+ return (usage(stderr));
+ }
+ break;
+
+ case 'u' : // -u USAGE
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing key usage after '-u'."));
+ return (usage(stderr));
+ }
+ keyusage = 0;
+ if (strstr(argv[i], "default-ca"))
+ keyusage = CUPS_CREDUSAGE_DEFAULT_CA;
+ if (strstr(argv[i], "default-tls"))
+ keyusage = CUPS_CREDUSAGE_DEFAULT_TLS;
+ if (strstr(argv[i], "digitalSignature"))
+ keyusage |= CUPS_CREDUSAGE_DIGITAL_SIGNATURE;
+ if (strstr(argv[i], "nonRepudiation"))
+ keyusage |= CUPS_CREDUSAGE_NON_REPUDIATION;
+ if (strstr(argv[i], "keyEncipherment"))
+ keyusage |= CUPS_CREDUSAGE_KEY_ENCIPHERMENT;
+ if (strstr(argv[i], "dataEncipherment"))
+ keyusage |= CUPS_CREDUSAGE_DATA_ENCIPHERMENT;
+ if (strstr(argv[i], "keyAgreement"))
+ keyusage |= CUPS_CREDUSAGE_KEY_AGREEMENT;
+ if (strstr(argv[i], "keyCertSign"))
+ keyusage |= CUPS_CREDUSAGE_KEY_CERT_SIGN;
+ if (strstr(argv[i], "cRLSign"))
+ keyusage |= CUPS_CREDUSAGE_CRL_SIGN;
+ if (strstr(argv[i], "encipherOnly"))
+ keyusage |= CUPS_CREDUSAGE_ENCIPHER_ONLY;
+ if (strstr(argv[i], "decipherOnly"))
+ keyusage |= CUPS_CREDUSAGE_DECIPHER_ONLY;
+ if (keyusage == 0)
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Bad key usage '%s'."), argv[i]);
+ return (usage(stderr));
+ }
+ break;
+
+ default :
+ _cupsLangPrintf(stderr, _("%s: Unknown option '-%c'."), "cups-x509", *opt);
+ return (usage(stderr));
+ }
+ }
+ }
+ else if (!command)
+ {
+ command = argv[i];
+ }
+ else if (!arg)
+ {
+ arg = argv[i];
+ }
+ else
+ {
+ _cupsLangPrintf(stderr, _("%s: Unknown option '%s'."), "cups-x509", argv[i]);
+ return (usage(stderr));
+ }
+ }
+
+ if (!command || !arg)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Missing command argument."));
+ return (usage(stderr));
+ }
+
+ // Run the corresponding command...
+ if (!strcmp(command, "ca"))
+ {
+ return (do_ca(arg, csrfile, root_name, days));
+ }
+ else if (!strcmp(command, "cacert"))
+ {
+ return (do_cert(true, purpose, type, keyusage, organization, org_unit, locality, state, country, root_name, arg, num_alt_names, alt_names, days));
+ }
+ else if (!strcmp(command, "cert"))
+ {
+ return (do_cert(false, purpose, type, keyusage, organization, org_unit, locality, state, country, root_name, arg, num_alt_names, alt_names, days));
+ }
+ else if (!strcmp(command, "client"))
+ {
+ return (do_client(arg));
+ }
+ else if (!strcmp(command, "csr"))
+ {
+ return (do_csr(purpose, type, keyusage, organization, org_unit, locality, state, country, arg, num_alt_names, alt_names));
+ }
+ else if (!strcmp(command, "server"))
+ {
+ return (do_server(arg));
+ }
+ else if (!strcmp(command, "show"))
+ {
+ return (do_show(arg));
+ }
+ else
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unknown command '%s'."), command);
+ return (usage(stderr));
+ }
+}
+
+
+//
+// 'do_ca()' - Test generating a certificate from a CSR.
+//
+
+static int // O - Exit status
+do_ca(const char *common_name, // I - Common name
+ const char *csrfile, // I - CSR filename, if any
+ const char *root_name, // I - Root certificate name
+ int days) // I - Number of days
+{
+ char *request, // Certificate request
+ *cert; // Certificate
+
+
+ if (csrfile)
+ {
+ int csrfd = open(csrfile, O_RDONLY);
+ // File descriptor
+ struct stat csrinfo; // File information
+
+ if (csrfd < 0)
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to access '%s': %s"), csrfile, strerror(errno));
+ return (1);
+ }
+
+ if (fstat(csrfd, &csrinfo))
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to stat '%s': %s"), csrfile, strerror(errno));
+ close(csrfd);
+ return (1);
+ }
+
+ if ((request = malloc((size_t)csrinfo.st_size + 1)) == NULL)
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to allocate memory for '%s': %s"), csrfile, strerror(errno));
+ close(csrfd);
+ return (1);
+ }
+
+ if (read(csrfd, request, (size_t)csrinfo.st_size) < (ssize_t)csrinfo.st_size)
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to read '%s'."), csrfile);
+ close(csrfd);
+ return (1);
+ }
+
+ close(csrfd);
+ request[csrinfo.st_size] = '\0';
+ }
+ else if ((request = cupsCopyCredentialsRequest(/*path*/NULL, common_name)) == NULL)
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: No request for '%s'."), common_name);
+ return (1);
+ }
+
+ if (!cupsSignCredentialsRequest(/*path*/NULL, common_name, request, root_name, CUPS_CREDPURPOSE_ALL, CUPS_CREDUSAGE_ALL, /*cb*/NULL, /*cb_data*/NULL, time(NULL) + days * 86400))
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to create certificate (%s)"), cupsGetErrorString());
+ free(request);
+ return (1);
+ }
+
+ free(request);
+
+ if ((cert = cupsCopyCredentials(/*path*/NULL, common_name)) != NULL)
+ {
+ puts(cert);
+ free(cert);
+ }
+ else
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to get generated certificate for '%s'."), common_name);
+ return (1);
+ }
+
+ return (0);
+}
+
+
+//
+// 'do_cert()' - Test creating a self-signed certificate.
+//
+
+static int // O - Exit status
+do_cert(
+ bool ca_cert, // I - `true` for a CA certificate, `false` for a regular one
+ cups_credpurpose_t purpose, // I - Certificate purpose
+ cups_credtype_t type, // I - Certificate type
+ cups_credusage_t keyusage, // I - Key usage
+ const char *organization, // I - Organization
+ const char *org_unit, // I - Organizational unit
+ const char *locality, // I - Locality (city/town/etc.)
+ const char *state, // I - State/province
+ const char *country, // I - Country
+ const char *root_name, // I - Root certificate name
+ const char *common_name, // I - Common name
+ size_t num_alt_names, // I - Number of subjectAltName's
+ const char **alt_names, // I - subjectAltName's
+ int days) // I - Number of days until expiration
+{
+ char *cert, // Certificate
+ *key; // Private key
+
+
+ if (!cupsCreateCredentials(/*path*/NULL, ca_cert, purpose, type, keyusage, organization, org_unit, locality, state, country, common_name, /*email*/NULL, num_alt_names, alt_names, root_name, time(NULL) + days * 86400))
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to create certificate (%s)"), cupsGetErrorString());
+ return (1);
+ }
+
+ if ((cert = cupsCopyCredentials(/*path*/NULL, common_name)) != NULL)
+ {
+ puts(cert);
+ free(cert);
+ }
+ else
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to get generated certificate for '%s'."), common_name);
+ return (1);
+ }
+
+ if ((key = cupsCopyCredentialsKey(/*path*/NULL, common_name)) != NULL)
+ {
+ puts(key);
+ free(key);
+ }
+ else
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to get generated private key for '%s'."), common_name);
+ return (1);
+ }
+
+ return (0);
+}
+
+
+//
+// 'do_client()' - Test connecting to a HTTPS server.
+//
+
+static int // O - Exit status
+do_client(const char *uri) // I - URI
+{
+ http_t *http; // HTTP connection
+ char scheme[HTTP_MAX_URI], // Scheme from URI
+ hostname[HTTP_MAX_URI], // Hostname from URI
+ username[HTTP_MAX_URI], // Username:password from URI
+ resource[HTTP_MAX_URI]; // Resource from URI
+ int port; // Port number from URI
+ http_trust_t trust; // Trust evaluation for connection
+ char *hcreds; // Credentials from connection
+ char hinfo[1024], // String for connection credentials
+ datestr[256]; // Date string
+ static const char *trusts[] = // Trust strings
+ { "OK", "Invalid", "Changed", "Expired", "Renewed", "Unknown" };
+
+
+ // Connect to the host and validate credentials...
+ if (httpSeparateURI(HTTP_URI_CODING_MOST, uri, scheme, sizeof(scheme), username, sizeof(username), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Bad URI '%s'."), uri);
+ return (1);
+ }
+
+ if ((http = httpConnect2(hostname, port, NULL, AF_UNSPEC, HTTP_ENCRYPTION_ALWAYS, 1, 30000, NULL)) == NULL)
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to connect to '%s' on port %d: %s"), hostname, port, cupsGetErrorString());
+ return (1);
+ }
+
+ puts("TLS Server Credentials:");
+ if ((hcreds = httpCopyPeerCredentials(http)) != NULL)
+ {
+ trust = cupsGetCredentialsTrust(/*path*/NULL, hostname, hcreds, /*require_ca*/false);
+
+ cupsGetCredentialsInfo(hcreds, hinfo, sizeof(hinfo));
+
+// printf(" Certificate Count: %u\n", (unsigned)cupsArrayGetCount(hcreds));
+ if (trust == HTTP_TRUST_OK)
+ puts(" Trust: OK");
+ else
+ printf(" Trust: %s (%s)\n", trusts[trust], cupsGetErrorString());
+ printf(" Expiration: %s\n", httpGetDateString2(cupsGetCredentialsExpiration(hcreds), datestr, sizeof(datestr)));
+ printf(" ValidName: %s\n", cupsAreCredentialsValidForName(hostname, hcreds) ? "true" : "false");
+ printf(" Info: \"%s\"\n", hinfo);
+
+ free(hcreds);
+ }
+ else
+ {
+ puts(" Not present (error).");
+ }
+
+ puts("");
+
+ return (do_show(hostname));
+}
+
+
+//
+// 'do_csr()' - Test creating a certificate signing request.
+//
+
+static int // O - Exit status
+do_csr(
+ cups_credpurpose_t purpose, // I - Certificate purpose
+ cups_credtype_t type, // I - Certificate type
+ cups_credusage_t keyusage, // I - Key usage
+ const char *organization, // I - Organization
+ const char *org_unit, // I - Organizational unit
+ const char *locality, // I - Locality (city/town/etc.)
+ const char *state, // I - State/province
+ const char *country, // I - Country
+ const char *common_name, // I - Common name
+ size_t num_alt_names, // I - Number of subjectAltName's
+ const char **alt_names) // I - subjectAltName's
+{
+ char *csr; // Certificate request
+
+
+ if (!cupsCreateCredentialsRequest(/*path*/NULL, purpose, type, keyusage, organization, org_unit, locality, state, country, common_name, /*email*/NULL, num_alt_names, alt_names))
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to create certificate request (%s)"), cupsGetErrorString());
+ return (1);
+ }
+
+ if ((csr = cupsCopyCredentialsRequest(/*path*/NULL, common_name)) != NULL)
+ {
+ puts(csr);
+ free(csr);
+ }
+ else
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to get generated certificate request for '%s'."), common_name);
+ return (1);
+ }
+
+ return (0);
+}
+
+
+//
+// 'do_server()' - Test running a server.
+//
+
+static int // O - Exit status
+do_server(const char *host_port) // I - Hostname/port
+{
+ char host[256], // Hostname
+ *hostptr; // Pointer into hostname
+ int port; // Port number
+ nfds_t i, // Looping var
+ num_listeners = 0; // Number of listeners
+ struct pollfd listeners[2]; // Listeners
+ http_addr_t addr; // Listen address
+ http_t *http; // Client
+
+
+ // Get the host and port...
+ cupsCopyString(host, host_port, sizeof(host));
+ if ((hostptr = strrchr(host, ':')) != NULL)
+ {
+ // Extract the port number from the argument...
+ *hostptr++ = '\0';
+ port = atoi(hostptr);
+ }
+ else
+ {
+ // Use the default port 8NNN where NNN is the bottom 3 digits of the UID...
+ port = 8000 + (int)getuid() % 1000;
+ }
+
+ // Setup listeners for IPv4 and IPv6...
+ memset(&addr, 0, sizeof(addr));
+ addr.ipv4.sin_family = AF_INET;
+
+ if ((listeners[num_listeners].fd = httpAddrListen(&addr, port)) > 0)
+ {
+ listeners[num_listeners].events = POLLIN | POLLERR;
+ num_listeners ++;
+ }
+
+ addr.ipv6.sin6_family = AF_INET6;
+
+ if ((listeners[num_listeners].fd = httpAddrListen(&addr, port)) > 0)
+ {
+ listeners[num_listeners].events = POLLIN | POLLERR;
+ num_listeners ++;
+ }
+
+ if (num_listeners == 0)
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to listen on port %d: %s"), port, cupsGetErrorString());
+ return (1);
+ }
+
+ printf("Listening for connections on port %d...\n", port);
+
+ // Set certificate info...
+ cupsSetServerCredentials(/*path*/NULL, host, true);
+
+ // Wait for connections...
+ for (;;)
+ {
+ http_state_t state; // HTTP request state
+ char resource[1024]; // Resource path
+
+ // Look for new connections...
+ if (poll(listeners, num_listeners, 1000) < 0)
+ {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+
+ perror("cups-x509: Unable to poll");
+ break;
+ }
+
+ // Try accepting a connection...
+ for (i = 0, http = NULL; i < num_listeners; i ++)
+ {
+ if (listeners[i].revents & POLLIN)
+ {
+ if ((http = httpAcceptConnection(listeners[i].fd, true)) != NULL)
+ break;
+
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to accept connection: %s"), cupsGetErrorString());
+ }
+ }
+
+ if (!http)
+ continue;
+
+ // Negotiate a secure connection...
+ if (!httpSetEncryption(http, HTTP_ENCRYPTION_ALWAYS))
+ {
+ _cupsLangPrintf(stderr, _("cups-x509: Unable to encrypt connection: %s"), cupsGetErrorString());
+ httpClose(http);
+ continue;
+ }
+
+ // Process a single request and then close it out...
+ while ((state = httpReadRequest(http, resource, sizeof(resource))) == HTTP_STATE_WAITING)
+ usleep(1000);
+
+ if (state == HTTP_STATE_ERROR)
+ {
+ if (httpGetError(http) == EPIPE)
+ _cupsLangPuts(stderr, _("cups-x509: Client closed connection."));
+ else
+ _cupsLangPrintf(stderr, _("cups-x509: Bad request line (%s)."), strerror(httpGetError(http)));
+ }
+ else if (state == HTTP_STATE_UNKNOWN_METHOD)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Bad/unknown operation."));
+ }
+ else if (state == HTTP_STATE_UNKNOWN_VERSION)
+ {
+ _cupsLangPuts(stderr, _("cups-x509: Bad HTTP version."));
+ }
+ else
+ {
+ printf("%s %s\n", httpStateString(state), resource);
+
+ if (state == HTTP_STATE_GET || state == HTTP_STATE_HEAD)
+ {
+ httpClearFields(http);
+ httpSetField(http, HTTP_FIELD_CONTENT_TYPE, "text/plain");
+ httpSetField(http, HTTP_FIELD_CONNECTION, "close");
+ httpSetLength(http, strlen(resource) + 1);
+ httpWriteResponse(http, HTTP_STATUS_OK);
+
+ if (state == HTTP_STATE_GET)
+ {
+ // Echo back the resource path...
+ httpWrite(http, resource, strlen(resource));
+ httpWrite(http, "\n", 1);
+ httpFlushWrite(http);
+ }
+ }
+ else
+ {
+ httpWriteResponse(http, HTTP_STATUS_BAD_REQUEST);
+ }
+ }
+
+ httpClose(http);
+ }
+
+ // Close listeners and return...
+ for (i = 0; i < num_listeners; i ++)
+ httpAddrClose(&addr, listeners[i].fd);
+
+ return (0);
+}
+
+
+//
+// 'do_show()' - Test showing stored certificates.
+//
+
+static int // O - Exit status
+do_show(const char *common_name) // I - Common name
+{
+ char *tcreds, // Credentials from trust store
+ tinfo[1024], // String for trust store credentials
+ datestr[256]; // Date string
+
+
+ printf("Trust Store for \"%s\":\n", common_name);
+
+ if ((tcreds = cupsCopyCredentials(/*path*/NULL, common_name)) != NULL)
+ {
+ cupsGetCredentialsInfo(tcreds, tinfo, sizeof(tinfo));
+
+// printf(" Certificate Count: %u\n", (unsigned)cupsArrayGetCount(tcreds));
+ printf(" Expiration: %s\n", httpGetDateString2(cupsGetCredentialsExpiration(tcreds), datestr, sizeof(datestr)));
+ printf(" ValidName: %s\n", cupsAreCredentialsValidForName(common_name, tcreds) ? "true" : "false");
+ printf(" Info: \"%s\"\n", tinfo);
+
+ free(tcreds);
+ }
+ else
+ {
+ puts(" Not present.");
+ }
+
+ return (0);
+}
+
+
+//
+// 'usage()' - Show program usage...
+//
+
+static int // O - Exit code
+usage(FILE *out) // I - Output file (stdout or stderr)
+{
+ _cupsLangPuts(out, _("Usage: cups-x509 [OPTIONS] [COMMAND] [ARGUMENT]"));
+ _cupsLangPuts(out, "");
+ _cupsLangPuts(out, _("Commands:"));
+ _cupsLangPuts(out, "");
+ _cupsLangPuts(out, _("ca COMMON-NAME Sign a CSR to produce a certificate."));
+ _cupsLangPuts(out, _("cacert COMMON-NAME Create a CA certificate."));
+ _cupsLangPuts(out, _("cert COMMON-NAME Create a certificate."));
+ _cupsLangPuts(out, _("client URI Connect to URI."));
+ _cupsLangPuts(out, _("csr COMMON-NAME Create a certificate signing request."));
+ _cupsLangPuts(out, _("server COMMON-NAME[:PORT] Run a HTTPS server (default port 8NNN.)"));
+ _cupsLangPuts(out, _("show COMMON-NAME Show stored credentials for COMMON-NAME."));
+ _cupsLangPuts(out, "");
+ _cupsLangPuts(out, _("Options:"));
+ _cupsLangPuts(out, _(""));
+ _cupsLangPuts(out, _("--help Show this help"));
+ _cupsLangPuts(out, _("--pin Pin certificate found by client command"));
+ _cupsLangPuts(out, _("--require-ca Require CA-signed certificate for client command"));
+ _cupsLangPuts(out, _("--version Show the program version"));
+ _cupsLangPuts(out, _("-C COUNTRY Set country."));
+ _cupsLangPuts(out, _("-L LOCALITY Set locality name."));
+ _cupsLangPuts(out, _("-O ORGANIZATION Set organization name."));
+ _cupsLangPuts(out, _("-R CSR-FILENAME Specify certificate signing request file."));
+ _cupsLangPuts(out, _("-S STATE Set state."));
+ _cupsLangPuts(out, _("-U ORGANIZATIONAL-UNIT Set organizational unit name."));
+ _cupsLangPuts(out, _("-a SUBJECT-ALT-NAME Add a subjectAltName."));
+ _cupsLangPuts(out, _("-d DAYS Set expiration date in days."));
+ _cupsLangPuts(out, _("-p PURPOSE Comma-delimited certificate purpose\n"
+ " (serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, OCSPSigning)"));
+ _cupsLangPuts(out, _("-r ROOT-NAME Name of root certificate"));
+ _cupsLangPuts(out, _("-t TYPE Certificate type\n"
+ " (rsa-2048, rsa-3072, rsa-4096, ecdsa-p256, ecdsa-p384, ecdsa-p521)"));
+ _cupsLangPuts(out, _("-u USAGE Comma-delimited key usage\n"
+ " (digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign, encipherOnly, decipherOnly, default-ca, default-tls)"));
+
+ return (out == stderr);
+}