]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - scheduler/client.c
Sync changes and files from 1.1.x.
[thirdparty/cups.git] / scheduler / client.c
index e698574d604f9fad0ce6202b75a199b0e0205921..de825609becaf707cdd59df3c2d563d34c4f766e 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * "$Id: client.c,v 1.91.2.82 2004/06/17 14:45:12 mike Exp $"
+ * "$Id$"
  *
  *   Client routines for the Common UNIX Printing System (CUPS) scheduler.
  *
- *   Copyright 1997-2003 by Easy Software Products, all rights reserved.
+ *   Copyright 1997-2004 by Easy Software Products, all rights reserved.
  *
  *   These coded instructions, statements, and computer programs are the
  *   property of Easy Software Products and are protected by Federal
@@ -15,9 +15,9 @@
  *       Attn: CUPS Licensing Information
  *       Easy Software Products
  *       44141 Airport View Drive, Suite 204
- *       Hollywood, Maryland 20636-3111 USA
+ *       Hollywood, Maryland 20636 USA
  *
- *       Voice: (301) 373-9603
+ *       Voice: (301) 373-9600
  *       EMail: cups-info@cups.org
  *         WWW: http://www.cups.org
  *
@@ -39,6 +39,7 @@
  *   decode_auth()         - Decode an authorization string.
  *   get_file()            - Get a filename and state info.
  *   install_conf_file()   - Install a configuration file.
+ *   is_path_absolute()    - Is a path absolute and free of relative elements.
  *   pipe_command()        - Pipe the output of a command to the remote client.
  *   CDSAReadFunc()        - Read function for CDSA decryption code.
  *   CDSAWriteFunc()       - Write function for CDSA encryption code.
@@ -63,6 +64,7 @@ static void           decode_auth(client_t *con);
 static char            *get_file(client_t *con, struct stat *filestats, 
                                  char *filename, int len);
 static http_status_t   install_conf_file(client_t *con);
+static int             is_path_absolute(const char *path);
 static int             pipe_command(client_t *con, int infile, int *outfile,
                                     char *command, char *options);
 
@@ -163,7 +165,7 @@ AcceptClient(listener_t *lis)       /* I - Listener socket */
 
     return;
   }
-  
+
  /*
   * Get the hostname or format the IP address as needed...
   */
@@ -459,7 +461,7 @@ CloseClient(client_t *con)  /* I - Client to close */
       close(con->http.fd);
       FD_CLR(con->http.fd, InputSet);
       FD_CLR(con->http.fd, OutputSet);
-      con->http.fd = 0;
+      con->http.fd = -1;
     }
   }
 
@@ -1201,7 +1203,7 @@ ReadClient(client_t *con)         /* I - Client to read from */
       httpPrintf(HTTP(con), "Content-Length: 0\r\n");
       httpPrintf(HTTP(con), "\r\n");
     }
-    else if (strstr(con->uri, "..") != NULL)
+    else if (!is_path_absolute(con->uri))
     {
      /*
       * Protect against malicious users!
@@ -1229,15 +1231,13 @@ ReadClient(client_t *con)               /* I - Client to read from */
        httpPrintf(HTTP(con), "\r\n");
 
         EncryptClient(con);
-
-       status = IsAuthorized(con);
 #else
        if (!SendError(con, HTTP_NOT_IMPLEMENTED))
          return (CloseClient(con));
 #endif /* HAVE_SSL */
       }
 
-      if (status != HTTP_OK)
+      if ((status = IsAuthorized(con)) != HTTP_OK)
       {
         LogMessage(L_DEBUG2, "ReadClient: Unauthorized request for %s...\n",
                   con->uri);
@@ -1287,7 +1287,11 @@ ReadClient(client_t *con)                /* I - Client to read from */
               if (strncmp(con->uri, "/admin", 6) == 0)
              {
                SetStringf(&con->command, "%s/cgi-bin/admin.cgi", ServerBin);
-               SetString(&con->options, con->uri + 6);
+
+               if ((ptr = strchr(con->uri + 6, '?')) != NULL)
+                 SetStringf(&con->options, "admin%s", ptr);
+               else
+                 SetString(&con->options, "admin");
              }
               else if (strncmp(con->uri, "/printers", 9) == 0)
              {
@@ -1302,11 +1306,11 @@ ReadClient(client_t *con)               /* I - Client to read from */
              else
              {
                SetStringf(&con->command, "%s/cgi-bin/jobs.cgi", ServerBin);
-               SetString(&con->options, con->uri + 5);
+                SetString(&con->options, con->uri + 5);
              }
 
-             if (con->options[0] == '/')
-               cups_strcpy(con->options, con->options + 1);
+              if (con->options[0] == '/')
+               cups_strcpy(con->options, con->options + 1);
 
               if (!SendCommand(con, con->command, con->options))
              {
@@ -1352,6 +1356,11 @@ ReadClient(client_t *con)                /* I - Client to read from */
 
               if (IsCGI(con, filename, &filestats, type))
              {
+              /*
+               * Note: con->command and con->options were set by
+               * IsCGI()...
+               */
+
                if (!SendCommand(con, con->command, con->options))
                {
                  if (!SendError(con, HTTP_NOT_FOUND))
@@ -1437,7 +1446,11 @@ ReadClient(client_t *con)                /* I - Client to read from */
               if (strncmp(con->uri, "/admin", 6) == 0)
              {
                SetStringf(&con->command, "%s/cgi-bin/admin.cgi", ServerBin);
-               SetString(&con->options, con->uri + 6);
+
+               if ((ptr = strchr(con->uri + 6, '?')) != NULL)
+                 SetStringf(&con->options, "admin%s", ptr);
+               else
+                 SetString(&con->options, "admin");
              }
               else if (strncmp(con->uri, "/printers", 9) == 0)
              {
@@ -1552,8 +1565,6 @@ ReadClient(client_t *con)         /* I - Client to read from */
 
             SetStringf(&con->filename, "%s/%08x", RequestRoot, request_id ++);
            con->file = open(con->filename, O_WRONLY | O_CREAT | O_TRUNC, 0640);
-           fchmod(con->file, 0640);
-           fchown(con->file, RunUser, Group);
 
             LogMessage(L_DEBUG2, "ReadClient: %d REQUEST %s=%d", con->http.fd,
                       con->filename, con->file);
@@ -1563,6 +1574,10 @@ ReadClient(client_t *con)                /* I - Client to read from */
              if (!SendError(con, HTTP_REQUEST_TOO_LARGE))
                return (CloseClient(con));
            }
+
+           fchmod(con->file, 0640);
+           fchown(con->file, RunUser, Group);
+           fcntl(con->file, F_SETFD, fcntl(con->file, F_GETFD) | FD_CLOEXEC);
            break;
 
        case HTTP_DELETE :
@@ -1795,8 +1810,6 @@ ReadClient(client_t *con)         /* I - Client to read from */
 
           SetStringf(&con->filename, "%s/%08x", RequestRoot, request_id ++);
          con->file = open(con->filename, O_WRONLY | O_CREAT | O_TRUNC, 0640);
-         fchmod(con->file, 0640);
-         fchown(con->file, RunUser, Group);
 
           LogMessage(L_DEBUG2, "ReadClient: %d REQUEST %s=%d", con->http.fd,
                     con->filename, con->file);
@@ -1806,6 +1819,10 @@ ReadClient(client_t *con)                /* I - Client to read from */
            if (!SendError(con, HTTP_REQUEST_TOO_LARGE))
              return (CloseClient(con));
          }
+
+         fchmod(con->file, 0640);
+         fchown(con->file, RunUser, Group);
+          fcntl(con->file, F_SETFD, fcntl(con->file, F_GETFD) | FD_CLOEXEC);
        }
 
        if (con->http.state != HTTP_POST_SEND)
@@ -1925,6 +1942,16 @@ SendCommand(client_t      *con,
   else
     fd = open("/dev/null", O_RDONLY);
 
+  if (fd < 0)
+  {
+    LogMessage(L_ERROR, "SendCommand: %d Unable to open \"%s\" for reading: %s",
+               con->http.fd, con->filename ? con->filename : "/dev/null",
+              strerror(errno));
+    return (0);
+  }
+
+  fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+
   con->pipe_pid = pipe_command(con, fd, &(con->file), command, options);
 
   close(fd);
@@ -2106,8 +2133,9 @@ SendHeader(client_t    *con,      /* I - Client to send to */
     return (0);
   if (httpPrintf(HTTP(con), "Date: %s\r\n", httpGetDateString(time(NULL))) < 0)
     return (0);
-  if (httpPrintf(HTTP(con), "Server: CUPS/1.1\r\n") < 0)
-    return (0);
+  if (ServerHeader)
+    if (httpPrintf(HTTP(con), "Server: %s\r\n", ServerHeader) < 0)
+      return (0);
   if (con->http.keep_alive && con->http.version >= HTTP_1_0)
   {
     if (httpPrintf(HTTP(con), "Connection: Keep-Alive\r\n") < 0)
@@ -2134,7 +2162,7 @@ SendHeader(client_t    *con,      /* I - Client to send to */
     }
     else
     {
-      if (httpPrintf(HTTP(con), "WWW-Authenticate: Digest realm=\"CUPS\" "
+      if (httpPrintf(HTTP(con), "WWW-Authenticate: Digest realm=\"CUPS\", "
                                 "nonce=\"%s\"\r\n", con->http.hostname) < 0)
        return (0);
     }
@@ -2165,129 +2193,23 @@ SendHeader(client_t    *con,   /* I - Client to send to */
 void
 UpdateCGI(void)
 {
-  int          bytes;          /* Number of bytes read */
-  char         *lineptr,       /* Pointer to end of line in buffer */
-               *message;       /* Pointer to message text */
-  int          loglevel;       /* Log level for message */
-  static int   bufused = 0;    /* Number of bytes used in buffer */
-  static char  buffer[1024];   /* Status buffer */
-
-
-  if ((bytes = read(CGIPipes[0], buffer + bufused,
-                    sizeof(buffer) - bufused - 1)) > 0)
-  {
-    bufused += bytes;
-    buffer[bufused] = '\0';
-    lineptr = strchr(buffer, '\n');
-  }
-  else if (bytes < 0 && errno == EINTR)
-    return;
-  else
-  {
-    lineptr    = buffer + bufused;
-    lineptr[1] = 0;
-  }
-
-  if (bytes == 0 && bufused == 0)
-    lineptr = NULL;
-
-  while (lineptr != NULL)
-  {
-   /*
-    * Terminate each line and process it...
-    */
-
-    *lineptr++ = '\0';
-
-   /*
-    * Figure out the logging level...
-    */
-
-    if (strncmp(buffer, "EMERG:", 6) == 0)
-    {
-      loglevel = L_EMERG;
-      message  = buffer + 6;
-    }
-    else if (strncmp(buffer, "ALERT:", 6) == 0)
-    {
-      loglevel = L_ALERT;
-      message  = buffer + 6;
-    }
-    else if (strncmp(buffer, "CRIT:", 5) == 0)
-    {
-      loglevel = L_CRIT;
-      message  = buffer + 5;
-    }
-    else if (strncmp(buffer, "ERROR:", 6) == 0)
-    {
-      loglevel = L_ERROR;
-      message  = buffer + 6;
-    }
-    else if (strncmp(buffer, "WARNING:", 8) == 0)
-    {
-      loglevel = L_WARN;
-      message  = buffer + 8;
-    }
-    else if (strncmp(buffer, "NOTICE:", 6) == 0)
-    {
-      loglevel = L_NOTICE;
-      message  = buffer + 6;
-    }
-    else if (strncmp(buffer, "INFO:", 5) == 0)
-    {
-      loglevel = L_INFO;
-      message  = buffer + 5;
-    }
-    else if (strncmp(buffer, "DEBUG:", 6) == 0)
-    {
-      loglevel = L_DEBUG;
-      message  = buffer + 6;
-    }
-    else if (strncmp(buffer, "DEBUG2:", 7) == 0)
-    {
-      loglevel = L_DEBUG2;
-      message  = buffer + 7;
-    }
-    else if (strncmp(buffer, "PAGE:", 5) == 0)
-    {
-      loglevel = L_PAGE;
-      message  = buffer + 5;
-    }
-    else
-    {
-      loglevel = L_DEBUG;
-      message  = buffer;
-    }
-
-   /*
-    * Skip leading whitespace in the message...
-    */
-
-    while (isspace(*message))
-      message ++;
-
-    LogMessage(loglevel, "[CGI] %s", message);
-
-   /*
-    * Copy over the buffer data we've used up...
-    */
+  char         *ptr,                   /* Pointer to end of line in buffer */
+               message[1024];          /* Pointer to message text */
+  int          loglevel;               /* Log level for message */
 
-    strcpy(buffer, lineptr);
-    bufused -= lineptr - buffer;
 
-    if (bufused < 0)
-      bufused = 0;
-
-    lineptr = strchr(buffer, '\n');
-  }
+  while ((ptr = cupsdStatBufUpdate(CGIStatusBuffer, &loglevel,
+                                   message, sizeof(message))) != NULL)
+    if (!strchr(CGIStatusBuffer->buffer, '\n'))
+      break;
 
-  if (bytes <= 0)
+  if (ptr == NULL)
   {
    /*
     * Fatal error on pipe - should never happen!
     */
 
-    LogMessage(L_ERROR, "UpdateCGI: error reading from CGI error pipe - %s",
+    LogMessage(L_CRIT, "UpdateCGI: error reading from CGI error pipe - %s",
                strerror(errno));
   }
 }
@@ -2424,11 +2346,11 @@ WriteClient(client_t *con)              /* I - Client connection */
                    con->file);
        FD_CLR(con->file, InputSet);
       }
-  
+
       if (con->pipe_pid)
        kill(con->pipe_pid, SIGTERM);
 
-      LogMessage(L_DEBUG2, "WriteClient() %d Closing data file %d.",
+      LogMessage(L_DEBUG2, "WriteClient: %d Closing data file %d.",
                  con->http.fd, con->file);
 
       close(con->file);
@@ -2438,7 +2360,7 @@ WriteClient(client_t *con)                /* I - Client connection */
 
     if (con->filename)
     {
-      LogMessage(L_DEBUG2, "WriteClient() %d Removing temp file %s",
+      LogMessage(L_DEBUG2, "WriteClient: %d Removing temp file %s",
                  con->http.fd, con->filename);
       unlink(con->filename);
       ClearString(&con->filename);
@@ -2599,13 +2521,13 @@ decode_auth(client_t *con)              /* I - Client to decode to */
     * Get the username and password from the Digest attributes...
     */
 
-    if (httpGetSubField(&(con->http), HTTP_FIELD_WWW_AUTHENTICATE, "username",
+    if (httpGetSubField(&(con->http), HTTP_FIELD_AUTHORIZATION, "username",
                         value))
       strlcpy(con->username, value, sizeof(con->username));
 
-    if (httpGetSubField(&(con->http), HTTP_FIELD_WWW_AUTHENTICATE, "response",
+    if (httpGetSubField(&(con->http), HTTP_FIELD_AUTHORIZATION, "response",
                         value))
-      strlcpy(con->password, value, sizeof(con->password) - 1);
+      strlcpy(con->password, value, sizeof(con->password));
   }
 
   LogMessage(L_DEBUG2, "decode_auth: %d username=\"%s\"",
@@ -2613,6 +2535,10 @@ decode_auth(client_t *con)               /* I - Client to decode to */
 }
 
 
+/*
+ * 'get_file()' - Get a filename and state info.
+ */
+
 static char *                          /* O  - Real filename */
 get_file(client_t    *con,             /* I  - Client connection */
          struct stat *filestats,       /* O  - File information */
@@ -2886,6 +2812,41 @@ install_conf_file(client_t *con) /* I - Connection */
 }
 
 
+/*
+ * 'is_path_absolute()' - Is a path absolute and free of relative elements (i.e. "..").
+ */
+
+static int                             /* O - 0 if relative, 1 if absolute */
+is_path_absolute(const char *path)     /* I - Input path */
+{
+ /*
+  * Check for a leading slash...
+  */
+
+  if (path[0] != '/')
+    return (0);
+
+ /*
+  * Check for "/.." in the path...
+  */
+
+  while ((path = strstr(path, "/..")) != NULL)
+  {
+    if (!path[3] || path[3] == '/')
+      return (0);
+
+    path ++;
+  }
+
+ /*
+  * If we haven't found any relative paths, return 1 indicating an
+  * absolute path...
+  */
+
+  return (1);
+}
+
+
 /*
  * 'pipe_command()' - Pipe the output of a command to the remote client.
  */
@@ -2901,7 +2862,6 @@ pipe_command(client_t *con,               /* I - Client connection */
   int          pid;                    /* Process ID */
   char         *commptr;               /* Command string pointer */
   char         *uriptr;                /* URI string pointer */
-  int          fd;                     /* Looping var */
   int          fds[2];                 /* Pipe FDs */
   int          argc;                   /* Number of arguments */
   int          envc;                   /* Number of environment variables */
@@ -2921,7 +2881,7 @@ pipe_command(client_t *con,               /* I - Client connection */
                dyld_library_path[1024],/* DYLD_LIBRARY_PATH environment variable */
                shlib_path[1024],       /* SHLIB_PATH environment variable */
                nlspath[1024],          /* NLSPATH environment variable */
-               query_string[10240],    /* QUERY_STRING env variable */
+               *query_string,          /* QUERY_STRING env variable */
                remote_addr[1024],      /* REMOTE_ADDR environment variable */
                remote_host[1024],      /* REMOTE_HOST environment variable */
                remote_user[1024],      /* REMOTE_USER environment variable */
@@ -2984,37 +2944,79 @@ pipe_command(client_t *con,             /* I - Client connection */
 
 
  /*
-  * Copy the command string...
+  * Parse a copy of the options string, which is of the form:
+  *
+  *     name argument+argument+argument
+  *     name?argument+argument+argument
+  *     name param=value&param=value
+  *     name?param=value&param=value
+  *
+  * If the string contains an "=" character after the initial name,
+  * then we treat it as a HTTP GET form request and make a copy of
+  * the remaining string for the environment variable.
+  *
+  * The string is always parsed out as command-line arguments, to
+  * be consistent with Apache...
   */
 
-  strlcpy(argbuf, options, sizeof(argbuf));
+  LogMessage(L_DEBUG2, "pipe_command: command=\"%s\", options=\"%s\"",
+             command, options);
 
- /*
-  * Parse the string; arguments can be separated by + and are terminated
-  * by ?...
-  */
+  strlcpy(argbuf, options, sizeof(argbuf));
 
-  argv[0] = argbuf;
+  argv[0]      = argbuf;
+  query_string = NULL;
 
   for (commptr = argbuf, argc = 1; *commptr != '\0' && argc < 99; commptr ++)
-    if (*commptr == ' ' || *commptr == '+')
+  {
+   /*
+    * Break arguments whenever we see a + or space...
+    */
+
+    if (*commptr == ' ' || *commptr == '+' || (*commptr == '?' && argc == 1))
     {
+     /*
+      * Terminate the current string and skip trailing whitespace...
+      */
+
       *commptr++ = '\0';
 
       while (*commptr == ' ')
         commptr ++;
 
-      if (*commptr != '\0')
+     /*
+      * If we don't have a blank string, save it as another argument...
+      */
+
+      if (*commptr)
       {
         argv[argc] = commptr;
        argc ++;
       }
+      else
+        break;
+
+     /*
+      * If we see an "=" in the remaining string, make a copy of it since
+      * it will be query data...
+      */
+
+      if (argc == 2 && strchr(commptr, '=') && con->operation == HTTP_GET)
+       SetStringf(&query_string, "QUERY_STRING=%s", commptr);
+
+     /*
+      * Don't skip the first non-blank character...
+      */
 
       commptr --;
     }
     else if (*commptr == '%' && isxdigit(commptr[1] & 255) &&
              isxdigit(commptr[2] & 255))
     {
+     /*
+      * Convert the %xx notation to the individual character.
+      */
+
       if (commptr[1] >= '0' && commptr[1] <= '9')
         *commptr = (commptr[1] - '0') << 4;
       else
@@ -3026,9 +3028,15 @@ pipe_command(client_t *con,              /* I - Client connection */
         *commptr |= tolower(commptr[2]) - 'a' + 10;
 
       cups_strcpy(commptr + 1, commptr + 3);
+
+     /*
+      * Check for a %00 and break if that is the case...
+      */
+
+      if (!*commptr)
+        break;
     }
-    else if (*commptr == '?')
-      break;
+  }
 
   argv[argc] = NULL;
 
@@ -3044,7 +3052,7 @@ pipe_command(client_t *con,               /* I - Client connection */
              locale_encodings[con->language->encoding]);
   else
     strcpy(lang, "LANG=C");
-  
+
   sprintf(ipp_port, "IPP_PORT=%d", LocalPort);
 #ifdef AF_INET6
   if (con->http.hostaddr.addr.sa_family == AF_INET6)
@@ -3165,15 +3173,12 @@ pipe_command(client_t *con,             /* I - Client connection */
       LogMessage(L_DEBUG2, "argv[%d] = \"%s\"", i, argv[i]);
     envp[envc ++] = "REQUEST_METHOD=GET";
 
-    if (*commptr)
+    if (query_string)
     {
      /*
       * Add GET form variables after ?...
       */
 
-      *commptr++ = '\0';
-
-      snprintf(query_string, sizeof(query_string), "QUERY_STRING=%s", commptr);
       envp[envc ++] = query_string;
     }
   }
@@ -3206,17 +3211,19 @@ pipe_command(client_t *con,             /* I - Client connection */
   if (LogLevel == L_DEBUG2)
   {
     for (i = 0; i < argc; i ++)
-      LogMessage(L_DEBUG2, "argv[%d] = \"%s\"", i, argv[i]);
+      LogMessage(L_DEBUG2, "pipe_command: argv[%d] = \"%s\"", i, argv[i]);
     for (i = 0; i < envc; i ++)
-      LogMessage(L_DEBUG2, "envp[%d] = \"%s\"", i, envp[i]);
+      LogMessage(L_DEBUG2, "pipe_command: envp[%d] = \"%s\"", i, envp[i]);
   }
 
  /*
   * Create a pipe for the output...
   */
 
-  if (pipe(fds))
+  if (cupsdOpenPipe(fds))
   {
+    ClearString(&query_string);
+
     LogMessage(L_ERROR, "Unable to create pipes for CGI %s - %s",
                argv[0], strerror(errno));
     return (0);
@@ -3280,13 +3287,6 @@ pipe_command(client_t *con,              /* I - Client connection */
     close(2);
     dup(CGIPipes[1]);
 
-   /*
-    * Close extra file descriptors...
-    */
-
-    for (fd = 3; fd < MaxFDs; fd ++)
-      close(fd);
-
    /*
     * Change umask to restrict permissions on created files...
     */
@@ -3332,8 +3332,7 @@ pipe_command(client_t *con,               /* I - Client connection */
     LogMessage(L_ERROR, "Unable to fork for CGI %s - %s", argv[0],
                strerror(errno));
 
-    close(fds[0]);
-    close(fds[1]);
+    cupsdClosePipe(fds);
     pid = 0;
   }
   else
@@ -3352,6 +3351,8 @@ pipe_command(client_t *con,               /* I - Client connection */
 
   ReleaseSignals();
 
+  ClearString(&query_string);
+
   return (pid);
 }
 
@@ -3405,5 +3406,5 @@ CDSAWriteFunc(SSLConnectionRef connection,        /* I  - SSL/TLS connection */
 
 
 /*
- * End of "$Id: client.c,v 1.91.2.82 2004/06/17 14:45:12 mike Exp $".
+ * End of "$Id$".
  */