X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=scheduler%2Fclient.c;h=de825609becaf707cdd59df3c2d563d34c4f766e;hb=b2e108950fe59ee63a1f6d2381d67d3f06999cab;hp=6b9d4addd537226d708714e2444a8bb887970a21;hpb=c7a9d594ca547ad1a46823f0117d1e6c1e106cbf;p=thirdparty%2Fcups.git diff --git a/scheduler/client.c b/scheduler/client.c index 6b9d4addd..de825609b 100644 --- a/scheduler/client.c +++ b/scheduler/client.c @@ -1,9 +1,9 @@ /* - * "$Id: client.c,v 1.91.2.20 2002/09/15 22:55:15 mike Exp $" + * "$Id$" * * Client routines for the Common UNIX Printing System (CUPS) scheduler. * - * Copyright 1997-2002 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 * @@ -27,34 +27,32 @@ * CloseAllClients() - Close all remote clients immediately. * CloseClient() - Close a remote client. * EncryptClient() - Enable encryption for the client... + * IsCGI() - Is the resource a CGI script/program? * ReadClient() - Read data from a client. * SendCommand() - Send output from a command via HTTP. * SendError() - Send an error message via HTTP. * SendFile() - Send a file via HTTP. * SendHeader() - Send an HTTP request. - * ShutdownClient() - Shutdown the receiving end of a connection. + * UpdateCGI() - Read status messages from CGI scripts and programs. * WriteClient() - Write data to a client as needed. * check_if_modified() - Decode an "If-Modified-Since" line. * 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. */ /* * Include necessary headers... */ +#include #include "cupsd.h" - #include -#ifdef HAVE_LIBSSL -# include -# include -# include -#endif /* HAVE_LIBSSL */ - /* * Local functions... @@ -63,11 +61,20 @@ static int check_if_modified(client_t *con, struct stat *filestats); static void decode_auth(client_t *con); -static char *get_file(client_t *con, struct stat *filestats); +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); +#ifdef HAVE_CDSASSL +static OSStatus CDSAReadFunc(SSLConnectionRef connection, void *data, + size_t *dataLength); +static OSStatus CDSAWriteFunc(SSLConnectionRef connection, + const void *data, size_t *dataLength); +#endif /* HAVE_CDSASSL */ + /* * 'AcceptClient()' - Accept a new client. @@ -77,14 +84,17 @@ void AcceptClient(listener_t *lis) /* I - Listener socket */ { int i; /* Looping var */ + int count; /* Count of connections on a host */ int val; /* Parameter value */ client_t *con; /* New client pointer */ const struct hostent *host; /* Host entry for address */ char *hostname;/* Hostname for address */ http_addr_t temp; /* Temporary address variable */ + static time_t last_dos = 0; + /* Time of last DoS attack */ - LogMessage(L_DEBUG2, "AcceptClient(%p) %d NumClients = %d", + LogMessage(L_DEBUG2, "AcceptClient(lis=%p) %d NumClients = %d", lis, lis->fd, NumClients); /* @@ -102,6 +112,7 @@ AcceptClient(listener_t *lis) /* I - Listener socket */ memset(con, 0, sizeof(client_t)); con->http.activity = time(NULL); + con->file = -1; /* * Accept the client and get the remote address... @@ -124,6 +135,41 @@ AcceptClient(listener_t *lis) /* I - Listener socket */ #endif /* AF_INET6 */ con->http.hostaddr.ipv4.sin_port = lis->address.ipv4.sin_port; + /* + * Check the number of clients on the same address... + */ + + for (i = 0, count = 0; i < NumClients; i ++) + if (memcmp(&(Clients[i].http.hostaddr), &(con->http.hostaddr), + sizeof(con->http.hostaddr)) == 0) + { + count ++; + if (count >= MaxClientsPerHost) + break; + } + + if (count >= MaxClientsPerHost) + { + if ((time(NULL) - last_dos) >= 60) + { + last_dos = time(NULL); + LogMessage(L_WARN, "Possible DoS attack - more than %d clients connecting from %s!", + MaxClientsPerHost, Clients[i].http.hostname); + } + +#ifdef WIN32 + closesocket(con->http.fd); +#else + close(con->http.fd); +#endif /* WIN32 */ + + return; + } + + /* + * Get the hostname or format the IP address as needed... + */ + if (HostNameLookups) hostname = httpAddrLookup(&(con->http.hostaddr), con->http.hostname, sizeof(con->http.hostname)); @@ -252,14 +298,18 @@ AcceptClient(listener_t *lis) /* I - Listener socket */ setsockopt(con->http.fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)); /* - * Add the socket to the select() input mask. + * Close this file on all execs... */ fcntl(con->http.fd, F_SETFD, fcntl(con->http.fd, F_GETFD) | FD_CLOEXEC); + /* + * Add the socket to the select() input mask. + */ + LogMessage(L_DEBUG2, "AcceptClient: Adding fd %d to InputSet...", con->http.fd); - FD_SET(con->http.fd, &InputSet); + FD_SET(con->http.fd, InputSet); NumClients ++; @@ -270,7 +320,7 @@ AcceptClient(listener_t *lis) /* I - Listener socket */ if (NumClients == MaxClients) PauseListening(); -#ifdef HAVE_LIBSSL +#ifdef HAVE_SSL /* * See if we are connecting on a secure port... */ @@ -285,7 +335,7 @@ AcceptClient(listener_t *lis) /* I - Listener socket */ EncryptClient(con); } -#endif /* HAVE_LIBSSL */ +#endif /* HAVE_SSL */ } @@ -305,26 +355,36 @@ CloseAllClients(void) * 'CloseClient()' - Close a remote client. */ -void +int /* O - 1 if partial close, 0 if fully closed */ CloseClient(client_t *con) /* I - Client to close */ { - int status; /* Exit status of pipe command */ -#ifdef HAVE_LIBSSL + int partial; /* Do partial close for SSL? */ +#if defined(HAVE_LIBSSL) SSL_CTX *context; /* Context for encryption */ SSL *conn; /* Connection for encryption */ unsigned long error; /* Error code */ -#endif /* HAVE_LIBSSL */ +#elif defined(HAVE_GNUTLS) + http_tls_t *conn; /* TLS connection information */ + int error; /* Error code */ + gnutls_certificate_server_credentials *credentials; + /* TLS credentials */ +#endif /* HAVE_GNUTLS */ + + LogMessage(L_DEBUG, "CloseClient: %d", con->http.fd); - LogMessage(L_DEBUG, "CloseClient() %d", con->http.fd); + partial = 0; -#ifdef HAVE_LIBSSL +#ifdef HAVE_SSL /* * Shutdown encryption as needed... */ if (con->http.tls) { + partial = 1; + +# ifdef HAVE_LIBSSL conn = (SSL *)(con->http.tls); context = SSL_get_SSL_CTX(conn); @@ -344,87 +404,146 @@ CloseClient(client_t *con) /* I - Client to close */ SSL_CTX_free(context); SSL_free(conn); +# elif defined(HAVE_GNUTLS) + conn = (http_tls_t *)(con->http.tls); + credentials = (gnutls_certificate_server_credentials *)(conn->credentials); + + error = gnutls_bye(conn->session, GNUTLS_SHUT_WR); + switch (error) + { + case GNUTLS_E_SUCCESS: + LogMessage(L_INFO, "CloseClient: SSL shutdown successful!"); + break; + default: + LogMessage(L_ERROR, "CloseClient: %s", gnutls_strerror(error)); + break; + } + + gnutls_deinit(conn->session); + gnutls_certificate_free_credentials(*credentials); + free(credentials); + free(conn); + +# elif defined(HAVE_CDSASSL) + status = SSLClose((SSLContextRef)con->http.tls); + SSLDisposeContext((SSLContextRef)con->http.tls); +# endif /* HAVE_LIBSSL */ + con->http.tls = NULL; } -#endif /* HAVE_LIBSSL */ +#endif /* HAVE_SSL */ /* * Close the socket and clear the file from the input set for select()... */ - if (con->http.fd >= 0) + if (con->http.fd > 0) { - LogMessage(L_DEBUG2, "CloseClient: Removing fd %d from InputSet and OutputSet...", - con->http.fd); - close(con->http.fd); - FD_CLR(con->http.fd, &InputSet); - FD_CLR(con->http.fd, &OutputSet); - con->http.fd = 0; - } + if (partial) + { + /* + * Only do a partial close so that the encrypted client gets everything. + */ - if (con->pipe_pid != 0) - { - LogMessage(L_DEBUG2, "CloseClient: Removing fd %d from InputSet...", - con->file); - FD_CLR(con->file, &InputSet); + LogMessage(L_DEBUG2, "CloseClient: Removing fd %d from OutputSet...", + con->http.fd); + shutdown(con->http.fd, 0); + FD_CLR(con->http.fd, OutputSet); + } + else + { + /* + * Shut the socket down fully... + */ + + LogMessage(L_DEBUG2, "CloseClient: Removing fd %d from InputSet and OutputSet...", + con->http.fd); + close(con->http.fd); + FD_CLR(con->http.fd, InputSet); + FD_CLR(con->http.fd, OutputSet); + con->http.fd = -1; + } } - if (con->file) + if (con->pipe_pid != 0) { /* - * Close the open data file... + * Stop any CGI process... */ - if (con->pipe_pid) + LogMessage(L_DEBUG2, "CloseClient: %d Killing process ID %d...", + con->http.fd, con->pipe_pid); + kill(con->pipe_pid, SIGKILL); + } + + if (con->file >= 0) + { + if (FD_ISSET(con->file, InputSet)) { - kill(con->pipe_pid, SIGKILL); - waitpid(con->pipe_pid, &status, WNOHANG); + LogMessage(L_DEBUG2, "CloseClient: %d Removing fd %d from InputSet...", + con->http.fd, con->file); + FD_CLR(con->file, InputSet); } - LogMessage(L_DEBUG2, "CloseClient() %d Closing data file %d.", - con->http.fd, con->file); - LogMessage(L_DEBUG2, "CloseClient() %d Removing fd %d from InputSet.", + LogMessage(L_DEBUG2, "CloseClient: %d Closing data file %d.", con->http.fd, con->file); - FD_CLR(con->file, &InputSet); close(con->file); - con->file = 0; + con->file = -1; } - if (con->request) + if (!partial) { - ippDelete(con->request); - con->request = NULL; - } + /* + * Free memory... + */ - if (con->response) - { - ippDelete(con->response); - con->response = NULL; - } + if (con->http.input_set) + free(con->http.input_set); - if (con->language) - { - cupsLangFree(con->language); - con->language = NULL; - } + httpClearCookie(HTTP(con)); - /* - * Re-enable new client connections if we are going back under the - * limit... - */ + ClearString(&con->filename); + ClearString(&con->command); + ClearString(&con->options); - if (NumClients == MaxClients) - ResumeListening(); + if (con->request) + { + ippDelete(con->request); + con->request = NULL; + } - /* - * Compact the list of clients as necessary... - */ + if (con->response) + { + ippDelete(con->response); + con->response = NULL; + } + + if (con->language) + { + cupsLangFree(con->language); + con->language = NULL; + } + + /* + * Re-enable new client connections if we are going back under the + * limit... + */ + + if (NumClients == MaxClients) + ResumeListening(); + + /* + * Compact the list of clients as necessary... + */ - NumClients --; + NumClients --; - if (con < (Clients + NumClients)) - memcpy(con, con + 1, (Clients + NumClients - con) * sizeof(client_t)); + if (con < (Clients + NumClients)) + memmove(con, con + 1, (Clients + NumClients - con) * sizeof(client_t)); + } + + return (partial); } @@ -435,7 +554,7 @@ CloseClient(client_t *con) /* I - Client to close */ int /* O - 1 on success, 0 on error */ EncryptClient(client_t *con) /* I - Client to encrypt */ { -#ifdef HAVE_LIBSSL +#if defined HAVE_LIBSSL SSL_CTX *context; /* Context for encryption */ SSL *conn; /* Connection for encryption */ unsigned long error; /* Error code */ @@ -445,15 +564,19 @@ EncryptClient(client_t *con) /* I - Client to encrypt */ * Create the SSL context and accept the connection... */ - context = SSL_CTX_new(SSLv23_method()); - conn = SSL_new(context); + context = SSL_CTX_new(SSLv23_server_method()); + + SSL_CTX_use_PrivateKey_file(context, ServerKey, SSL_FILETYPE_PEM); + SSL_CTX_use_certificate_file(context, ServerCertificate, SSL_FILETYPE_PEM); - SSL_use_PrivateKey_file(conn, ServerKey, SSL_FILETYPE_PEM); - SSL_use_certificate_file(conn, ServerCertificate, SSL_FILETYPE_PEM); + conn = SSL_new(context); SSL_set_fd(conn, con->http.fd); if (SSL_accept(conn) != 1) { + LogMessage(L_ERROR, "EncryptClient: Unable to encrypt connection from %s!", + con->http.hostname); + while ((error = ERR_get_error()) != 0) LogMessage(L_ERROR, "EncryptClient: %s", ERR_error_string(error, NULL)); @@ -462,11 +585,289 @@ EncryptClient(client_t *con) /* I - Client to encrypt */ return (0); } + LogMessage(L_DEBUG, "EncryptClient: %d Connection from %s now encrypted.", + con->http.fd, con->http.hostname); + + con->http.tls = conn; + return (1); + +#elif defined(HAVE_GNUTLS) + http_tls_t *conn; /* TLS session object */ + int error; /* Error code */ + gnutls_certificate_server_credentials *credentials; + /* TLS credentials */ + + /* + * Create the SSL object and perform the SSL handshake... + */ + + conn = (http_tls_t *)malloc(sizeof(gnutls_session)); + + if (conn == NULL) + return (0); + + credentials = (gnutls_certificate_server_credentials *) + malloc(sizeof(gnutls_certificate_server_credentials)); + if (credentials == NULL) + { + LogMessage(L_ERROR, "EncryptClient: Unable to encrypt connection from %s!", + con->http.hostname); + LogMessage(L_ERROR, "EncryptClient: %s", strerror(errno)); + + free(conn); + return (0); + } + + gnutls_certificate_allocate_credentials(credentials); + gnutls_certificate_set_x509_key_file(*credentials, ServerCertificate, + ServerKey, GNUTLS_X509_FMT_PEM); + + gnutls_init(&(conn->session), GNUTLS_SERVER); + gnutls_set_default_priority(conn->session); + gnutls_credentials_set(conn->session, GNUTLS_CRD_CERTIFICATE, *credentials); + gnutls_transport_set_ptr(conn->session, con->http.fd); + + error = gnutls_handshake(conn->session); + + if (error != GNUTLS_E_SUCCESS) + { + LogMessage(L_ERROR, "EncryptClient: Unable to encrypt connection from %s!", + con->http.hostname); + LogMessage(L_ERROR, "EncryptClient: %s", gnutls_strerror(error)); + + gnutls_deinit(conn->session); + gnutls_certificate_free_credentials(*credentials); + free(conn); + free(credentials); + return (0); + } + + LogMessage(L_DEBUG, "EncryptClient: %d Connection from %s now encrypted.", + con->http.fd, con->http.hostname); + + conn->credentials = credentials; + con->http.tls = conn; + return (1); + +#elif defined(HAVE_CDSASSL) + OSStatus error; /* Error info */ + SSLContextRef conn; /* New connection */ + SSLProtocol tryVersion; /* Protocol version */ + const char *hostName; /* Local hostname */ + int allowExpired; /* Allow expired certificates? */ + int allowAnyRoot; /* Allow any root certificate? */ + SSLProtocol *negVersion; /* Negotiated protocol version */ + SSLCipherSuite *negCipher; /* Negotiated cypher */ + CFArrayRef *peerCerts; /* Certificates */ + + + conn = NULL; + error = SSLNewContext(true, &conn); + allowExpired = 1; + allowAnyRoot = 1; + + if (!error) + error = SSLSetIOFuncs(conn, CDSAReadFunc, CDSAWriteFunc); + + if (!error) + error = SSLSetProtocolVersion(conn, kSSLProtocol3); + + if (!error) + error = SSLSetConnection(conn, (SSLConnectionRef)con->http.fd); + + if (!error) + { + hostName = ServerName; /* MRS: ??? */ + error = SSLSetPeerDomainName(conn, hostName, strlen(hostName) + 1); + } + + /* have to do these options befor setting server certs */ + if (!error && allowExpired) + error = SSLSetAllowsExpiredCerts(conn, true); + + if (!error && allowAnyRoot) + error = SSLSetAllowsAnyRoot(conn, true); + + if (!error && ServerCertificatesArray != NULL) + error = SSLSetCertificate(conn, ServerCertificatesArray); + + /* + * Perform SSL/TLS handshake + */ + + do + { + error = SSLHandshake(conn); + } + while (error == errSSLWouldBlock); + + if (error) + { + LogMessage(L_ERROR, "EncryptClient: Unable to encrypt connection from %s!", + con->http.hostname); + + LogMessage(L_ERROR, "EncryptClient: CDSA error code is %d", error); + + con->http.error = error; + con->http.status = HTTP_ERROR; + + if (conn != NULL) + SSLDisposeContext(conn); + + return (0); + } + + LogMessage(L_DEBUG, "EncryptClient: %d Connection from %s now encrypted.", + con->http.fd, con->http.hostname); + con->http.tls = conn; return (1); + #else return (0); -#endif /* HAVE_LIBSSL */ +#endif /* HAVE_GNUTLS */ +} + + +/* + * 'IsCGI()' - Is the resource a CGI script/program? + */ + +int /* O - 1 = CGI, 0 = file */ +IsCGI(client_t *con, /* I - Client connection */ + const char *filename, /* I - Real filename */ + struct stat *filestats, /* I - File information */ + mime_type_t *type) /* I - MIME type */ +{ + const char *options; /* Options on URL */ + + + LogMessage(L_DEBUG2, "IsCGI(con=%p, filename=\"%s\", filestats=%p, type=%s/%s)\n", + con, filename, filestats, type ? type->super : "unknown", + type ? type->type : "unknown"); + + /* + * Get the options, if any... + */ + + if ((options = strchr(con->uri, '?')) != NULL) + options ++; + + /* + * Check for known types... + */ + + if (!type || strcasecmp(type->super, "application")) + { + LogMessage(L_DEBUG2, "IsCGI: Returning 0..."); + return (0); + } + + if (!strcasecmp(type->type, "x-httpd-cgi") && + (filestats->st_mode & 0111)) + { + /* + * "application/x-httpd-cgi" is a CGI script. + */ + + SetString(&con->command, filename); + + filename = strrchr(filename, '/') + 1; /* Filename always absolute */ + + if (options) + SetStringf(&con->options, "%s %s", filename, options); + else + SetStringf(&con->options, "%s", filename); + + LogMessage(L_DEBUG2, "IsCGI: Returning 1 with command=\"%s\" and options=\"%s\"", + con->command, con->options); + + return (1); + } +#ifdef HAVE_JAVA + else if (!strcasecmp(type->type, "x-httpd-java")) + { + /* + * "application/x-httpd-java" is a Java servlet. + */ + + SetString(&con->command, CUPS_JAVA); + + if (options) + SetStringf(&con->options, "java %s %s", filename, options); + else + SetStringf(&con->options, "java %s", filename); + + LogMessage(L_DEBUG2, "IsCGI: Returning 1 with command=\"%s\" and options=\"%s\"", + con->command, con->options); + + return (1); + } +#endif /* HAVE_JAVA */ +#ifdef HAVE_PERL + else if (!strcasecmp(type->type, "x-httpd-perl")) + { + /* + * "application/x-httpd-perl" is a Perl page. + */ + + SetString(&con->command, CUPS_PERL); + + if (options) + SetStringf(&con->options, "perl %s %s", filename, options); + else + SetStringf(&con->options, "perl %s", filename); + + LogMessage(L_DEBUG2, "IsCGI: Returning 1 with command=\"%s\" and options=\"%s\"", + con->command, con->options); + + return (1); + } +#endif /* HAVE_PERL */ +#ifdef HAVE_PHP + else if (!strcasecmp(type->type, "x-httpd-php")) + { + /* + * "application/x-httpd-php" is a PHP page. + */ + + SetString(&con->command, CUPS_PHP); + + if (options) + SetStringf(&con->options, "php %s %s", filename, options); + else + SetStringf(&con->options, "php %s", filename); + + LogMessage(L_DEBUG2, "IsCGI: Returning 1 with command=\"%s\" and options=\"%s\"", + con->command, con->options); + + return (1); + } +#endif /* HAVE_PHP */ +#ifdef HAVE_PYTHON + else if (!strcasecmp(type->type, "x-httpd-python")) + { + /* + * "application/x-httpd-python" is a Python page. + */ + + SetString(&con->command, CUPS_PYTHON); + + if (options) + SetStringf(&con->options, "python %s %s", filename, options); + else + SetStringf(&con->options, "python %s", filename); + + LogMessage(L_DEBUG2, "IsCGI: Returning 1 with command=\"%s\" and options=\"%s\"", + con->command, con->options); + + return (1); + } +#endif /* HAVE_PYTHON */ + + LogMessage(L_DEBUG2, "IsCGI: Returning 0..."); + + return (0); } @@ -474,26 +875,38 @@ EncryptClient(client_t *con) /* I - Client to encrypt */ * 'ReadClient()' - Read data from a client. */ -int /* O - 1 on success, 0 on error */ -ReadClient(client_t *con) /* I - Client to read from */ +int /* O - 1 on success, 0 on error */ +ReadClient(client_t *con) /* I - Client to read from */ { - char line[32768], /* Line from client... */ - operation[64], /* Operation code from socket */ - version[64]; /* HTTP version number string */ - int major, minor; /* HTTP version numbers */ - http_status_t status; /* Transfer status */ - ipp_state_t ipp_state; /* State of IPP transfer */ - int bytes; /* Number of bytes to POST */ - char *filename; /* Name of file for GET/HEAD */ - struct stat filestats; /* File information */ - mime_type_t *type; /* MIME type of file */ - printer_t *p; /* Printer */ - location_t *best; /* Best match for authentication */ - static unsigned request_id = 0;/* Request ID for temp files */ + char line[32768], /* Line from client... */ + operation[64], /* Operation code from socket */ + version[64], /* HTTP version number string */ + locale[64], /* Locale */ + *ptr; /* Pointer into strings */ + int major, minor; /* HTTP version numbers */ + http_status_t status; /* Transfer status */ + ipp_state_t ipp_state; /* State of IPP transfer */ + int bytes; /* Number of bytes to POST */ + char *filename; /* Name of file for GET/HEAD */ + char buf[1024]; /* Buffer for real filename */ + struct stat filestats; /* File information */ + mime_type_t *type; /* MIME type of file */ + printer_t *p; /* Printer */ + location_t *best; /* Best match for authentication */ + static unsigned request_id = 0; /* Request ID for temp files */ status = HTTP_CONTINUE; + LogMessage(L_DEBUG2, "ReadClient: %d, used=%d, file=%d", con->http.fd, + con->http.used, con->file); + + if (con->http.error) + { + LogMessage(L_DEBUG2, "ReadClient: http error seen..."); + return (CloseClient(con)); + } + switch (con->http.state) { case HTTP_WAITING : @@ -503,8 +916,8 @@ ReadClient(client_t *con) /* I - Client to read from */ if (httpGets(line, sizeof(line) - 1, HTTP(con)) == NULL) { - CloseClient(con); - return (0); + LogMessage(L_DEBUG2, "ReadClient: httpGets returned EOF..."); + return (CloseClient(con)); } /* @@ -527,13 +940,16 @@ ReadClient(client_t *con) /* I - Client to read from */ con->http.data_remaining = 0; con->operation = HTTP_WAITING; con->bytes = 0; - con->file = 0; + con->file = -1; + con->file_ready = 0; con->pipe_pid = 0; - con->command[0] = '\0'; con->username[0] = '\0'; con->password[0] = '\0'; con->uri[0] = '\0'; + ClearString(&con->command); + ClearString(&con->options); + if (con->language != NULL) { cupsLangFree(con->language); @@ -547,18 +963,20 @@ ReadClient(client_t *con) /* I - Client to read from */ switch (sscanf(line, "%63s%1023s%63s", operation, con->uri, version)) { case 1 : + LogMessage(L_ERROR, "Bad request line \"%s\" from %s!", line, + con->http.hostname); SendError(con, HTTP_BAD_REQUEST); - ShutdownClient(con); - return (0); + return (CloseClient(con)); case 2 : con->http.version = HTTP_0_9; break; case 3 : if (sscanf(version, "HTTP/%d.%d", &major, &minor) != 2) { + LogMessage(L_ERROR, "Bad request line \"%s\" from %s!", line, + con->http.hostname); SendError(con, HTTP_BAD_REQUEST); - ShutdownClient(con); - return (0); + return (CloseClient(con)); } if (major < 2) @@ -572,12 +990,56 @@ ReadClient(client_t *con) /* I - Client to read from */ else { SendError(con, HTTP_NOT_SUPPORTED); - ShutdownClient(con); - return (0); + return (CloseClient(con)); } break; } + /* + * Handle full URLs in the request line... + */ + + if (con->uri[0] != '/' && strcmp(con->uri, "*")) + { + char method[HTTP_MAX_URI], /* Method/scheme */ + userpass[HTTP_MAX_URI], /* Username:password */ + hostname[HTTP_MAX_URI], /* Hostname */ + resource[HTTP_MAX_URI]; /* Resource path */ + int port; /* Port number */ + + + /* + * Separate the URI into its components... + */ + + httpSeparate(con->uri, method, userpass, hostname, &port, resource); + + /* + * Only allow URIs with the servername, localhost, or an IP + * address... + */ + + if (strcasecmp(hostname, ServerName) && + strcasecmp(hostname, "localhost") && + !isdigit(hostname[0])) + { + /* + * Nope, we don't do proxies... + */ + + LogMessage(L_ERROR, "Bad URI \"%s\" in request!", con->uri); + SendError(con, HTTP_METHOD_NOT_ALLOWED); + return (CloseClient(con)); + } + + /* + * Copy the resource portion back into the URI; both resource and + * con->uri are HTTP_MAX_URI bytes in size... + */ + + strcpy(con->uri, resource); + } + /* * Process the request... */ @@ -598,15 +1060,15 @@ ReadClient(client_t *con) /* I - Client to read from */ con->http.state = HTTP_HEAD; else { + LogMessage(L_ERROR, "Bad operation \"%s\"!", operation); SendError(con, HTTP_BAD_REQUEST); - ShutdownClient(con); - return (0); + return (CloseClient(con)); } con->start = time(NULL); con->operation = con->http.state; - LogMessage(L_DEBUG, "ReadClient() %d %s %s HTTP/%d.%d", con->http.fd, + LogMessage(L_DEBUG, "ReadClient: %d %s %s HTTP/%d.%d", con->http.fd, operation, con->uri, con->http.version / 100, con->http.version % 100); @@ -628,8 +1090,7 @@ ReadClient(client_t *con) /* I - Client to read from */ if (status != HTTP_OK && status != HTTP_CONTINUE) { SendError(con, HTTP_BAD_REQUEST); - ShutdownClient(con); - return (0); + return (CloseClient(con)); } break; @@ -643,7 +1104,40 @@ ReadClient(client_t *con) /* I - Client to read from */ if (status == HTTP_OK) { - con->language = cupsLangGet(con->http.fields[HTTP_FIELD_ACCEPT_LANGUAGE]); + if (con->http.fields[HTTP_FIELD_ACCEPT_LANGUAGE][0]) + { + /* + * Figure out the locale from the Accept-Language and Content-Type + * fields... + */ + + if ((ptr = strchr(con->http.fields[HTTP_FIELD_ACCEPT_LANGUAGE], ',')) != NULL) + *ptr = '\0'; + + if ((ptr = strchr(con->http.fields[HTTP_FIELD_ACCEPT_LANGUAGE], ';')) != NULL) + *ptr = '\0'; + + if ((ptr = strstr(con->http.fields[HTTP_FIELD_CONTENT_TYPE], "charset=")) != NULL) + { + /* + * Combine language and charset, and trim any extra params in the + * content-type. + */ + + snprintf(locale, sizeof(locale), "%s.%s", + con->http.fields[HTTP_FIELD_ACCEPT_LANGUAGE], ptr + 8); + + if ((ptr = strchr(locale, ',')) != NULL) + *ptr = '\0'; + } + else + snprintf(locale, sizeof(locale), "%s.%s", + con->http.fields[HTTP_FIELD_ACCEPT_LANGUAGE], DefaultCharset); + + con->language = cupsLangGet(locale); + } + else + con->language = cupsLangGet(DefaultLocale); decode_auth(con); @@ -659,10 +1153,7 @@ ReadClient(client_t *con) /* I - Client to read from */ */ if (!SendError(con, HTTP_BAD_REQUEST)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } else if (con->operation == HTTP_OPTIONS) { @@ -674,25 +1165,19 @@ ReadClient(client_t *con) /* I - Client to read from */ best->type != AUTH_NONE) { if (!SendHeader(con, HTTP_UNAUTHORIZED, NULL)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } if (strcasecmp(con->http.fields[HTTP_FIELD_CONNECTION], "Upgrade") == 0 && con->http.tls == NULL) { -#ifdef HAVE_LIBSSL +#ifdef HAVE_SSL /* * Do encryption stuff... */ if (!SendHeader(con, HTTP_SWITCHING_PROTOCOLS, NULL)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); httpPrintf(HTTP(con), "Connection: Upgrade\r\n"); httpPrintf(HTTP(con), "Upgrade: TLS/1.0,HTTP/1.1\r\n"); @@ -702,62 +1187,43 @@ ReadClient(client_t *con) /* I - Client to read from */ EncryptClient(con); #else if (!SendError(con, HTTP_NOT_IMPLEMENTED)) - { - CloseClient(con); - return (0); - } -#endif /* HAVE_LIBSSL */ + return (CloseClient(con)); +#endif /* HAVE_SSL */ } - if (!SendHeader(con, HTTP_OK, NULL)) + if (con->http.expect) { - CloseClient(con); - return (0); + /**** TODO: send expected header ****/ } + if (!SendHeader(con, HTTP_OK, NULL)) + return (CloseClient(con)); + httpPrintf(HTTP(con), "Allow: GET, HEAD, OPTIONS, POST, PUT\r\n"); 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! */ if (!SendError(con, HTTP_FORBIDDEN)) - { - CloseClient(con); - return (0); - } - } - else if (con->uri[0] != '/') - { - /* - * Don't allow proxying (yet)... - */ - - if (!SendError(con, HTTP_METHOD_NOT_ALLOWED)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } else { if (strcasecmp(con->http.fields[HTTP_FIELD_CONNECTION], "Upgrade") == 0 && con->http.tls == NULL) { -#ifdef HAVE_LIBSSL +#ifdef HAVE_SSL /* * Do encryption stuff... */ if (!SendHeader(con, HTTP_SWITCHING_PROTOCOLS, NULL)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); httpPrintf(HTTP(con), "Connection: Upgrade\r\n"); httpPrintf(HTTP(con), "Upgrade: TLS/1.0,HTTP/1.1\r\n"); @@ -765,22 +1231,23 @@ 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)) - { - CloseClient(con); - return (0); - } -#endif /* HAVE_LIBSSL */ + 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); SendError(con, status); - ShutdownClient(con); - return (0); + return (CloseClient(con)); + } + + if (con->http.expect) + { + /**** TODO: send expected header ****/ } switch (con->http.state) @@ -801,10 +1268,7 @@ ReadClient(client_t *con) /* I - Client to read from */ else { if (!SendError(con, HTTP_NOT_FOUND)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); break; } @@ -822,39 +1286,36 @@ ReadClient(client_t *con) /* I - Client to read from */ if (strncmp(con->uri, "/admin", 6) == 0) { - snprintf(con->command, sizeof(con->command), - "%s/cgi-bin/admin.cgi", ServerBin); - con->options = con->uri + 6; + SetStringf(&con->command, "%s/cgi-bin/admin.cgi", ServerBin); + + 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) { - snprintf(con->command, sizeof(con->command), - "%s/cgi-bin/printers.cgi", ServerBin); - con->options = con->uri + 9; + SetStringf(&con->command, "%s/cgi-bin/printers.cgi", ServerBin); + SetString(&con->options, con->uri + 9); } else if (strncmp(con->uri, "/classes", 8) == 0) { - snprintf(con->command, sizeof(con->command), - "%s/cgi-bin/classes.cgi", ServerBin); - con->options = con->uri + 8; + SetStringf(&con->command, "%s/cgi-bin/classes.cgi", ServerBin); + SetString(&con->options, con->uri + 8); } else { - snprintf(con->command, sizeof(con->command), - "%s/cgi-bin/jobs.cgi", ServerBin); - con->options = con->uri + 5; + SetStringf(&con->command, "%s/cgi-bin/jobs.cgi", ServerBin); + SetString(&con->options, con->uri + 5); } - if (con->options[0] == '/') - con->options ++; + if (con->options[0] == '/') + cups_strcpy(con->options, con->options + 1); if (!SendCommand(con, con->command, con->options)) { if (!SendError(con, HTTP_NOT_FOUND)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } else LogRequest(con, HTTP_OK); @@ -872,10 +1333,7 @@ ReadClient(client_t *con) /* I - Client to read from */ */ if (!SendError(con, HTTP_FORBIDDEN)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); break; } @@ -885,35 +1343,51 @@ ReadClient(client_t *con) /* I - Client to read from */ * Serve a file... */ - if ((filename = get_file(con, &filestats)) == NULL) + if ((filename = get_file(con, &filestats, buf, + sizeof(buf))) == NULL) { if (!SendError(con, HTTP_NOT_FOUND)) + return (CloseClient(con)); + + break; + } + + type = mimeFileType(MimeDatabase, filename, NULL); + + if (IsCGI(con, filename, &filestats, type)) + { + /* + * Note: con->command and con->options were set by + * IsCGI()... + */ + + if (!SendCommand(con, con->command, con->options)) { - CloseClient(con); - return (0); - } + if (!SendError(con, HTTP_NOT_FOUND)) + return (CloseClient(con)); + } + else + LogRequest(con, HTTP_OK); + + if (con->http.version <= HTTP_1_0) + con->http.keep_alive = HTTP_KEEPALIVE_OFF; + break; } - else if (!check_if_modified(con, &filestats)) + + if (!check_if_modified(con, &filestats)) { if (!SendError(con, HTTP_NOT_MODIFIED)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } else { - type = mimeFileType(MimeDatabase, filename); if (type == NULL) strcpy(line, "text/plain"); else snprintf(line, sizeof(line), "%s/%s", type->super, type->type); if (!SendFile(con, HTTP_OK, filename, line, &filestats)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } } break; @@ -936,13 +1410,21 @@ ReadClient(client_t *con) /* I - Client to read from */ */ if (!SendError(con, HTTP_REQUEST_TOO_LARGE)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); break; } + else if (atoi(con->http.fields[HTTP_FIELD_CONTENT_LENGTH]) < 0) + { + /* + * Negative content lengths are invalid! + */ + + if (!SendError(con, HTTP_BAD_REQUEST)) + return (CloseClient(con)); + + break; + } /* * See what kind of POST request this is; for IPP requests the @@ -963,42 +1445,64 @@ ReadClient(client_t *con) /* I - Client to read from */ if (strncmp(con->uri, "/admin", 6) == 0) { - snprintf(con->command, sizeof(con->command), - "%s/cgi-bin/admin.cgi", ServerBin); - con->options = con->uri + 6; + SetStringf(&con->command, "%s/cgi-bin/admin.cgi", ServerBin); + + 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) { - snprintf(con->command, sizeof(con->command), - "%s/cgi-bin/printers.cgi", ServerBin); - con->options = con->uri + 9; + SetStringf(&con->command, "%s/cgi-bin/printers.cgi", ServerBin); + SetString(&con->options, con->uri + 9); } else if (strncmp(con->uri, "/classes", 8) == 0) { - snprintf(con->command, sizeof(con->command), - "%s/cgi-bin/classes.cgi", ServerBin); - con->options = con->uri + 8; + SetStringf(&con->command, "%s/cgi-bin/classes.cgi", ServerBin); + SetString(&con->options, con->uri + 8); } else { - snprintf(con->command, sizeof(con->command), - "%s/cgi-bin/jobs.cgi", ServerBin); - con->options = con->uri + 5; + SetStringf(&con->command, "%s/cgi-bin/jobs.cgi", ServerBin); + SetString(&con->options, con->uri + 5); } if (con->options[0] == '/') - con->options ++; + cups_strcpy(con->options, con->options + 1); - LogMessage(L_DEBUG2, "ReadClient() %d command=\"%s\", options = \"%s\"", + LogMessage(L_DEBUG2, "ReadClient: %d command=\"%s\", options = \"%s\"", con->http.fd, con->command, con->options); if (con->http.version <= HTTP_1_0) con->http.keep_alive = HTTP_KEEPALIVE_OFF; } - else if (!SendError(con, HTTP_UNAUTHORIZED)) + else { - CloseClient(con); - return (0); + /* + * POST to a file... + */ + + if ((filename = get_file(con, &filestats, buf, + sizeof(buf))) == NULL) + { + if (!SendError(con, HTTP_NOT_FOUND)) + return (CloseClient(con)); + + break; + } + + type = mimeFileType(MimeDatabase, filename, NULL); + + if (!IsCGI(con, filename, &filestats, type)) + { + /* + * Only POST to CGI's... + */ + + if (!SendError(con, HTTP_UNAUTHORIZED)) + return (CloseClient(con)); + } } break; @@ -1017,10 +1521,7 @@ ReadClient(client_t *con) /* I - Client to read from */ */ if (!SendError(con, HTTP_FORBIDDEN)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); break; } @@ -1042,42 +1543,47 @@ ReadClient(client_t *con) /* I - Client to read from */ */ if (!SendError(con, HTTP_REQUEST_TOO_LARGE)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); break; } + else if (atoi(con->http.fields[HTTP_FIELD_CONTENT_LENGTH]) < 0) + { + /* + * Negative content lengths are invalid! + */ + + if (!SendError(con, HTTP_BAD_REQUEST)) + return (CloseClient(con)); + + break; + } /* * Open a temporary file to hold the request... */ - snprintf(con->filename, sizeof(con->filename), "%s/%08x", - RequestRoot, request_id ++); + 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, User, Group); - LogMessage(L_DEBUG2, "ReadClient() %d REQUEST %s=%d", con->http.fd, + LogMessage(L_DEBUG2, "ReadClient: %d REQUEST %s=%d", con->http.fd, con->filename, con->file); if (con->file < 0) { if (!SendError(con, HTTP_REQUEST_TOO_LARGE)) - { - CloseClient(con); - return (0); - } + 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 : case HTTP_TRACE : SendError(con, HTTP_NOT_IMPLEMENTED); - ShutdownClient(con); - return (0); + return (CloseClient(con)); case HTTP_HEAD : if (strncmp(con->uri, "/printers/", 10) == 0 && @@ -1095,10 +1601,7 @@ ReadClient(client_t *con) /* I - Client to read from */ else { if (!SendError(con, HTTP_NOT_FOUND)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); break; } @@ -1115,16 +1618,10 @@ ReadClient(client_t *con) /* I - Client to read from */ */ if (!SendHeader(con, HTTP_OK, "text/html")) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); - if (httpPrintf(HTTP(con), "\r\n") < 0) - { - CloseClient(con); - return (0); - } + if (httpPrintf(HTTP(con), "\r\n") < 0) + return (CloseClient(con)); LogRequest(con, HTTP_OK); } @@ -1138,30 +1635,22 @@ ReadClient(client_t *con) /* I - Client to read from */ */ if (!SendError(con, HTTP_FORBIDDEN)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); break; } - else if ((filename = get_file(con, &filestats)) == NULL) + else if ((filename = get_file(con, &filestats, buf, + sizeof(buf))) == NULL) { if (!SendHeader(con, HTTP_NOT_FOUND, "text/html")) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); LogRequest(con, HTTP_NOT_FOUND); } else if (!check_if_modified(con, &filestats)) { if (!SendError(con, HTTP_NOT_MODIFIED)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); LogRequest(con, HTTP_NOT_MODIFIED); } @@ -1171,40 +1660,28 @@ ReadClient(client_t *con) /* I - Client to read from */ * Serve a file... */ - type = mimeFileType(MimeDatabase, filename); + type = mimeFileType(MimeDatabase, filename, NULL); if (type == NULL) strcpy(line, "text/plain"); else snprintf(line, sizeof(line), "%s/%s", type->super, type->type); if (!SendHeader(con, HTTP_OK, line)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); if (httpPrintf(HTTP(con), "Last-Modified: %s\r\n", httpGetDateString(filestats.st_mtime)) < 0) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); if (httpPrintf(HTTP(con), "Content-Length: %lu\r\n", (unsigned long)filestats.st_size) < 0) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); LogRequest(con, HTTP_OK); } if (httpPrintf(HTTP(con), "\r\n") < 0) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); con->http.state = HTTP_WAITING; break; @@ -1222,21 +1699,18 @@ ReadClient(client_t *con) /* I - Client to read from */ switch (con->http.state) { case HTTP_PUT_RECV : - LogMessage(L_DEBUG2, "ReadClient() %d con->data_encoding = %s, con->data_remaining = %d, con->file = %d", + LogMessage(L_DEBUG2, "ReadClient: %d con->data_encoding = %s, con->data_remaining = %d, con->file = %d", con->http.fd, con->http.data_encoding == HTTP_ENCODE_CHUNKED ? "chunked" : "length", con->http.data_remaining, con->file); if ((bytes = httpRead(HTTP(con), line, sizeof(line))) < 0) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); else if (bytes > 0) { con->bytes += bytes; - LogMessage(L_DEBUG2, "ReadClient() %d writing %d bytes to %d", + LogMessage(L_DEBUG2, "ReadClient: %d writing %d bytes to %d", con->http.fd, bytes, con->file); if (write(con->file, line, bytes) < bytes) @@ -1248,15 +1722,12 @@ ReadClient(client_t *con) /* I - Client to read from */ con->file); close(con->file); - con->file = 0; + con->file = -1; unlink(con->filename); - con->filename[0] = '\0'; + ClearString(&con->filename); if (!SendError(con, HTTP_REQUEST_TOO_LARGE)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } } @@ -1268,11 +1739,11 @@ ReadClient(client_t *con) /* I - Client to read from */ fstat(con->file, &filestats); - LogMessage(L_DEBUG2, "ReadClient() %d Closing data file %d, size = %d.", + LogMessage(L_DEBUG2, "ReadClient: %d Closing data file %d, size = %d.", con->http.fd, con->file, (int)filestats.st_size); close(con->file); - con->file = 0; + con->file = -1; if (filestats.st_size > MaxRequestSize && MaxRequestSize > 0) @@ -1281,16 +1752,13 @@ ReadClient(client_t *con) /* I - Client to read from */ * Request is too big; remove it and send an error... */ - LogMessage(L_DEBUG2, "ReadClient() %d Removing temp file %s", + LogMessage(L_DEBUG2, "ReadClient: %d Removing temp file %s", con->http.fd, con->filename); unlink(con->filename); - con->filename[0] = '\0'; + ClearString(&con->filename); if (!SendError(con, HTTP_REQUEST_TOO_LARGE)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } /* @@ -1304,15 +1772,12 @@ ReadClient(client_t *con) /* I - Client to read from */ */ if (!SendError(con, status)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } break; case HTTP_POST_RECV : - LogMessage(L_DEBUG2, "ReadClient() %d con->data_encoding = %s, con->data_remaining = %d, con->file = %d", + LogMessage(L_DEBUG2, "ReadClient: %d con->data_encoding = %s, con->data_remaining = %d, con->file = %d", con->http.fd, con->http.data_encoding == HTTP_ENCODE_CHUNKED ? "chunked" : "length", con->http.data_remaining, con->file); @@ -1325,10 +1790,11 @@ ReadClient(client_t *con) /* I - Client to read from */ if ((ipp_state = ippRead(&(con->http), con->request)) == IPP_ERROR) { - LogMessage(L_ERROR, "ReadClient() %d IPP Read Error!", + LogMessage(L_ERROR, "ReadClient: %d IPP Read Error!", con->http.fd); - CloseClient(con); - return (0); + + SendError(con, HTTP_BAD_REQUEST); + return (CloseClient(con)); } else if (ipp_state != IPP_DATA) break; @@ -1336,43 +1802,38 @@ ReadClient(client_t *con) /* I - Client to read from */ con->bytes += ippLength(con->request); } - if (con->file == 0 && con->http.state != HTTP_POST_SEND) + if (con->file < 0 && con->http.state != HTTP_POST_SEND) { /* * Create a file as needed for the request data... */ - snprintf(con->filename, sizeof(con->filename), "%s/%08x", - RequestRoot, request_id ++); + 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, User, Group); - LogMessage(L_DEBUG2, "ReadClient() %d REQUEST %s=%d", con->http.fd, + LogMessage(L_DEBUG2, "ReadClient: %d REQUEST %s=%d", con->http.fd, con->filename, con->file); if (con->file < 0) { if (!SendError(con, HTTP_REQUEST_TOO_LARGE)) - { - CloseClient(con); - return (0); - } + 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) { if ((bytes = httpRead(HTTP(con), line, sizeof(line))) < 0) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); else if (bytes > 0) { con->bytes += bytes; - LogMessage(L_DEBUG2, "ReadClient() %d writing %d bytes to %d", + LogMessage(L_DEBUG2, "ReadClient: %d writing %d bytes to %d", con->http.fd, bytes, con->file); if (write(con->file, line, bytes) < bytes) @@ -1384,35 +1845,31 @@ ReadClient(client_t *con) /* I - Client to read from */ con->file); close(con->file); - con->file = 0; + con->file = -1; unlink(con->filename); - con->filename[0] = '\0'; + ClearString(&con->filename); if (!SendError(con, HTTP_REQUEST_TOO_LARGE)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } } + else if (con->http.state == HTTP_POST_RECV) + return (1); /* ??? */ else if (con->http.state != HTTP_POST_SEND) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } if (con->http.state == HTTP_POST_SEND) { - if (con->file) + if (con->file >= 0) { fstat(con->file, &filestats); - LogMessage(L_DEBUG2, "ReadClient() %d Closing data file %d, size = %d.", + LogMessage(L_DEBUG2, "ReadClient: %d Closing data file %d, size = %d.", con->http.fd, con->file, (int)filestats.st_size); close(con->file); - con->file = 0; + con->file = -1; if (filestats.st_size > MaxRequestSize && MaxRequestSize > 0) @@ -1421,10 +1878,10 @@ ReadClient(client_t *con) /* I - Client to read from */ * Request is too big; remove it and send an error... */ - LogMessage(L_DEBUG2, "ReadClient() %d Removing temp file %s", + LogMessage(L_DEBUG2, "ReadClient: %d Removing temp file %s", con->http.fd, con->filename); unlink(con->filename); - con->filename[0] = '\0'; + ClearString(&con->filename); if (con->request) { @@ -1437,21 +1894,15 @@ ReadClient(client_t *con) /* I - Client to read from */ } if (!SendError(con, HTTP_REQUEST_TOO_LARGE)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } - if (con->command[0]) + if (con->command) { if (!SendCommand(con, con->command, con->options)) { if (!SendError(con, HTTP_NOT_FOUND)) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); } else LogRequest(con, HTTP_OK); @@ -1459,7 +1910,7 @@ ReadClient(client_t *con) /* I - Client to read from */ } if (con->request) - ProcessIPPRequest(con); + return (ProcessIPPRequest(con)); } break; @@ -1468,10 +1919,7 @@ ReadClient(client_t *con) /* I - Client to read from */ } if (!con->http.keep_alive && con->http.state == HTTP_WAITING) - { - CloseClient(con); - return (0); - } + return (CloseClient(con)); else return (1); } @@ -1489,18 +1937,28 @@ SendCommand(client_t *con, int fd; - if (con->filename[0]) + if (con->filename) fd = open(con->filename, O_RDONLY); 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); LogMessage(L_INFO, "Started \"%s\" (pid=%d)", command, con->pipe_pid); - LogMessage(L_DEBUG, "SendCommand() %d file=%d", con->http.fd, con->file); + LogMessage(L_DEBUG, "SendCommand: %d file=%d", con->http.fd, con->file); if (con->pipe_pid == 0) return (0); @@ -1511,8 +1969,8 @@ SendCommand(client_t *con, LogMessage(L_DEBUG2, "SendCommand: Adding fd %d to OutputSet...", con->http.fd); - FD_SET(con->file, &InputSet); - FD_SET(con->http.fd, &OutputSet); + FD_SET(con->file, InputSet); + FD_SET(con->http.fd, OutputSet); if (!SendHeader(con, HTTP_OK, NULL)) return (0); @@ -1525,6 +1983,7 @@ SendCommand(client_t *con, return (0); } + con->file_ready = 0; con->got_fields = 0; con->field_col = 0; @@ -1547,10 +2006,10 @@ SendError(client_t *con, /* I - Connection */ * Put the request in the access_log file... */ - if (con->operation > HTTP_WAITING) - LogRequest(con, code); + LogRequest(con, code); - LogMessage(L_DEBUG, "SendError() %d code=%d", con->http.fd, code); + LogMessage(L_DEBUG, "SendError: %d code=%d (%s)", con->http.fd, code, + httpStatus(code)); /* * To work around bugs in some proxies, don't use Keep-Alive for some @@ -1568,16 +2027,17 @@ SendError(client_t *con, /* I - Connection */ if (!SendHeader(con, code, NULL)) return (0); -#ifdef HAVE_LIBSSL +#ifdef HAVE_SSL if (code == HTTP_UPGRADE_REQUIRED) if (httpPrintf(HTTP(con), "Connection: Upgrade\r\n") < 0) return (0); if (httpPrintf(HTTP(con), "Upgrade: TLS/1.0,HTTP/1.1\r\n") < 0) return (0); -#endif /* HAVE_LIBSSL */ +#endif /* HAVE_SSL */ - if (con->http.version >= HTTP_1_1 && !con->http.keep_alive) + if ((con->http.version >= HTTP_1_1 && !con->http.keep_alive) || + (code >= HTTP_BAD_REQUEST && code != HTTP_UPGRADE_REQUIRED)) { if (httpPrintf(HTTP(con), "Connection: close\r\n") < 0) return (0); @@ -1598,7 +2058,8 @@ SendError(client_t *con, /* I - Connection */ if (httpPrintf(HTTP(con), "Content-Type: text/html\r\n") < 0) return (0); - if (httpPrintf(HTTP(con), "Content-Length: %d\r\n", strlen(message)) < 0) + if (httpPrintf(HTTP(con), "Content-Length: %d\r\n", + (int)strlen(message)) < 0) return (0); if (httpPrintf(HTTP(con), "\r\n") < 0) return (0); @@ -1627,7 +2088,7 @@ SendFile(client_t *con, { con->file = open(filename, O_RDONLY); - LogMessage(L_DEBUG, "SendFile() %d file=%d", con->http.fd, con->file); + LogMessage(L_DEBUG, "SendFile: %d file=%d", con->http.fd, con->file); if (con->file < 0) return (0); @@ -1649,7 +2110,7 @@ SendFile(client_t *con, LogMessage(L_DEBUG2, "SendFile: Adding fd %d to OutputSet...", con->http.fd); - FD_SET(con->http.fd, &OutputSet); + FD_SET(con->http.fd, OutputSet); return (1); } @@ -1672,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) @@ -1700,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); } @@ -1725,23 +2187,31 @@ SendHeader(client_t *con, /* I - Client to send to */ /* - * 'ShutdownClient()' - Shutdown the receiving end of a connection. + * 'UpdateCGI()' - Read status messages from CGI scripts and programs. */ void -ShutdownClient(client_t *con) /* I - Client connection */ +UpdateCGI(void) { - /* - * Shutdown the receiving end of the socket, since the client - * still needs to read the error message... - */ + char *ptr, /* Pointer to end of line in buffer */ + message[1024]; /* Pointer to message text */ + int loglevel; /* Log level for message */ - shutdown(con->http.fd, 0); - LogMessage(L_DEBUG2, "ShutdownClient: Removing fd %d from InputSet...", - con->http.fd); + while ((ptr = cupsdStatBufUpdate(CGIStatusBuffer, &loglevel, + message, sizeof(message))) != NULL) + if (!strchr(CGIStatusBuffer->buffer, '\n')) + break; + + if (ptr == NULL) + { + /* + * Fatal error on pipe - should never happen! + */ - FD_CLR(con->http.fd, &InputSet); + LogMessage(L_CRIT, "UpdateCGI: error reading from CGI error pipe - %s", + strerror(errno)); + } } @@ -1758,6 +2228,11 @@ WriteClient(client_t *con) /* I - Client connection */ ipp_state_t ipp_state; /* IPP state value */ +#ifdef DEBUG + LogMessage(L_DEBUG2, "WriteClient(con=%p) %d response=%p, file=%d pipe_pid=%d", + con, con->http.fd, con->response, con->file, con->pipe_pid); +#endif /* DEBUG */ + if (con->http.state != HTTP_GET_SEND && con->http.state != HTTP_POST_SEND) return (1); @@ -1769,6 +2244,11 @@ WriteClient(client_t *con) /* I - Client connection */ } else if ((bytes = read(con->file, buf, HTTP_MAX_BUFFER)) > 0) { +#ifdef DEBUG + LogMessage(L_DEBUG2, "WriteClient: Read %d bytes from file %d...", + bytes, con->file); +#endif /* DEBUG */ + if (con->pipe_pid && !con->got_fields) { /* @@ -1789,14 +2269,14 @@ WriteClient(client_t *con) /* I - Client connection */ *bufptr++ = '\0'; httpPrintf(HTTP(con), "%s\r\n", buf); - LogMessage(L_DEBUG2, "WriteClient() %d %s", con->http.fd, buf); + LogMessage(L_DEBUG2, "WriteClient: %d %s", con->http.fd, buf); /* * Update buffer... */ bytes -= (bufptr - buf); - memcpy(buf, bufptr, bytes + 1); + memmove(buf, bufptr, bytes + 1); bufptr = buf - 1; /* @@ -1853,34 +2333,37 @@ WriteClient(client_t *con) /* I - Client connection */ con->http.state = HTTP_WAITING; - LogMessage(L_DEBUG2, "WriteClient() Removing fd %d from OutputSet...", + LogMessage(L_DEBUG2, "WriteClient: Removing fd %d from OutputSet...", con->http.fd); - FD_CLR(con->http.fd, &OutputSet); + FD_CLR(con->http.fd, OutputSet); - if (con->file) + if (con->file >= 0) { - LogMessage(L_DEBUG2, "WriteClient() Removing fd %d from InputSet...", - con->file); - FD_CLR(con->file, &InputSet); + if (FD_ISSET(con->file, InputSet)) + { + LogMessage(L_DEBUG2, "WriteClient: Removing fd %d from InputSet...", + 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); - con->file = 0; + con->file = -1; con->pipe_pid = 0; } - if (con->filename[0]) + 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); - con->filename[0] = '\0'; + ClearString(&con->filename); } if (con->request != NULL) @@ -1895,15 +2378,28 @@ WriteClient(client_t *con) /* I - Client connection */ con->response = NULL; } + ClearString(&con->command); + ClearString(&con->options); + if (!con->http.keep_alive) { CloseClient(con); return (0); } } + else + { + con->file_ready = 0; + + if (con->pipe_pid && !FD_ISSET(con->file, InputSet)) + { + LogMessage(L_DEBUG2, "WriteClient: Adding fd %d to InputSet...", con->file); + FD_SET(con->file, InputSet); + } + } if (bytes >= 1024) - LogMessage(L_DEBUG2, "WriteClient() %d %d bytes", con->http.fd, bytes); + LogMessage(L_DEBUG2, "WriteClient: %d %d bytes", con->http.fd, bytes); con->http.activity = time(NULL); @@ -1931,7 +2427,7 @@ check_if_modified(client_t *con, /* I - Client connection */ if (*ptr == '\0') return (1); - LogMessage(L_DEBUG2, "check_if_modified() %d If-Modified-Since=\"%s\"", + LogMessage(L_DEBUG2, "check_if_modified: %d If-Modified-Since=\"%s\"", con->http.fd, ptr); while (*ptr != '\0') @@ -1955,7 +2451,7 @@ check_if_modified(client_t *con, /* I - Client connection */ } } - LogMessage(L_DEBUG2, "check_if_modified() %d sizes=%d,%d dates=%d,%d", + LogMessage(L_DEBUG2, "check_if_modified: %d sizes=%d,%d dates=%d,%d", con->http.fd, size, (int)filestats->st_size, (int)date, (int)filestats->st_mtime); @@ -1985,8 +2481,6 @@ decode_auth(client_t *con) /* I - Client to decode to */ LogMessage(L_DEBUG2, "decode_auth(%p): Authorization string = \"%s\"", con, s); - LogMessage(L_INFO, "decode_auth(%p): Authorization string = \"%s\"", - con, s); if (strncmp(s, "Basic", 5) == 0) { @@ -2002,7 +2496,7 @@ decode_auth(client_t *con) /* I - Client to decode to */ if ((s = strchr(value, ':')) == NULL) { - LogMessage(L_DEBUG, "decode_auth() %d no colon in auth string \"%s\"", + LogMessage(L_DEBUG, "decode_auth: %d no colon in auth string \"%s\"", con->http.fd, value); return; } @@ -2027,16 +2521,16 @@ 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\"", + LogMessage(L_DEBUG2, "decode_auth: %d username=\"%s\"", con->http.fd, con->username); } @@ -2045,13 +2539,15 @@ 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 */ +static char * /* O - Real filename */ +get_file(client_t *con, /* I - Client connection */ + struct stat *filestats, /* O - File information */ + char *filename, /* IO - Filename buffer */ + int len) /* I - Buffer length */ { - int status; /* Status of filesystem calls */ - char *params; /* Pointer to parameters in URI */ - static char filename[1024]; /* Filename buffer */ + int status; /* Status of filesystem calls */ + char *ptr; /* Pointer info filename */ + int plen; /* Remaining length after pointer */ /* @@ -2059,17 +2555,17 @@ get_file(client_t *con, /* I - Client connection */ */ if (strncmp(con->uri, "/ppd/", 5) == 0) - snprintf(filename, sizeof(filename), "%s%s", ServerRoot, con->uri); + snprintf(filename, len, "%s%s", ServerRoot, con->uri); else if (strncmp(con->uri, "/admin/conf/", 12) == 0) - snprintf(filename, sizeof(filename), "%s%s", ServerRoot, con->uri + 11); + snprintf(filename, len, "%s%s", ServerRoot, con->uri + 11); else if (con->language != NULL) - snprintf(filename, sizeof(filename), "%s/%s%s", DocumentRoot, con->language->language, + snprintf(filename, len, "%s/%s%s", DocumentRoot, con->language->language, con->uri); else - snprintf(filename, sizeof(filename), "%s%s", DocumentRoot, con->uri); + snprintf(filename, len, "%s%s", DocumentRoot, con->uri); - if ((params = strchr(filename, '?')) != NULL) - *params = '\0'; + if ((ptr = strchr(filename, '?')) != NULL) + *ptr = '\0'; /* * Grab the status for this language; if there isn't a language-specific file @@ -2085,7 +2581,10 @@ get_file(client_t *con, /* I - Client connection */ if (strncmp(con->uri, "/ppd/", 5) != 0 && strncmp(con->uri, "/admin/conf/", 12) != 0) { - snprintf(filename, sizeof(filename), "%s%s", DocumentRoot, con->uri); + snprintf(filename, len, "%s%s", DocumentRoot, con->uri); + + if ((ptr = strchr(filename, '?')) != NULL) + *ptr = '\0'; status = stat(filename, filestats); } @@ -2097,15 +2596,55 @@ get_file(client_t *con, /* I - Client connection */ if (!status && S_ISDIR(filestats->st_mode)) { - if (filename[strlen(filename) - 1] == '/') - strlcat(filename, "index.html", sizeof(filename)); - else - strlcat(filename, "/index.html", sizeof(filename)); + if (filename[strlen(filename) - 1] != '/') + strlcat(filename, "/", len); + ptr = filename + strlen(filename); + plen = len - (ptr - filename); + + strlcpy(ptr, "index.html", plen); status = stat(filename, filestats); + +#ifdef HAVE_JAVA + if (status) + { + strlcpy(ptr, "index.class", plen); + status = stat(filename, filestats); + } +#endif /* HAVE_JAVA */ + +#ifdef HAVE_PERL + if (status) + { + strlcpy(ptr, "index.pl", plen); + status = stat(filename, filestats); + } +#endif /* HAVE_PERL */ + +#ifdef HAVE_PHP + if (status) + { + strlcpy(ptr, "index.php", plen); + status = stat(filename, filestats); + } +#endif /* HAVE_PHP */ + +#ifdef HAVE_PYTHON + if (status) + { + strlcpy(ptr, "index.pyc", plen); + status = stat(filename, filestats); + } + + if (status) + { + strlcpy(ptr, "index.py", plen); + status = stat(filename, filestats); + } +#endif /* HAVE_PYTHON */ } - LogMessage(L_DEBUG2, "get_file() %d filename=%s size=%d", + LogMessage(L_DEBUG2, "get_file: %d filename=%s size=%d", con->http.fd, filename, status ? -1 : (int)filestats->st_size); if (status) @@ -2122,7 +2661,7 @@ get_file(client_t *con, /* I - Client connection */ static http_status_t /* O - Status */ install_conf_file(client_t *con) /* I - Connection */ { - FILE *in, /* Input file */ + cups_file_t *in, /* Input file */ *out; /* Output file */ char buffer[1024]; /* Copy buffer */ int bytes; /* Number of bytes */ @@ -2152,43 +2691,43 @@ install_conf_file(client_t *con) /* I - Connection */ { confinfo.st_uid = User; confinfo.st_gid = Group; - confinfo.st_mode = 0640; + confinfo.st_mode = ConfigFilePerm; } /* * Open the request file and new config file... */ - if ((in = fopen(con->filename, "rb")) == NULL) + if ((in = cupsFileOpen(con->filename, "rb")) == NULL) { LogMessage(L_ERROR, "Unable to open request file \"%s\" - %s", con->filename, strerror(errno)); return (HTTP_SERVER_ERROR); } - if ((out = fopen(newfile, "wb")) == NULL) + if ((out = cupsFileOpen(newfile, "wb")) == NULL) { - fclose(in); + cupsFileClose(in); LogMessage(L_ERROR, "Unable to open config file \"%s\" - %s", newfile, strerror(errno)); return (HTTP_SERVER_ERROR); } - fchmod(fileno(out), confinfo.st_mode); - fchown(fileno(out), confinfo.st_uid, confinfo.st_gid); + fchmod(cupsFileNumber(out), confinfo.st_mode); + fchown(cupsFileNumber(out), confinfo.st_uid, confinfo.st_gid); /* * Copy from the request to the new config file... */ - while ((bytes = fread(buffer, 1, sizeof(buffer), in)) > 0) - if (fwrite(buffer, 1, bytes, out) < bytes) + while ((bytes = cupsFileRead(in, buffer, sizeof(buffer))) > 0) + if (cupsFileWrite(out, buffer, bytes) < bytes) { LogMessage(L_ERROR, "Unable to copy to config file \"%s\" - %s", newfile, strerror(errno)); - fclose(in); - fclose(out); + cupsFileClose(in); + cupsFileClose(out); unlink(newfile); return (HTTP_SERVER_ERROR); @@ -2198,8 +2737,8 @@ install_conf_file(client_t *con) /* I - Connection */ * Close the files... */ - fclose(in); - if (fclose(out)) + cupsFileClose(in); + if (cupsFileClose(out)) { LogMessage(L_ERROR, "Error file closing config file \"%s\" - %s", newfile, strerror(errno)); @@ -2214,7 +2753,7 @@ install_conf_file(client_t *con) /* I - Connection */ */ unlink(con->filename); - con->filename[0] = '\0'; + ClearString(&con->filename); /* * Unlink the old backup, rename the current config file to the backup @@ -2259,7 +2798,11 @@ install_conf_file(client_t *con) /* I - Connection */ */ if (strcmp(con->uri, "/admin/conf/cupsd.conf") == 0) - NeedReload = TRUE; + NeedReload = RELOAD_CUPSD; + else + NeedReload = RELOAD_ALL; + + ReloadTime = time(NULL); /* * Return that the file was created successfully... @@ -2269,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. */ @@ -2280,67 +2858,165 @@ pipe_command(client_t *con, /* I - Client connection */ char *command, /* I - Command to run */ char *options) /* I - Options for command */ { + int i; /* Looping var */ int pid; /* Process ID */ char *commptr; /* Command string pointer */ - int fd; /* Looping var */ + char *uriptr; /* URI string pointer */ int fds[2]; /* Pipe FDs */ int argc; /* Number of arguments */ int envc; /* Number of environment variables */ char argbuf[10240], /* Argument buffer */ *argv[100], /* Argument strings */ *envp[100]; /* Environment variables */ - char lang[1024], /* LANG env variable */ - content_length[1024], /* CONTENT_LENGTH env variable */ - content_type[1024], /* CONTENT_TYPE env variable */ - ipp_port[1024], /* Default listen port */ - server_port[1024], /* Default server port */ - server_name[1024], /* Default listen hostname */ - remote_host[1024], /* REMOTE_HOST env variable */ - remote_user[1024], /* REMOTE_USER env variable */ - tmpdir[1024], /* TMPDIR environment variable */ - ldpath[1024], /* LD_LIBRARY_PATH environment variable */ + char content_length[1024], /* CONTENT_LENGTH environment variable */ + content_type[1024], /* CONTENT_TYPE environment variable */ + cups_datadir[1024], /* CUPS_DATADIR environment variable */ + cups_serverroot[1024], /* CUPS_SERVERROOT environment variable */ + http_cookie[1024], /* HTTP_COOKIE environment variable */ + http_user_agent[1024], /* HTTP_USER_AGENT environment variable */ + ipp_port[1024], /* IPP_PORT environment variable */ + lang[1024], /* LANG environment variable */ + ld_library_path[1024], /* LD_LIBRARY_PATH environment variable */ + ld_preload[1024], /* LD_PRELOAD environment variable */ + dyld_library_path[1024],/* DYLD_LIBRARY_PATH environment variable */ + shlib_path[1024], /* SHLIB_PATH environment variable */ nlspath[1024], /* NLSPATH environment variable */ - datadir[1024], /* CUPS_DATADIR environment variable */ - root[1024], /* CUPS_SERVERROOT 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 */ + script_name[1024], /* SCRIPT_NAME environment variable */ + server_name[1024], /* SERVER_NAME environment variable */ + server_port[1024], /* SERVER_PORT environment variable */ + tmpdir[1024], /* TMPDIR environment variable */ + vg_args[1024], /* VG_ARGS environment variable */ + ld_assume_kernel[1024]; /* LD_ASSUME_KERNEL environment variable */ #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) - sigset_t oldmask, /* POSIX signal masks */ - newmask; struct sigaction action; /* POSIX signal handler */ #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ + static const char * const locale_encodings[] = + { /* Locale charset names */ + "ASCII", "ISO8859-1", "ISO8859-2", "ISO8859-3", + "ISO8859-4", "ISO8859-5", "ISO8859-6", "ISO8859-7", + "ISO8859-8", "ISO8859-9", "ISO8859-10", "UTF-8", + "ISO8859-13", "ISO8859-14", "ISO8859-15", "CP874", + "CP1250", "CP1251", "CP1252", "CP1253", + "CP1254", "CP1255", "CP1256", "CP1257", + "CP1258", "KOI8R", "KOI8U", "ISO8859-11", + "ISO8859-16", "", "", "", + + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + + "CP932", "CP936", "CP949", "CP950", + "CP1361", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + + "EUC-CN", "EUC-JP", "EUC-KR", "EUC-TW" + }; + static const char * const encryptions[] = + { + "CUPS_ENCRYPTION=IfRequested", + "CUPS_ENCRYPTION=Never", + "CUPS_ENCRYPTION=Required", + "CUPS_ENCRYPTION=Always" + }; /* - * 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 == '%') + 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 @@ -2351,10 +3027,16 @@ pipe_command(client_t *con, /* I - Client connection */ else *commptr |= tolower(commptr[2]) - 'a' + 10; - strcpy(commptr + 1, commptr + 3); + 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; @@ -2365,83 +3047,138 @@ pipe_command(client_t *con, /* I - Client connection */ * Setup the environment variables as needed... */ - snprintf(lang, sizeof(lang), "LANG=%s", - con->language ? con->language->language : "C"); + if (con->language) + snprintf(lang, sizeof(lang), "LANG=%s.%s", con->language->language, + 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) - { - sprintf(ipp_port, "IPP_PORT=%d", ntohs(con->http.hostaddr.ipv6.sin6_port)); sprintf(server_port, "SERVER_PORT=%d", ntohs(con->http.hostaddr.ipv6.sin6_port)); - } else #endif /* AF_INET6 */ - { - sprintf(ipp_port, "IPP_PORT=%d", ntohs(con->http.hostaddr.ipv4.sin_port)); sprintf(server_port, "SERVER_PORT=%d", ntohs(con->http.hostaddr.ipv4.sin_port)); - } - snprintf(server_name, sizeof(server_name), "SERVER_NAME=%s", ServerName); + if (strcmp(con->http.hostname, "localhost") == 0) + strlcpy(server_name, "SERVER_NAME=localhost", sizeof(server_name)); + else + snprintf(server_name, sizeof(server_name), "SERVER_NAME=%s", ServerName); snprintf(remote_host, sizeof(remote_host), "REMOTE_HOST=%s", con->http.hostname); + strcpy(remote_addr, "REMOTE_ADDR="); + httpAddrString(&(con->http.hostaddr), remote_addr + 12, + sizeof(remote_addr) - 12); snprintf(remote_user, sizeof(remote_user), "REMOTE_USER=%s", con->username); snprintf(tmpdir, sizeof(tmpdir), "TMPDIR=%s", TempDir); - snprintf(datadir, sizeof(datadir), "CUPS_DATADIR=%s", DataDir); - snprintf(root, sizeof(root), "CUPS_SERVERROOT=%s", ServerRoot); + snprintf(cups_datadir, sizeof(cups_datadir), "CUPS_DATADIR=%s", DataDir); + snprintf(cups_serverroot, sizeof(cups_serverroot), "CUPS_SERVERROOT=%s", ServerRoot); + + envc = 0; + + envp[envc ++] = "PATH=/bin:/usr/bin"; + envp[envc ++] = "SERVER_SOFTWARE=CUPS/1.1"; + envp[envc ++] = "GATEWAY_INTERFACE=CGI/1.1"; + if (con->http.version == HTTP_1_1) + envp[envc ++] = "SERVER_PROTOCOL=HTTP/1.1"; + else if (con->http.version == HTTP_1_0) + envp[envc ++] = "SERVER_PROTOCOL=HTTP/1.0"; + else + envp[envc ++] = "SERVER_PROTOCOL=HTTP/0.9"; + envp[envc ++] = "REDIRECT_STATUS=1"; + envp[envc ++] = "CUPS_SERVER=localhost"; + envp[envc ++] = ipp_port; + envp[envc ++] = server_name; + envp[envc ++] = server_port; + envp[envc ++] = remote_addr; + envp[envc ++] = remote_host; + envp[envc ++] = remote_user; + envp[envc ++] = lang; + envp[envc ++] = TZ; + envp[envc ++] = tmpdir; + envp[envc ++] = cups_datadir; + envp[envc ++] = cups_serverroot; + + if (getenv("VG_ARGS") != NULL) + { + snprintf(vg_args, sizeof(vg_args), "VG_ARGS=%s", getenv("VG_ARGS")); + envp[envc ++] = vg_args; + } + + if (getenv("LD_ASSUME_KERNEL") != NULL) + { + snprintf(ld_assume_kernel, sizeof(ld_assume_kernel), "LD_ASSUME_KERNEL=%s", + getenv("LD_ASSUME_KERNEL")); + envp[envc ++] = ld_assume_kernel; + } if (getenv("LD_LIBRARY_PATH") != NULL) - snprintf(ldpath, sizeof(ldpath), "LD_LIBRARY_PATH=%s", + { + snprintf(ld_library_path, sizeof(ld_library_path), "LD_LIBRARY_PATH=%s", getenv("LD_LIBRARY_PATH")); - else if (getenv("DYLD_LIBRARY_PATH") != NULL) - snprintf(ldpath, sizeof(ldpath), "DYLD_LIBRARY_PATH=%s", + envp[envc ++] = ld_library_path; + } + + if (getenv("LD_PRELOAD") != NULL) + { + snprintf(ld_preload, sizeof(ld_preload), "LD_PRELOAD=%s", + getenv("LD_PRELOAD")); + envp[envc ++] = ld_preload; + } + + if (getenv("DYLD_LIBRARY_PATH") != NULL) + { + snprintf(dyld_library_path, sizeof(dyld_library_path), "DYLD_LIBRARY_PATH=%s", getenv("DYLD_LIBRARY_PATH")); - else if (getenv("SHLIB_PATH") != NULL) - snprintf(ldpath, sizeof(ldpath), "SHLIB_PATH=%s", + envp[envc ++] = dyld_library_path; + } + + if (getenv("SHLIB_PATH") != NULL) + { + snprintf(shlib_path, sizeof(shlib_path), "SHLIB_PATH=%s", getenv("SHLIB_PATH")); - else - ldpath[0] = '\0'; + envp[envc ++] = shlib_path; + } if (getenv("NLSPATH") != NULL) + { snprintf(nlspath, sizeof(nlspath), "NLSPATH=%s", getenv("NLSPATH")); - else - nlspath[0] = '\0'; - - envp[0] = "PATH=/bin:/usr/bin"; - envp[1] = "SERVER_SOFTWARE=CUPS/1.2"; - envp[2] = "GATEWAY_INTERFACE=CGI/1.1"; - envp[3] = "SERVER_PROTOCOL=HTTP/1.1"; - envp[4] = ipp_port; - envp[5] = server_name; - envp[6] = server_port; - envp[7] = remote_host; - envp[8] = remote_user; - envp[9] = lang; - envp[10] = TZ; - envp[11] = tmpdir; - envp[12] = datadir; - envp[13] = root; - - envc = 14; - - if (ldpath[0]) - envp[envc ++] = ldpath; - - if (nlspath[0]) envp[envc ++] = nlspath; + } + + if (con->http.cookie) + { + snprintf(http_cookie, sizeof(http_cookie), "HTTP_COOKIE=%s", + con->http.cookie); + envp[envc ++] = http_cookie; + } + + if (con->http.fields[HTTP_FIELD_USER_AGENT][0]) + { + snprintf(http_user_agent, sizeof(http_user_agent), "HTTP_USER_AGENT=%s", + con->http.fields[HTTP_FIELD_USER_AGENT]); + envp[envc ++] = http_user_agent; + } + + snprintf(script_name, sizeof(script_name), "SCRIPT_NAME=%s", con->uri); + if ((uriptr = strchr(script_name, '?')) != NULL) + *uriptr = '\0'; + envp[envc ++] = script_name; if (con->operation == HTTP_GET) { + for (i = 0; i < argc; i ++) + 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; } } @@ -2461,19 +3198,32 @@ pipe_command(client_t *con, /* I - Client connection */ */ if (con->http.encryption == HTTP_ENCRYPT_ALWAYS) - { envp[envc ++] = "HTTPS=ON"; - envp[envc ++] = "CUPS_ENCRYPTION=Always"; - } + + envp[envc ++] = (char *)encryptions[LocalEncryption]; + + /* + * Terminate the environment array... + */ envp[envc] = NULL; + if (LogLevel == L_DEBUG2) + { + for (i = 0; i < argc; i ++) + LogMessage(L_DEBUG2, "pipe_command: argv[%d] = \"%s\"", i, argv[i]); + for (i = 0; i < envc; 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); @@ -2483,15 +3233,7 @@ pipe_command(client_t *con, /* I - Client connection */ * Block signals before forking... */ -#ifdef HAVE_SIGSET - sighold(SIGTERM); - sighold(SIGCHLD); -#elif defined(HAVE_SIGACTION) - sigemptyset(&newmask); - sigaddset(&newmask, SIGTERM); - sigaddset(&newmask, SIGCHLD); - sigprocmask(SIG_BLOCK, &newmask, &oldmask); -#endif /* HAVE_SIGSET */ + HoldSignals(); /* * Then execute the command... @@ -2503,7 +3245,7 @@ pipe_command(client_t *con, /* I - Client connection */ * Child comes here... Close stdin if necessary and dup the pipe to stdout. */ - if (getuid() == 0) + if (!RunUser) { /* * Running as root, so change to a non-priviledged user... @@ -2512,15 +3254,20 @@ pipe_command(client_t *con, /* I - Client connection */ if (setgid(Group)) exit(errno); + if (setgroups(1, &Group)) + exit(errno); + if (setuid(User)) exit(errno); } + else + { + /* + * Reset group membership to just the main one we belong to. + */ - /* - * Reset group membership to just the main one we belong to. - */ - - setgroups(0, NULL); + setgroups(1, &Group); + } /* * Update stdin/stdout/stderr... @@ -2538,14 +3285,7 @@ pipe_command(client_t *con, /* I - Client connection */ exit(errno); close(2); - open("/dev/null", O_WRONLY); - - /* - * Close extra file descriptors... - */ - - for (fd = 3; fd < MaxFDs; fd ++) - close(fd); + dup(CGIPipes[1]); /* * Change umask to restrict permissions on created files... @@ -2560,9 +3300,6 @@ pipe_command(client_t *con, /* I - Client connection */ #ifdef HAVE_SIGSET sigset(SIGTERM, SIG_DFL); sigset(SIGCHLD, SIG_DFL); - - sigrelse(SIGTERM); - sigrelse(SIGCHLD); #elif defined(HAVE_SIGACTION) memset(&action, 0, sizeof(action)); @@ -2571,13 +3308,12 @@ pipe_command(client_t *con, /* I - Client connection */ sigaction(SIGTERM, &action, NULL); sigaction(SIGCHLD, &action, NULL); - - sigprocmask(SIG_SETMASK, &oldmask, NULL); #else signal(SIGTERM, SIG_DFL); signal(SIGCHLD, SIG_DFL); #endif /* HAVE_SIGSET */ + ReleaseSignals(); /* * Execute the pipe program; if an error occurs, exit with status 1... @@ -2596,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 @@ -2614,17 +3349,62 @@ pipe_command(client_t *con, /* I - Client connection */ close(fds[1]); } -#ifdef HAVE_SIGSET - sigrelse(SIGTERM); - sigrelse(SIGCHLD); -#elif defined(HAVE_SIGACTION) - sigprocmask(SIG_SETMASK, &oldmask, NULL); -#endif /* HAVE_SIGSET */ + ReleaseSignals(); + + ClearString(&query_string); return (pid); } +#if defined(HAVE_CDSASSL) +/* + * 'CDSAReadFunc()' - Read function for CDSA decryption code. + */ + +static OSStatus /* O - -1 on error, 0 on success */ +CDSAReadFunc(SSLConnectionRef connection, /* I - SSL/TLS connection */ + void *data, /* I - Data buffer */ + size_t *dataLength) /* IO - Number of bytes */ +{ + ssize_t bytes; /* Number of bytes read */ + + + bytes = recv((int)connection, data, *dataLength, 0); + if (bytes >= 0) + { + *dataLength = bytes; + return (0); + } + else + return (-1); +} + + +/* + * 'CDSAWriteFunc()' - Write function for CDSA encryption code. + */ + +static OSStatus /* O - -1 on error, 0 on success */ +CDSAWriteFunc(SSLConnectionRef connection, /* I - SSL/TLS connection */ + const void *data, /* I - Data buffer */ + size_t *dataLength) /* IO - Number of bytes */ +{ + ssize_t bytes; + + + bytes = write((int)connection, data, *dataLength); + if (bytes >= 0) + { + *dataLength = bytes; + return (0); + } + else + return (-1); +} +#endif /* HAVE_CDSASSL */ + + /* - * End of "$Id: client.c,v 1.91.2.20 2002/09/15 22:55:15 mike Exp $". + * End of "$Id$". */