diff options
-rw-r--r-- | CHANGES.md | 1 | ||||
-rw-r--r-- | man/ippeveprinter.1 | 16 | ||||
-rw-r--r-- | tools/Makefile | 4 | ||||
-rw-r--r-- | tools/ippeveprinter.c | 238 | ||||
-rw-r--r-- | xcode/CUPS.xcodeproj/project.pbxproj | 4 |
5 files changed, 253 insertions, 10 deletions
diff --git a/CHANGES.md b/CHANGES.md index 463570d72..f526061a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,7 @@ Changes in CUPS v2.3.1 - The libusb-based USB backend now reports an error when the distribution permissions are wrong (Issue #5658) - Added paint can labels to Dymo driver (Issue #5662) +- The `ippeveprinter` program now supports authentication (Issue #5665) - The `--with-dbusdir` option was ignored by the configure script (Issue #5671) - Sandboxed applications were not able to get the default printer (Issue #5676) - Log file access controls were not preserved by `cupsctl` (Issue #5677) diff --git a/man/ippeveprinter.1 b/man/ippeveprinter.1 index 5753c69da..6fc30d3ec 100644 --- a/man/ippeveprinter.1 +++ b/man/ippeveprinter.1 @@ -6,7 +6,7 @@ .\" Licensed under Apache License v2.0. See the file "LICENSE" for more .\" information. .\" -.TH ippeveprinter 1 "CUPS" "17 May 2019" "Apple Inc." +.TH ippeveprinter 1 "CUPS" "2 December 2019" "Apple Inc." .SH NAME ippeveprinter \- an ipp everywhere printer application for cups .SH SYNOPSIS @@ -16,10 +16,15 @@ ippeveprinter \- an ipp everywhere printer application for cups ] [ .B \-\-no\-web\-forms ] [ +.B \-\-pam\-service +.I service +] [ .B \-\-version ] [ .B \-2 ] [ +.B \-A +] [ .B \-D .I device-uri ] [ @@ -89,12 +94,21 @@ Show program usage. .B \-\-no\-web\-forms Disable the web interface forms used to update the media and supply levels. .TP 5 +\fB\-\-pam\-service \fIservice\fR +Set the PAM service name. +The default service is "other". +.TP 5 .B \-\-version Show the CUPS version. .TP 5 .B \-2 Report support for two-sided (duplex) printing. .TP 5 +.B \-A +Enable authentication for the created printer. +.B ippeveprinter +uses PAM to authenticate HTTP Basic credentials. +.TP 5 \fB\-D \fIdevice-uri\fR Set the device URI for print output. The URI can be a filename, directory, or a network socket URI of the form "socket://ADDRESS[:PORT]" (where the default port number is 9100). diff --git a/tools/Makefile b/tools/Makefile index be9cba366..f1781ef0b 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -147,7 +147,7 @@ local: ippeveprinter-static ipptool-static ippeveprinter: ippeveprinter.o ../cups/$(LIBCUPS) echo Linking $@... - $(LD_CC) $(ALL_LDFLAGS) -o $@ ippeveprinter.o $(DNSSDLIBS) $(LINKCUPS) + $(LD_CC) $(ALL_LDFLAGS) -o $@ ippeveprinter.o $(DNSSDLIBS) $(PAMLIBS) $(LINKCUPS) $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@ @@ -157,7 +157,7 @@ ippeveprinter: ippeveprinter.o ../cups/$(LIBCUPS) ippeveprinter-static: ippeveprinter.o ../cups/$(LIBCUPSSTATIC) echo Linking $@... - $(LD_CC) $(ALL_LDFLAGS) -o $@ ippeveprinter.o $(LINKCUPSSTATIC) + $(LD_CC) $(ALL_LDFLAGS) -o $@ ippeveprinter.o $(PAMLIBS) $(LINKCUPSSTATIC) $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@ diff --git a/tools/ippeveprinter.c b/tools/ippeveprinter.c index 2b921e235..a35c7321d 100644 --- a/tools/ippeveprinter.c +++ b/tools/ippeveprinter.c @@ -51,6 +51,7 @@ extern char **environ; # include <avahi-common/error.h> # include <avahi-common/thread-watch.h> #endif /* HAVE_DNSSD */ + #ifdef HAVE_SYS_MOUNT_H # include <sys/mount.h> #endif /* HAVE_SYS_MOUNT_H */ @@ -64,6 +65,14 @@ extern char **environ; # include <sys/vfs.h> #endif /* HAVE_SYS_VFS_H */ +#if HAVE_LIBPAM +# ifdef HAVE_PAM_PAM_APPL_H +# include <pam/pam_appl.h> +# else +# include <security/pam_appl.h> +# endif /* HAVE_PAM_PAM_APPL_H */ +#endif /* HAVE_LIBPAM */ + #include "printer-png.h" @@ -148,6 +157,14 @@ typedef void *ippeve_srv_t; /* Service reference */ typedef void *ippeve_txt_t; /* TXT record */ #endif /* HAVE_DNSSD */ +#if HAVE_LIBPAM +typedef struct ippeve_authdata_s /* Authentication data */ +{ + char username[HTTP_MAX_VALUE], /* Username string */ + *password; /* Password string */ +} ippeve_authdata_t; +#endif /* HAVE_LIBPAM */ + typedef struct ippeve_filter_s /**** Attribute filter ****/ { cups_array_t *ra; /* Requested attributes */ @@ -224,7 +241,9 @@ typedef struct ippeve_client_s /**** Client data ****/ char uri[1024], /* Request URI */ *options; /* URI options */ http_addr_t addr; /* Client address */ - char hostname[256]; /* Client hostname */ + char hostname[256], /* Client hostname */ + username[HTTP_MAX_VALUE]; + /* Authenticated username, if any */ ippeve_printer_t *printer; /* Printer */ ippeve_job_t *job; /* Current job, if any */ } ippeve_client_t; @@ -234,6 +253,7 @@ typedef struct ippeve_client_s /**** Client data ****/ * Local functions... */ +static http_status_t authenticate_request(ippeve_client_t *client); static void clean_jobs(ippeve_printer_t *printer); static int compare_jobs(ippeve_job_t *a, ippeve_job_t *b); static void copy_attributes(ipp_t *to, ipp_t *from, cups_array_t *ra, ipp_tag_t group_tag, int quickcopy); @@ -281,6 +301,9 @@ static ipp_t *load_legacy_attributes(const char *make, const char *model, int p #if !CUPS_LITE static ipp_t *load_ppd_attributes(const char *ppdfile, cups_array_t *docformats); #endif /* !CUPS_LITE */ +#if HAVE_LIBPAM +static int pam_func(int, const struct pam_message **, struct pam_response **, void *); +#endif /* HAVE_LIBPAM */ static int parse_options(ippeve_client_t *client, cups_option_t **options); static void process_attr_message(ippeve_job_t *job, char *message); static void *process_client(ippeve_client_t *client); @@ -316,6 +339,8 @@ static AvahiClient *DNSSDClient = NULL; static int KeepFiles = 0, /* Keep spooled job files? */ MaxVersion = 20,/* Maximum IPP version (20 = 2.0, 11 = 1.1, etc.) */ Verbosity = 0; /* Verbosity level */ +static const char *PAMService = NULL; + /* PAM service */ /* @@ -371,6 +396,14 @@ main(int argc, /* I - Number of command-line args */ { web_forms = 0; } + else if (!strcmp(argv[i], "--pam-service")) + { + i ++; + if (i >= argc) + usage(1); + + PAMService = argv[i]; + } else if (!strcmp(argv[i], "--version")) { puts(CUPS_SVERSION); @@ -392,6 +425,11 @@ main(int argc, /* I - Number of command-line args */ legacy = 1; break; + case 'A' : /* -A (enable authentication) */ + if (!PAMService) + PAMService = "other"; + break; + case 'D' : /* -D device-uri */ i ++; if (i >= argc) @@ -692,6 +730,102 @@ main(int argc, /* I - Number of command-line args */ /* + * 'authenticate_request()' - Try to authenticate the request. + */ + +static http_status_t /* O - HTTP_STATUS_CONTINUE to keep going, otherwise status to return */ +authenticate_request( + ippeve_client_t *client) /* I - Client */ +{ +#if HAVE_LIBPAM + /* + * If PAM isn't enabled, return 'continue' now... + */ + + const char *authorization; /* Pointer into Authorization string */ + int userlen; /* Username:password length */ + pam_handle_t *pamh; /* PAM authentication handle */ + int pamerr; /* PAM error code */ + struct pam_conv pamdata; /* PAM conversation data */ + ippeve_authdata_t data; /* Authentication data */ + + + if (!PAMService) + return (HTTP_STATUS_CONTINUE); + + /* + * Try authenticating using PAM... + */ + + authorization = httpGetField(client->http, HTTP_FIELD_AUTHORIZATION); + + if (strncmp(authorization, "Basic ", 6)) + { + fputs("Unsupported scheme in Authorization header.\n", stderr); + return (HTTP_STATUS_BAD_REQUEST); + } + + authorization += 5; + while (isspace(*authorization & 255)) + authorization ++; + + userlen = sizeof(data.username); + httpDecode64_2(data.username, &userlen, authorization); + + if ((data.password = strchr(data.username, ':')) == NULL) + { + fputs("No password in Authorization header.\n", stderr); + return (HTTP_STATUS_BAD_REQUEST); + } + + *(data.password)++ = '\0'; + + if (!data.username[0]) + { + fputs("No username in Authorization header.\n", stderr); + return (HTTP_STATUS_BAD_REQUEST); + } + + pamdata.conv = pam_func; + pamdata.appdata_ptr = &data; + + if ((pamerr = pam_start(PAMService, data.username, &pamdata, &pamh)) != PAM_SUCCESS) + { + fprintf(stderr, "pam_start() returned %d (%s)\n", pamerr, pam_strerror(pamh, pamerr)); + return (HTTP_STATUS_SERVER_ERROR); + } + + if ((pamerr = pam_authenticate(pamh, PAM_SILENT)) != PAM_SUCCESS) + { + fprintf(stderr, "pam_authenticate() returned %d (%s)\n", pamerr, pam_strerror(pamh, pamerr)); + pam_end(pamh, 0); + return (HTTP_STATUS_UNAUTHORIZED); + } + + if ((pamerr = pam_acct_mgmt(pamh, PAM_SILENT)) != PAM_SUCCESS) + { + fprintf(stderr, "pam_acct_mgmt() returned %d (%s)\n", pamerr, pam_strerror(pamh, pamerr)); + pam_end(pamh, 0); + return (HTTP_STATUS_SERVER_ERROR); + } + + strlcpy(client->username, data.username, sizeof(client->username)); + + pam_end(pamh, PAM_SUCCESS); + + return (HTTP_STATUS_CONTINUE); + +#else + /* + * No authentication support built-in, return 'continue'... + */ + + return (HTTP_STATUS_CONTINUE); +#endif /* HAVE_LIBPAM */ +} + + +/* * 'clean_jobs()' - Clean out old (completed) jobs. */ @@ -5226,6 +5360,78 @@ load_ppd_attributes( #endif /* !CUPS_LITE */ +#if HAVE_LIBPAM +/* + * 'pam_func()' - PAM conversation function. + */ + +static int /* O - Success or failure */ +pam_func( + int num_msg, /* I - Number of messages */ + const struct pam_message **msg, /* I - Messages */ + struct pam_response **resp, /* O - Responses */ + void *appdata_ptr) + /* I - Pointer to connection */ +{ + int i; /* Looping var */ + struct pam_response *replies; /* Replies */ + ippeve_authdata_t *data; /* Pointer to auth data */ + + + /* + * Allocate memory for the responses... + */ + + if ((replies = malloc(sizeof(struct pam_response) * (size_t)num_msg)) == NULL) + return (PAM_CONV_ERR); + + /* + * Answer all of the messages... + */ + + data = (ippeve_authdata_t *)appdata_ptr; + + for (i = 0; i < num_msg; i ++) + { + switch (msg[i]->msg_style) + { + case PAM_PROMPT_ECHO_ON: + replies[i].resp_retcode = PAM_SUCCESS; + replies[i].resp = strdup(data->username); + break; + + case PAM_PROMPT_ECHO_OFF: + replies[i].resp_retcode = PAM_SUCCESS; + replies[i].resp = strdup(data->password); + break; + + case PAM_TEXT_INFO: + replies[i].resp_retcode = PAM_SUCCESS; + replies[i].resp = NULL; + break; + + case PAM_ERROR_MSG: + replies[i].resp_retcode = PAM_SUCCESS; + replies[i].resp = NULL; + break; + + default: + free(replies); + return (PAM_CONV_ERR); + } + } + + /* + * Return the responses back to PAM... + */ + + *resp = replies; + + return (PAM_SUCCESS); +} +#endif /* HAVE_LIBPAM */ + + /* * 'parse_options()' - Parse URL options into CUPS options. * @@ -5431,6 +5637,8 @@ process_http(ippeve_client_t *client) /* I - Client connection */ * Clear state variables... */ + client->username[0] = '\0'; + ippDelete(client->request); ippDelete(client->response); @@ -5730,6 +5938,7 @@ process_ipp(ippeve_client_t *client) /* I - Client */ ipp_attribute_t *uri; /* Printer URI attribute */ int major, minor; /* Version number */ const char *name; /* Name of attribute */ + http_status_t status; /* Authentication status */ debug_attributes("Request", client->request, 1); @@ -5880,13 +6089,18 @@ process_ipp(ippeve_client_t *client) /* I - Client */ strcmp(resource, "/ipp/print"))) respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "%s %s not found.", name, ippGetString(uri, 0, NULL)); - else + else if (client->operation_id != IPP_OP_GET_PRINTER_ATTRIBUTES && (status = authenticate_request(client)) != HTTP_STATUS_CONTINUE) + { + return (respond_http(client, status, NULL, NULL, 0)); + } + else { /* * Try processing the operation... */ - switch (ippGetOperation(client->request)) + + switch (client->operation_id) { case IPP_OP_PRINT_JOB : ipp_print_job(client); @@ -6824,6 +7038,14 @@ respond_http( client->operation == HTTP_STATE_OPTIONS) httpSetField(client->http, HTTP_FIELD_ALLOW, "GET, HEAD, OPTIONS, POST"); + if (code == HTTP_STATUS_UNAUTHORIZED) + { + char value[256]; /* WWW-Authenticate value */ + + snprintf(value, sizeof(value), "Basic realm=\"%s\"", PAMService); + httpSetField(client->http, HTTP_FIELD_WWW_AUTHENTICATE, value); + } + if (type) { if (!strcmp(type, "text/html")) @@ -7664,10 +7886,12 @@ usage(int status) /* O - Exit status */ { _cupsLangPuts(stdout, _("Usage: ippeveprinter [options] \"name\"")); _cupsLangPuts(stdout, _("Options:")); - _cupsLangPuts(stderr, _("--help Show program help")); - _cupsLangPuts(stderr, _("--no-web-forms Disable web forms for media and supplies")); - _cupsLangPuts(stderr, _("--version Show program version")); + _cupsLangPuts(stdout, _("--help Show program help")); + _cupsLangPuts(stdout, _("--no-web-forms Disable web forms for media and supplies")); + _cupsLangPuts(stdout, _("--pam-service service Use the named PAM service")); + _cupsLangPuts(stdout, _("--version Show program version")); _cupsLangPuts(stdout, _("-2 Set 2-sided printing support (default=1-sided)")); + _cupsLangPuts(stdout, _("-A Enable authentication")); _cupsLangPuts(stdout, _("-D device-uri Set the device URI for the printer")); _cupsLangPuts(stdout, _("-F output-type/subtype Set the output format for the printer")); #ifdef HAVE_SSL @@ -7688,7 +7912,7 @@ usage(int status) /* O - Exit status */ _cupsLangPuts(stdout, _("-p port Set port number for printer")); _cupsLangPuts(stdout, _("-r subtype,[subtype] Set DNS-SD service subtype")); _cupsLangPuts(stdout, _("-s speed[,color-speed] Set speed in pages per minute")); - _cupsLangPuts(stderr, _("-v Be verbose")); + _cupsLangPuts(stdout, _("-v Be verbose")); exit(status); } diff --git a/xcode/CUPS.xcodeproj/project.pbxproj b/xcode/CUPS.xcodeproj/project.pbxproj index a5a476767..21b1212d9 100644 --- a/xcode/CUPS.xcodeproj/project.pbxproj +++ b/xcode/CUPS.xcodeproj/project.pbxproj @@ -472,6 +472,7 @@ 278C58EA136B64B000836530 /* Kerberos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278C58E6136B64B000836530 /* Kerberos.framework */; }; 278C58EB136B64B000836530 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278C58E7136B64B000836530 /* Security.framework */; }; 278C58EC136B64B000836530 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278C58E8136B64B000836530 /* SystemConfiguration.framework */; }; + 279AE6F52395B80F004DD600 /* libpam.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 279AE6F42395B80F004DD600 /* libpam.tbd */; }; 27A034821A8BDC3A00650675 /* lpadmin.c in Sources */ = {isa = PBXBuildFile; fileRef = 2732E08D137A3F5200FAFEF6 /* lpadmin.c */; }; 27A034851A8BDC5C00650675 /* libcups.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 72220EAE1333047D00FCA411 /* libcups.dylib */; }; 7200511218F492F200E7B81B /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278C58E5136B64AF00836530 /* CoreFoundation.framework */; }; @@ -3307,6 +3308,7 @@ 278C58E6136B64B000836530 /* Kerberos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Kerberos.framework; path = /System/Library/Frameworks/Kerberos.framework; sourceTree = "<absolute>"; }; 278C58E7136B64B000836530 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = /System/Library/Frameworks/Security.framework; sourceTree = "<absolute>"; }; 278C58E8136B64B000836530 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = /System/Library/Frameworks/SystemConfiguration.framework; sourceTree = "<absolute>"; }; + 279AE6F42395B80F004DD600 /* libpam.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libpam.tbd; path = usr/lib/libpam.tbd; sourceTree = SDKROOT; }; 27A0347B1A8BDB1300650675 /* lpadmin */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = lpadmin; sourceTree = BUILT_PRODUCTS_DIR; }; 27D3037D134148CB00F022B1 /* libcups2.def */ = {isa = PBXFileReference; lastKnownFileType = text; name = libcups2.def; path = ../cups/libcups2.def; sourceTree = "<group>"; }; 27F89DA21B3AC43B00E5A4B7 /* testraster.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = testraster.c; path = ../cups/testraster.c; sourceTree = "<group>"; }; @@ -4637,6 +4639,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 279AE6F52395B80F004DD600 /* libpam.tbd in Frameworks */, 273B1ECA226B420C00428143 /* libcups.dylib in Frameworks */, 2767FC6619267538000F61D3 /* CoreFoundation.framework in Frameworks */, 2767FC6719267538000F61D3 /* libresolv.dylib in Frameworks */, @@ -5130,6 +5133,7 @@ 72220FB113330B4A00FCA411 /* Frameworks */ = { isa = PBXGroup; children = ( + 279AE6F42395B80F004DD600 /* libpam.tbd */, 2767FC591926750C000F61D3 /* CoreFoundation.framework */, 2767FC5A1926750C000F61D3 /* libiconv.dylib */, 2767FC5B1926750C000F61D3 /* libresolv.dylib */, |