]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
Don't allow local certificates over the loopback interface, drop support for writing...
authorMichael R Sweet <msweet@msweet.org>
Tue, 31 Mar 2026 19:55:31 +0000 (15:55 -0400)
committerMichael R Sweet <msweet@msweet.org>
Tue, 31 Mar 2026 19:55:31 +0000 (15:55 -0400)
cups/auth.c
doc/help/man-cups-files.conf.html
man/cups-files.conf.5
scheduler/auth.c
scheduler/client.c
scheduler/conf.c
scheduler/conf.h
scheduler/ipp.c
scheduler/job.c
test/4.2-cups-printer-ops.test
test/5.1-lpadmin.sh

index b29e998be6272c1e34a467764d19c1848a94fd97..9f962a83c97e88b018cd71bef08c39e0e4c3bd92 100644 (file)
@@ -1,7 +1,7 @@
 //
 // Authentication functions for CUPS.
 //
-// Copyright © 2020-2025 by OpenPrinting.
+// Copyright © 2020-2026 by OpenPrinting.
 // Copyright © 2007-2019 by Apple Inc.
 // Copyright © 1997-2007 by Easy Software Products.
 //
@@ -707,7 +707,7 @@ cups_auth_scheme(const char *www_authenticate,      // I - Pointer into WWW-Authentic
 static bool                            // O - `true` if authorized, `false` otherwise
 cups_do_local_auth(http_t *http)       // I - HTTP connection to server
 {
-#if !_WIN32 && !__EMX__
+#if !_WIN32 && !__EMX__ && defined(AF_LOCAL)
   int                  pid;            // Current process ID
   FILE                 *fp;            // Certificate file
   char                 trc[16],        // Try Root Certificate parameter
@@ -729,7 +729,7 @@ cups_do_local_auth(http_t *http)    // I - HTTP connection to server
   DEBUG_printf("7cups_local_auth(http=%p) hostaddr=%s, hostname=\"%s\"", (void *)http, httpAddrString(http->hostaddr, filename, sizeof(filename)), http->hostname);
 
   // See if we are accessing localhost...
-  if (!httpAddrIsLocalhost(httpGetAddress(http)))
+  if (httpAddrGetFamily(httpGetAddress(http)) != AF_LOCAL)
   {
     DEBUG_puts("8cups_local_auth: Not a local connection, returning false.");
     return (false);
@@ -794,12 +794,11 @@ cups_do_local_auth(http_t *http)  // I - HTTP connection to server
   }
 #  endif // HAVE_AUTHORIZATION_H
 
-#  if defined(SO_PEERCRED) && defined(AF_LOCAL)
+#  if defined(SO_PEERCRED)
   // See if we can authenticate using the peer credentials provided over a
   // domain socket; if so, specify "PeerCred username" as the authentication
   // information...
-  if (http->hostaddr->addr.sa_family == AF_LOCAL &&
-      !getenv("GATEWAY_INTERFACE") &&  // Not via CGI programs...
+  if (!getenv("GATEWAY_INTERFACE") &&  // Not via CGI programs...
       cups_auth_find(www_auth, "PeerCred"))
   {
     // Verify that the current cupsGetUser() matches the current UID...
@@ -863,7 +862,7 @@ cups_do_local_auth(http_t *http)    // I - HTTP connection to server
       return (true);
     }
   }
-#endif // !_WIN32 && !__EMX__
+#endif // !_WIN32 && !__EMX__ && defined(AF_LOCAL)
 
   DEBUG_puts("8cups_do_local_auth: Returning false.");
 
index f8d4188ada36a7911bd3e10d5af6052bca439766..bd0c9cbc1a346bc675e9ba6a02335793f424b343 100644 (file)
@@ -240,11 +240,15 @@ The default is &quot;lp&quot;.
     <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>FileDevice Yes</strong><br>
 </p>
     <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>FileDevice No</strong><br>
+</p>
+    <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>FileDevice ^</strong><em>PATH-REGEX</em><br>
 Specifies whether the file pseudo-device can be used for new printer queues.
 The URI &quot;file:///dev/null&quot; is always allowed.
 File devices cannot be used with &quot;raw&quot; print queues - a PPD file is required.
-The specified file is overwritten for every print job.
-Writing to directories is not supported.
+The specified file must already exist and is opened for writing in exclusive mode.
+Creating new files or writing to directories is not supported.
+Specifying a value of &quot;Yes&quot; allows access to TTY devices of the form &quot;/dev/ttySOMETHING&quot;.
+Specifying a regular expression allows access to TTY devices whose paths match the specified regular expression.
 </p>
     <p style="margin-left: 2.5em; text-indent: -2.5em;"><strong>Printcap </strong><em>filename</em><br>
 Specifies a file that is filled with a list of local print queues.
index 3caa50e855f9ad023f6dccbae99671f56abf6dec..bbd1b543099d86ebc17981c07adb2999d7c101a8 100644 (file)
@@ -1,14 +1,14 @@
 .\"
 .\" cups-files.conf man page for CUPS.
 .\"
-.\" Copyright © 2020-2025 by OpenPrinting.
+.\" Copyright © 2020-2026 by OpenPrinting.
 .\" Copyright © 2007-2019 by Apple Inc.
 .\" Copyright © 1997-2006 by Easy Software Products.
 .\"
 .\" Licensed under Apache License v2.0.  See the file "LICENSE" for more
 .\" information.
 .\"
-.TH cups-files.conf 5 "CUPS" "2025-10-08" "OpenPrinting"
+.TH cups-files.conf 5 "CUPS" "2026-03-19" "OpenPrinting"
 .SH NAME
 cups\-files.conf \- file and directory configuration file for cups
 .SH DESCRIPTION
@@ -266,11 +266,15 @@ The following directives are deprecated and will be removed from a future versio
 \fBFileDevice Yes\fR
 .TP 5
 \fBFileDevice No\fR
+.TP 5
+\fBFileDevice ^\fIPATH-REGEX\fR
 Specifies whether the file pseudo-device can be used for new printer queues.
 The URI "file:///dev/null" is always allowed.
 File devices cannot be used with "raw" print queues - a PPD file is required.
-The specified file is overwritten for every print job.
-Writing to directories is not supported.
+The specified file must already exist and is opened for writing in exclusive mode.
+Creating new files or writing to directories is not supported.
+Specifying a value of "Yes" allows access to TTY devices of the form "/dev/ttySOMETHING".
+Specifying a regular expression allows access to TTY devices whose paths match the specified regular expression.
 .\"#Printcap
 .TP 5
 \fBPrintcap \fIfilename\fR
index 55886076f52d535f1827786b6afe90c9dde661d0..95a71a19b720cac6516edb20712f472d3bb5c869 100644 (file)
@@ -382,7 +382,7 @@ cupsdAuthorize(cupsd_client_t *con) /* I - Client connection */
   }
 #ifdef HAVE_AUTHORIZATION_H
   else if (!strncmp(authorization, "AuthRef ", 8) &&
-           httpAddrLocalhost(httpGetAddress(con->http)))
+           httpAddrGetFamily(httpGetAddress(con->http)) == AF_LOCAL)
   {
     OSStatus           status;         /* Status */
     char               authdata[HTTP_MAX_VALUE];
@@ -463,7 +463,7 @@ cupsdAuthorize(cupsd_client_t *con) /* I - Client connection */
 #endif /* HAVE_AUTHORIZATION_H */
 #if defined(SO_PEERCRED) && defined(AF_LOCAL)
   else if (PeerCred != CUPSD_PEERCRED_OFF && !strncmp(authorization, "PeerCred ", 9) &&
-           con->http->hostaddr->addr.sa_family == AF_LOCAL && con->best)
+           httpAddrGetFamily(httpGetAddress(con->http)) == AF_LOCAL && con->best)
   {
    /*
     * Use peer credentials from domain socket connection...
@@ -553,7 +553,7 @@ cupsdAuthorize(cupsd_client_t *con) /* I - Client connection */
   }
 #endif /* SO_PEERCRED && AF_LOCAL */
   else if (!strncmp(authorization, "Local", 5) &&
-          httpAddrLocalhost(httpGetAddress(con->http)))
+          httpAddrGetFamily(httpGetAddress(con->http)) == AF_LOCAL)
   {
    /*
     * Get Local certificate authentication data...
index 22754a216b4ef9c489f9d20c184cf92fdeab8d9c..344e806fb1bc7eea0ce189fe8944e57522ee549b 100644 (file)
@@ -2194,7 +2194,7 @@ cupsdSendHeader(
       cupsCopyString(auth_str, "Negotiate", sizeof(auth_str));
     }
 
-    if (con->best && !con->is_browser && !_cups_strcasecmp(httpGetHostname(con->http, NULL, 0), "localhost"))
+    if (con->best && !con->is_browser && httpAddrGetFamily(httpGetAddress(con->http)) == AF_LOCAL)
     {
      /*
       * Add a "trc" (try root certification) parameter for local
@@ -2214,7 +2214,7 @@ cupsdSendHeader(
       auth_size = sizeof(auth_str) - (size_t)(auth_key - auth_str);
 
 #if defined(SO_PEERCRED) && defined(AF_LOCAL)
-      if (PeerCred != CUPSD_PEERCRED_OFF && httpAddrGetFamily(httpGetAddress(con->http)) == AF_LOCAL)
+      if (PeerCred != CUPSD_PEERCRED_OFF)
       {
         cupsCopyString(auth_key, ", PeerCred", auth_size);
         auth_key += 10;
index 3bb6b00f9c31bedb38221c1b9b1367c4bb8868ce..03a132797ada04935ce7c7e9a1cf8ddccd8c6b7e 100644 (file)
@@ -134,7 +134,6 @@ static const cupsd_var_t    cupsfiles_vars[] =
   { "DataDir",                 &DataDir,               CUPSD_VARTYPE_STRING },
   { "DocumentRoot",            &DocumentRoot,          CUPSD_VARTYPE_STRING },
   { "ErrorLog",                        &ErrorLog,              CUPSD_VARTYPE_STRING },
-  { "FileDevice",              &FileDevice,            CUPSD_VARTYPE_BOOLEAN },
   { "LogFilePerm",             &LogFilePerm,           CUPSD_VARTYPE_PERM },
   { "OAuthScopes",             &OAuthScopes,           CUPSD_VARTYPE_STRING },
   { "OAuthServer",             &OAuthServer,           CUPSD_VARTYPE_STRING },
@@ -574,6 +573,21 @@ cupsdReadConfiguration(void)
   old_remote_port = RemotePort;
   RemotePort      = 0;
 
+ /*
+  * FileDevice...
+  */
+
+  if (FileDevice)
+  {
+   /*
+    * Free file: device path matching regular expression...
+    */
+
+    regfree(FileDevice);
+    free(FileDevice);
+    FileDevice = NULL;
+  }
+
  /*
   * String options...
   */
@@ -741,7 +755,6 @@ cupsdReadConfiguration(void)
   JobKillDelay             = DEFAULT_TIMEOUT;
   JobRetryLimit            = 5;
   JobRetryInterval         = 300;
-  FileDevice               = FALSE;
   FilterLevel              = 0;
   FilterLimit              = 0;
   FilterNice               = 0;
@@ -3629,6 +3642,73 @@ read_cups_files_conf(cups_file_t *fp)    /* I - File to read from */
     {
       FatalErrors = parse_fatal_errors(value);
     }
+    else if (!_cups_strcasecmp(line, "FileDevice") && value)
+    {
+     /*
+      * FileDevice BOOLEAN
+      * FileDevice ^PATH-REGEX
+      */
+
+      int      r;                      /* Regular expression status */
+      char     rerror[1024];           /* Regular expression error */
+
+      if (FileDevice)
+      {
+       /*
+        * Free existing regular expression...
+        */
+
+       regfree(FileDevice);
+       free(FileDevice);
+       FileDevice = NULL;
+      }
+
+      if (!_cups_strcasecmp(value, "false") || !_cups_strcasecmp(value, "no") || !_cups_strcasecmp(value, "off"))
+      {
+       /*
+        * Do nothing more, no file: device support beyond /dev/null...
+        */
+
+        continue;
+      }
+      else if (!_cups_strcasecmp(value, "on") || !_cups_strcasecmp(value, "true") || !_cups_strcasecmp(value, "yes"))
+      {
+       /*
+        * Enable file: device support for /dev/ttySOMETHING...
+        */
+
+        value = "^/dev/tty[A-Za-z0-9._]+$";
+      }
+      else if (*value != '^')
+      {
+       cupsdLogMessage(CUPSD_LOG_ERROR, "Unsupported FileDevice \"%s\" on line %d of %s.", value, linenum, CupsFilesFile);
+       if (FatalErrors & CUPSD_FATAL_CONFIG)
+         return (0);
+      }
+
+     /*
+      * Try compiling the regular expression in "value"...
+      */
+
+      if ((FileDevice = calloc(1, sizeof(regex_t))) == NULL)
+      {
+       cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create FileDevice regular expression \"%s\" on line %d of %s.", value, linenum, CupsFilesFile);
+       if (FatalErrors & CUPSD_FATAL_CONFIG)
+         return (0);
+      }
+
+      if ((r = regcomp(FileDevice, value, REG_EXTENDED | REG_NOSUB)) != 0)
+      {
+       regerror(r, FileDevice, rerror, sizeof(rerror));
+       free(FileDevice);
+       FileDevice = NULL;
+
+       cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to compile FileDevice regular expression \"%s\" on line %d of %s: %s", value, linenum, CupsFilesFile, rerror);
+
+       if (FatalErrors & CUPSD_FATAL_CONFIG)
+         return (0);
+      }
+    }
     else if (!_cups_strcasecmp(line, "Group") && value)
     {
      /*
index 691364f0a79b5e32472962d5ea9653433ba101a9..19dfdb1340e7f4f16a72533a9bac2b7986cf50e9 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Configuration file definitions for the CUPS scheduler.
  *
- * Copyright © 2020-2025 by OpenPrinting.
+ * Copyright © 2020-2026 by OpenPrinting.
  * Copyright © 2007-2018 by Apple Inc.
  * Copyright © 1997-2007 by Easy Software Products, all rights reserved.
  *
@@ -203,11 +203,11 @@ VAR int                   MaxClients              VALUE(100),
                                        /* Do we do reverse lookups? */
                        Timeout                 VALUE(DEFAULT_TIMEOUT),
                                        /* Timeout during requests */
-                       KeepAlive               VALUE(TRUE),
+                       KeepAlive               VALUE(TRUE);
                                        /* Support the Keep-Alive option? */
-                       FileDevice              VALUE(FALSE),
+VAR regex_t            *FileDevice             VALUE(NULL);
                                        /* Allow file: devices? */
-                       FilterLimit             VALUE(0),
+VAR int                        FilterLimit             VALUE(0),
                                        /* Max filter cost at any time */
                        FilterLevel             VALUE(0),
                                        /* Current filter level */
index 691dbb381aaba6c507373b020f9d9fa9ceeb2c38..6677d729aaa859391a50936f00fc0265f3723f49 100644 (file)
@@ -2291,21 +2291,32 @@ add_printer(cupsd_client_t  *con,       /* I - Client connection */
       * See if the administrator has enabled file devices...
       */
 
-      if (!FileDevice && strcmp(resource, "/dev/null"))
+      if (strcmp(resource, "/dev/null"))
       {
-       /*
-        * File devices are disabled and the URL is not file:/dev/null...
-       */
+        if (FileDevice && regexec(FileDevice, resource, /*nmatch*/0, /*pmatch*/NULL, /*eflags*/0))
+        {
+        /*
+         * File devices are enabled but the path is not allowed...
+         */
 
-       send_ipp_status(con, IPP_STATUS_ERROR_NOT_POSSIBLE,
-                       _("File device URIs have been disabled. "
-                         "To enable, see the FileDevice directive in "
-                         "\"%s/cups-files.conf\"."),
-                       ServerRoot);
-       if (!modify)
-         cupsdDeletePrinter(printer, 0);
+         send_ipp_status(con, IPP_STATUS_ERROR_NOT_POSSIBLE, _("File device URI \"%s\" is not allowed."), ippGetString(attr, 0, NULL));
+         if (!modify)
+           cupsdDeletePrinter(printer, 0);
 
-       return;
+         return;
+        }
+        else if (!FileDevice)
+       {
+        /*
+         * File devices are disabled and the URL is not file:///dev/null...
+         */
+
+         send_ipp_status(con, IPP_STATUS_ERROR_NOT_POSSIBLE, _("File device URIs have been disabled. To enable, see the FileDevice directive in \"%s/cups-files.conf\"."), ServerRoot);
+         if (!modify)
+           cupsdDeletePrinter(printer, 0);
+
+         return;
+       }
       }
     }
     else
@@ -5483,7 +5494,7 @@ create_local_printer(
   * Require local access to create a local printer...
   */
 
-  if (!httpAddrLocalhost(httpGetAddress(con->http)))
+  if (httpAddrGetFamily(httpGetAddress(con->http)) != AF_LOCAL)
   {
     send_ipp_status(con, IPP_STATUS_ERROR_FORBIDDEN, _("Only local users can create a local printer."));
     return;
@@ -5543,9 +5554,9 @@ create_local_printer(
 
   ptr = ippGetString(device_uri, 0, NULL);
 
-  if (!ptr || !ptr[0])
+  if (!ptr || !ptr[0] || (strncmp(ptr, "ipp://", 6) && strncmp(ptr, "ipps://", 7)))
   {
-    send_ipp_status(con, IPP_STATUS_ERROR_BAD_REQUEST, _("Attribute \"%s\" has empty value."), "device-uri");
+    send_ipp_status(con, IPP_STATUS_ERROR_NOT_POSSIBLE, _("Bad device-uri \"%s\"."), ptr);
 
     return;
   }
index 9f7a44ea118ecc3b48e9f6cc87511a759e6c02a6..b05ed7f8df0a87cd9f3d0bd351b81ec0e7a3ac31 100644 (file)
@@ -1152,35 +1152,45 @@ cupsdContinueJob(cupsd_job_t *job)      /* I - Job */
        }
        else
        {
+         char  scheme[32],             /* URI scheme */
+               userpass[32],           /* URI username:password */
+               host[256],              /* URI hostname */
+               resource[1024];         /* URI resource path (filename) */
+         int   port;                   /* URI port number */
+
+          httpSeparateURI(HTTP_URI_CODING_ALL, job->printer->device_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource));
+
          job->print_pipes[0] = -1;
-         if (!strcmp(job->printer->device_uri, "file:/dev/null") ||
-             !strcmp(job->printer->device_uri, "file:///dev/null"))
-           job->print_pipes[1] = -1;
-         else
+         job->print_pipes[1] = -1;
+
+         if (strcmp(resource, "/dev/null"))
          {
-           if (!strncmp(job->printer->device_uri, "file:/dev/", 10))
-             job->print_pipes[1] = open(job->printer->device_uri + 5,
-                                        O_WRONLY | O_EXCL);
-           else if (!strncmp(job->printer->device_uri, "file:///dev/", 12))
-             job->print_pipes[1] = open(job->printer->device_uri + 7,
-                                        O_WRONLY | O_EXCL);
-           else if (!strncmp(job->printer->device_uri, "file:///", 8))
-             job->print_pipes[1] = open(job->printer->device_uri + 7,
-                                        O_WRONLY | O_CREAT | O_TRUNC, 0600);
-           else
-             job->print_pipes[1] = open(job->printer->device_uri + 5,
-                                        O_WRONLY | O_CREAT | O_TRUNC, 0600);
+           if (!FileDevice)
+           {
+             abort_message = "Stopping job because file: output is disabled.";
 
-           if (job->print_pipes[1] < 0)
+              goto abort_job;
+           }
+           else if (regexec(FileDevice, resource, /*nmatch*/0, /*pmatch*/NULL, /*eflags*/0))
            {
-             abort_message = "Stopping job because the scheduler could not "
-                             "open the output file.";
+             abort_message = "Stopping job because file: output is not allowed to the specified path.";
 
               goto abort_job;
            }
+           else if ((job->print_pipes[1] = open(resource, O_WRONLY | O_EXCL)) < 0)
+           {
+             abort_message = "Stopping job because the scheduler could not open the output file.";
 
-           fcntl(job->print_pipes[1], F_SETFD,
-                 fcntl(job->print_pipes[1], F_GETFD) | FD_CLOEXEC);
+              goto abort_job;
+           }
+           else
+           {
+            /*
+             * Close this file on execute...
+             */
+
+             fcntl(job->print_pipes[1], F_SETFD, fcntl(job->print_pipes[1], F_GETFD) | FD_CLOEXEC);
+           }
           }
        }
       }
index a4bf9bbdca56d9bd3e9ad4e8370cd5bcde590b49..0abaccaf2216eb19477c46835bfe0799078be0e1 100644 (file)
@@ -1,7 +1,7 @@
 #
 # Verify that the CUPS printer operations work.
 #
-# Copyright © 2020-2024 by OpenPrinting.
+# Copyright © 2020-2026 by OpenPrinting.
 # Copyright © 2007-2019 by Apple Inc.
 # Copyright © 2001-2006 by Easy Software Products. All rights reserved.
 #
        ATTR uri printer-uri $scheme://$hostname:$port/printers/Test2
 
        GROUP printer
-       ATTR uri device-uri file:/tmp/Test2
+       ATTR uri device-uri file:///dev/null
        ATTR enum printer-state 3
        ATTR boolean printer-is-accepting-jobs true
 
        ATTR uri printer-uri $scheme://$hostname:$port/printers/Test1
 
        GROUP printer
-       ATTR uri device-uri file:/tmp/Test1
+       ATTR uri device-uri file:///dev/null
        ATTR enum printer-state 3
        ATTR boolean printer-is-accepting-jobs true
        ATTR text printer-info "Test Printer 1"
index aa398000a1920a5259beaf26d1d1ca405e57be2e..36f2822275db1a4aa4e3986db2c64ae2898d7ed2 100644 (file)
@@ -2,7 +2,7 @@
 #
 # Test the lpadmin command.
 #
-# Copyright © 2020-2024 by OpenPrinting.
+# Copyright © 2020-2026 by OpenPrinting.
 # Copyright © 2007-2018 by Apple Inc.
 # Copyright © 1997-2005 by Easy Software Products, all rights reserved.
 #
@@ -12,8 +12,8 @@
 
 echo "Add Printer Test"
 echo ""
-echo "    lpadmin -p Test3 -v file:/dev/null -E -m drv:///sample.drv/deskjet.ppd"
-$runcups $VALGRIND ../systemv/lpadmin -p Test3 -v file:/dev/null -E -m drv:///sample.drv/deskjet.ppd 2>&1
+echo "    lpadmin -p Test3 -v file:///dev/null -E -m drv:///sample.drv/deskjet.ppd"
+$runcups $VALGRIND ../systemv/lpadmin -p Test3 -v file:///dev/null -E -m drv:///sample.drv/deskjet.ppd 2>&1
 if test $? != 0; then
        echo "    FAILED"
        exit 1
@@ -29,8 +29,8 @@ echo ""
 
 echo "Modify Printer Test"
 echo ""
-echo "    lpadmin -p Test3 -v file:/tmp/Test3 -o PageSize=A4"
-$runcups $VALGRIND ../systemv/lpadmin -p Test3 -v file:/tmp/Test3 -o PageSize=A4 2>&1
+echo "    lpadmin -p Test3 -v file:///dev/null -o PageSize=A4"
+$runcups $VALGRIND ../systemv/lpadmin -p Test3 -v file:///dev/null -o PageSize=A4 2>&1
 if test $? != 0; then
        echo "    FAILED"
        exit 1
@@ -65,8 +65,8 @@ echo ""
 
 echo "Add a printer for cupSNMP/IPPSupplies test"
 echo ""
-echo "    lpadmin -p Test4 -E -v file:/dev/null -m drv:///sample.drv/zebra.ppd"
-$runcups $VALGRIND ../systemv/lpadmin -p Test4 -E -v file:/dev/null -m drv:///sample.drv/zebra.ppd 2>&1
+echo "    lpadmin -p Test4 -E -v file:///dev/null -m drv:///sample.drv/zebra.ppd"
+$runcups $VALGRIND ../systemv/lpadmin -p Test4 -E -v file:///dev/null -m drv:///sample.drv/zebra.ppd 2>&1
 if test $? != 0; then
        echo "    FAILED"
        exit 1