]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
Add authentication support to ippeveprinter (Issue #5665)
authorMichael R Sweet <michael.r.sweet@gmail.com>
Mon, 2 Dec 2019 21:24:39 +0000 (16:24 -0500)
committerMichael R Sweet <michael.r.sweet@gmail.com>
Mon, 2 Dec 2019 21:24:39 +0000 (16:24 -0500)
CHANGES.md
man/ippeveprinter.1
tools/Makefile
tools/ippeveprinter.c
xcode/CUPS.xcodeproj/project.pbxproj

index 463570d726c4c4d7f2a723322fa479c5f2f79f58..f526061a0a08284fcbb92079b3f2c7d867b54b44 100644 (file)
@@ -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)
index 5753c69da6f6d9ec0e6eea6e0c3e39595c282114..6fc30d3ec16e13bf127bc45bae40cb360ee3dffb 100644 (file)
@@ -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).
index be9cba3663ef9d34cc68213ee477001b9d6408ca..f1781ef0b892bede201f440cd2ed162918fec7d2 100644 (file)
@@ -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)" $@
 
 
index 2b921e235410f8db876d8901aabd759a860b5bf1..a35c7321dc2775d28087c4a1b9b70cda6cf79c29 100644 (file)
@@ -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)
@@ -691,6 +729,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);
 }
index a5a4767676fe73bd180493497fa52072547f5e97..21b1212d9eaaa27db6319944c5875052ade0c18b 100644 (file)
                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 */; };
                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>"; };
                        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 */,
                72220FB113330B4A00FCA411 /* Frameworks */ = {
                        isa = PBXGroup;
                        children = (
+                               279AE6F42395B80F004DD600 /* libpam.tbd */,
                                2767FC591926750C000F61D3 /* CoreFoundation.framework */,
                                2767FC5A1926750C000F61D3 /* libiconv.dylib */,
                                2767FC5B1926750C000F61D3 /* libresolv.dylib */,