/*
- * "$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
* 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
*
* 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.
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);
return;
}
-
+
/*
* Get the hostname or format the IP address as needed...
*/
close(con->http.fd);
FD_CLR(con->http.fd, InputSet);
FD_CLR(con->http.fd, OutputSet);
- con->http.fd = 0;
+ con->http.fd = -1;
}
}
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!
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);
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)
{
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))
{
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))
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)
{
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);
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 :
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);
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)
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);
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)
}
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);
}
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));
}
}
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);
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);
* 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\"",
}
+/*
+ * '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 */
}
+/*
+ * '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.
*/
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 */
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 */
/*
- * 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¶m=value
+ * name?param=value¶m=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
*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;
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)
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;
}
}
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);
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...
*/
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
ReleaseSignals();
+ ClearString(&query_string);
+
return (pid);
}
/*
- * End of "$Id: client.c,v 1.91.2.82 2004/06/17 14:45:12 mike Exp $".
+ * End of "$Id$".
*/