/* * File functions for CUPS. * * Since stdio files max out at 256 files on many systems, we have to * write similar functions without this limit. At the same time, using * our own file functions allows us to provide transparent support of * different line endings, gzip'd print files, PPD files, etc. * * Copyright © 2007-2019 by Apple Inc. * Copyright © 1997-2007 by Easy Software Products, all rights reserved. * * Licensed under Apache License v2.0. See the file "LICENSE" for more * information. */ /* * Include necessary headers... */ #include "file-private.h" #include "debug-internal.h" #include #include # ifdef HAVE_LIBZ # include # endif /* HAVE_LIBZ */ /* * Internal structures... */ struct _cups_file_s /**** CUPS file structure... ****/ { int fd; /* File descriptor */ char mode, /* Mode ('r' or 'w') */ compressed, /* Compression used? */ is_stdio, /* stdin/out/err? */ eof, /* End of file? */ buf[4096], /* Buffer */ *ptr, /* Pointer into buffer */ *end; /* End of buffer data */ off_t pos, /* Position in file */ bufpos; /* File position for start of buffer */ #ifdef HAVE_LIBZ z_stream stream; /* (De)compression stream */ Bytef cbuf[4096]; /* (De)compression buffer */ uLong crc; /* (De)compression CRC */ #endif /* HAVE_LIBZ */ char *printf_buffer; /* cupsFilePrintf buffer */ size_t printf_size; /* Size of cupsFilePrintf buffer */ }; /* * Local functions... */ #ifdef HAVE_LIBZ static ssize_t cups_compress(cups_file_t *fp, const char *buf, size_t bytes); #endif /* HAVE_LIBZ */ static ssize_t cups_fill(cups_file_t *fp); static int cups_open(const char *filename, int mode); static ssize_t cups_read(cups_file_t *fp, char *buf, size_t bytes); static ssize_t cups_write(cups_file_t *fp, const char *buf, size_t bytes); #ifndef _WIN32 /* * '_cupsFileCheck()' - Check the permissions of the given filename. */ _cups_fc_result_t /* O - Check result */ _cupsFileCheck( const char *filename, /* I - Filename to check */ _cups_fc_filetype_t filetype, /* I - Type of file checks? */ int dorootchecks, /* I - Check for root permissions? */ _cups_fc_func_t cb, /* I - Callback function */ void *context) /* I - Context pointer for callback */ { struct stat fileinfo; /* File information */ char message[1024], /* Message string */ temp[1024], /* Parent directory filename */ *ptr; /* Pointer into parent directory */ _cups_fc_result_t result; /* Check result */ /* * Does the filename contain a relative path ("../")? */ if (strstr(filename, "../")) { /* * Yes, fail it! */ result = _CUPS_FILE_CHECK_RELATIVE_PATH; goto finishup; } /* * Does the program even exist and is it accessible? */ if (stat(filename, &fileinfo)) { /* * Nope... */ result = _CUPS_FILE_CHECK_MISSING; goto finishup; } /* * Check the execute bit... */ result = _CUPS_FILE_CHECK_OK; switch (filetype) { case _CUPS_FILE_CHECK_DIRECTORY : if (!S_ISDIR(fileinfo.st_mode)) result = _CUPS_FILE_CHECK_WRONG_TYPE; break; default : if (!S_ISREG(fileinfo.st_mode)) result = _CUPS_FILE_CHECK_WRONG_TYPE; break; } if (result) goto finishup; /* * Are we doing root checks? */ if (!dorootchecks) { /* * Nope, so anything (else) goes... */ goto finishup; } /* * Verify permission of the file itself: * * 1. Must be owned by root * 2. Must not be writable by group * 3. Must not be setuid * 4. Must not be writable by others */ if (fileinfo.st_uid || /* 1. Must be owned by root */ (fileinfo.st_mode & S_IWGRP) || /* 2. Must not be writable by group */ (fileinfo.st_mode & S_ISUID) || /* 3. Must not be setuid */ (fileinfo.st_mode & S_IWOTH)) /* 4. Must not be writable by others */ { result = _CUPS_FILE_CHECK_PERMISSIONS; goto finishup; } if (filetype == _CUPS_FILE_CHECK_DIRECTORY || filetype == _CUPS_FILE_CHECK_FILE_ONLY) goto finishup; /* * Now check the containing directory... */ strlcpy(temp, filename, sizeof(temp)); if ((ptr = strrchr(temp, '/')) != NULL) { if (ptr == temp) ptr[1] = '\0'; else *ptr = '\0'; } if (stat(temp, &fileinfo)) { /* * Doesn't exist?!? */ result = _CUPS_FILE_CHECK_MISSING; filetype = _CUPS_FILE_CHECK_DIRECTORY; filename = temp; goto finishup; } if (fileinfo.st_uid || /* 1. Must be owned by root */ (fileinfo.st_mode & S_IWGRP) || /* 2. Must not be writable by group */ (fileinfo.st_mode & S_ISUID) || /* 3. Must not be setuid */ (fileinfo.st_mode & S_IWOTH)) /* 4. Must not be writable by others */ { result = _CUPS_FILE_CHECK_PERMISSIONS; filetype = _CUPS_FILE_CHECK_DIRECTORY; filename = temp; } /* * Common return point... */ finishup: if (cb) { cups_lang_t *lang = cupsLangDefault(); /* Localization information */ switch (result) { case _CUPS_FILE_CHECK_OK : if (filetype == _CUPS_FILE_CHECK_DIRECTORY) snprintf(message, sizeof(message), _cupsLangString(lang, _("Directory \"%s\" permissions OK " "(0%o/uid=%d/gid=%d).")), filename, fileinfo.st_mode, (int)fileinfo.st_uid, (int)fileinfo.st_gid); else snprintf(message, sizeof(message), _cupsLangString(lang, _("File \"%s\" permissions OK " "(0%o/uid=%d/gid=%d).")), filename, fileinfo.st_mode, (int)fileinfo.st_uid, (int)fileinfo.st_gid); break; case _CUPS_FILE_CHECK_MISSING : if (filetype == _CUPS_FILE_CHECK_DIRECTORY) snprintf(message, sizeof(message), _cupsLangString(lang, _("Directory \"%s\" not available: " "%s")), filename, strerror(errno)); else snprintf(message, sizeof(message), _cupsLangString(lang, _("File \"%s\" not available: %s")), filename, strerror(errno)); break; case _CUPS_FILE_CHECK_PERMISSIONS : if (filetype == _CUPS_FILE_CHECK_DIRECTORY) snprintf(message, sizeof(message), _cupsLangString(lang, _("Directory \"%s\" has insecure " "permissions " "(0%o/uid=%d/gid=%d).")), filename, fileinfo.st_mode, (int)fileinfo.st_uid, (int)fileinfo.st_gid); else snprintf(message, sizeof(message), _cupsLangString(lang, _("File \"%s\" has insecure " "permissions " "(0%o/uid=%d/gid=%d).")), filename, fileinfo.st_mode, (int)fileinfo.st_uid, (int)fileinfo.st_gid); break; case _CUPS_FILE_CHECK_WRONG_TYPE : if (filetype == _CUPS_FILE_CHECK_DIRECTORY) snprintf(message, sizeof(message), _cupsLangString(lang, _("Directory \"%s\" is a file.")), filename); else snprintf(message, sizeof(message), _cupsLangString(lang, _("File \"%s\" is a directory.")), filename); break; case _CUPS_FILE_CHECK_RELATIVE_PATH : if (filetype == _CUPS_FILE_CHECK_DIRECTORY) snprintf(message, sizeof(message), _cupsLangString(lang, _("Directory \"%s\" contains a " "relative path.")), filename); else snprintf(message, sizeof(message), _cupsLangString(lang, _("File \"%s\" contains a relative " "path.")), filename); break; } (*cb)(context, result, message); } return (result); } /* * '_cupsFileCheckFilter()' - Report file check results as CUPS filter messages. */ void _cupsFileCheckFilter( void *context, /* I - Context pointer (unused) */ _cups_fc_result_t result, /* I - Result code */ const char *message) /* I - Message text */ { const char *prefix; /* Messaging prefix */ (void)context; switch (result) { default : case _CUPS_FILE_CHECK_OK : prefix = "DEBUG2"; break; case _CUPS_FILE_CHECK_MISSING : case _CUPS_FILE_CHECK_WRONG_TYPE : prefix = "ERROR"; fputs("STATE: +cups-missing-filter-warning\n", stderr); break; case _CUPS_FILE_CHECK_PERMISSIONS : case _CUPS_FILE_CHECK_RELATIVE_PATH : prefix = "ERROR"; fputs("STATE: +cups-insecure-filter-warning\n", stderr); break; } fprintf(stderr, "%s: %s\n", prefix, message); } #endif /* !_WIN32 */ /* * 'cupsFileClose()' - Close a CUPS file. * * @since CUPS 1.2/macOS 10.5@ */ int /* O - 0 on success, -1 on error */ cupsFileClose(cups_file_t *fp) /* I - CUPS file */ { int fd; /* File descriptor */ char mode; /* Open mode */ int status; /* Return status */ DEBUG_printf(("cupsFileClose(fp=%p)", (void *)fp)); /* * Range check... */ if (!fp) return (-1); /* * Flush pending write data... */ if (fp->mode == 'w') status = cupsFileFlush(fp); else status = 0; #ifdef HAVE_LIBZ if (fp->compressed && status >= 0) { if (fp->mode == 'r') { /* * Free decompression data... */ inflateEnd(&fp->stream); } else { /* * Flush any remaining compressed data... */ unsigned char trailer[8]; /* Trailer CRC and length */ int done; /* Done writing... */ fp->stream.avail_in = 0; for (done = 0;;) { if (fp->stream.next_out > fp->cbuf) { if (cups_write(fp, (char *)fp->cbuf, (size_t)(fp->stream.next_out - fp->cbuf)) < 0) status = -1; fp->stream.next_out = fp->cbuf; fp->stream.avail_out = sizeof(fp->cbuf); } if (done || status < 0) break; done = deflate(&fp->stream, Z_FINISH) == Z_STREAM_END && fp->stream.next_out == fp->cbuf; } /* * Write the CRC and length... */ trailer[0] = (unsigned char)fp->crc; trailer[1] = (unsigned char)(fp->crc >> 8); trailer[2] = (unsigned char)(fp->crc >> 16); trailer[3] = (unsigned char)(fp->crc >> 24); trailer[4] = (unsigned char)fp->pos; trailer[5] = (unsigned char)(fp->pos >> 8); trailer[6] = (unsigned char)(fp->pos >> 16); trailer[7] = (unsigned char)(fp->pos >> 24); if (cups_write(fp, (char *)trailer, 8) < 0) status = -1; /* * Free all memory used by the compression stream... */ deflateEnd(&(fp->stream)); } } #endif /* HAVE_LIBZ */ /* * If this is one of the cupsFileStdin/out/err files, return now and don't * actually free memory or close (these last the life of the process...) */ if (fp->is_stdio) return (status); /* * Save the file descriptor we used and free memory... */ fd = fp->fd; mode = fp->mode; if (fp->printf_buffer) free(fp->printf_buffer); free(fp); /* * Close the file, returning the close status... */ if (mode == 's') { if (httpAddrClose(NULL, fd) < 0) status = -1; } else if (close(fd) < 0) status = -1; return (status); } /* * 'cupsFileCompression()' - Return whether a file is compressed. * * @since CUPS 1.2/macOS 10.5@ */ int /* O - @code CUPS_FILE_NONE@ or @code CUPS_FILE_GZIP@ */ cupsFileCompression(cups_file_t *fp) /* I - CUPS file */ { return (fp ? fp->compressed : CUPS_FILE_NONE); } /* * 'cupsFileEOF()' - Return the end-of-file status. * * @since CUPS 1.2/macOS 10.5@ */ int /* O - 1 on end of file, 0 otherwise */ cupsFileEOF(cups_file_t *fp) /* I - CUPS file */ { return (fp ? fp->eof : 1); } /* * 'cupsFileFind()' - Find a file using the specified path. * * This function allows the paths in the path string to be separated by * colons (UNIX standard) or semicolons (Windows standard) and stores the * result in the buffer supplied. If the file cannot be found in any of * the supplied paths, @code NULL@ is returned. A @code NULL@ path only * matches the current directory. * * @since CUPS 1.2/macOS 10.5@ */ const char * /* O - Full path to file or @code NULL@ if not found */ cupsFileFind(const char *filename, /* I - File to find */ const char *path, /* I - Colon/semicolon-separated path */ int executable, /* I - 1 = executable files, 0 = any file/dir */ char *buffer, /* I - Filename buffer */ int bufsize) /* I - Size of filename buffer */ { char *bufptr, /* Current position in buffer */ *bufend; /* End of buffer */ /* * Range check input... */ DEBUG_printf(("cupsFileFind(filename=\"%s\", path=\"%s\", executable=%d, buffer=%p, bufsize=%d)", filename, path, executable, (void *)buffer, bufsize)); if (!filename || !buffer || bufsize < 2) return (NULL); if (!path) { /* * No path, so check current directory... */ if (!access(filename, 0)) { strlcpy(buffer, filename, (size_t)bufsize); return (buffer); } else return (NULL); } /* * Now check each path and return the first match... */ bufend = buffer + bufsize - 1; bufptr = buffer; while (*path) { #ifdef _WIN32 if (*path == ';' || (*path == ':' && ((bufptr - buffer) > 1 || !isalpha(buffer[0] & 255)))) #else if (*path == ';' || *path == ':') #endif /* _WIN32 */ { if (bufptr > buffer && bufptr[-1] != '/' && bufptr < bufend) *bufptr++ = '/'; strlcpy(bufptr, filename, (size_t)(bufend - bufptr)); #ifdef _WIN32 if (!access(buffer, 0)) #else if (!access(buffer, executable ? X_OK : 0)) #endif /* _WIN32 */ { DEBUG_printf(("1cupsFileFind: Returning \"%s\"", buffer)); return (buffer); } bufptr = buffer; } else if (bufptr < bufend) *bufptr++ = *path; path ++; } /* * Check the last path... */ if (bufptr > buffer && bufptr[-1] != '/' && bufptr < bufend) *bufptr++ = '/'; strlcpy(bufptr, filename, (size_t)(bufend - bufptr)); if (!access(buffer, 0)) { DEBUG_printf(("1cupsFileFind: Returning \"%s\"", buffer)); return (buffer); } else { DEBUG_puts("1cupsFileFind: Returning NULL"); return (NULL); } } /* * 'cupsFileFlush()' - Flush pending output. * * @since CUPS 1.2/macOS 10.5@ */ int /* O - 0 on success, -1 on error */ cupsFileFlush(cups_file_t *fp) /* I - CUPS file */ { ssize_t bytes; /* Bytes to write */ DEBUG_printf(("cupsFileFlush(fp=%p)", (void *)fp)); /* * Range check input... */ if (!fp || fp->mode != 'w') { DEBUG_puts("1cupsFileFlush: Attempt to flush a read-only file..."); return (-1); } bytes = (ssize_t)(fp->ptr - fp->buf); DEBUG_printf(("2cupsFileFlush: Flushing " CUPS_LLFMT " bytes...", CUPS_LLCAST bytes)); if (bytes > 0) { #ifdef HAVE_LIBZ if (fp->compressed) bytes = cups_compress(fp, fp->buf, (size_t)bytes); else #endif /* HAVE_LIBZ */ bytes = cups_write(fp, fp->buf, (size_t)bytes); if (bytes < 0) return (-1); fp->ptr = fp->buf; } return (0); } /* * 'cupsFileGetChar()' - Get a single character from a file. * * @since CUPS 1.2/macOS 10.5@ */ int /* O - Character or -1 on end of file */ cupsFileGetChar(cups_file_t *fp) /* I - CUPS file */ { /* * Range check input... */ DEBUG_printf(("4cupsFileGetChar(fp=%p)", (void *)fp)); if (!fp || (fp->mode != 'r' && fp->mode != 's')) { DEBUG_puts("5cupsFileGetChar: Bad arguments!"); return (-1); } if (fp->eof) { DEBUG_puts("5cupsFileGetChar: End-of-file!"); return (-1); } /* * If the input buffer is empty, try to read more data... */ DEBUG_printf(("5cupsFileGetChar: fp->eof=%d, fp->ptr=%p, fp->end=%p", fp->eof, (void *)fp->ptr, (void *)fp->end)); if (fp->ptr >= fp->end) if (cups_fill(fp) <= 0) { DEBUG_puts("5cupsFileGetChar: Unable to fill buffer!"); return (-1); } /* * Return the next character in the buffer... */ DEBUG_printf(("5cupsFileGetChar: Returning %d...", *(fp->ptr) & 255)); fp->pos ++; DEBUG_printf(("6cupsFileGetChar: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); return (*(fp->ptr)++ & 255); } /* * 'cupsFileGetConf()' - Get a line from a configuration file. * * @since CUPS 1.2/macOS 10.5@ */ char * /* O - Line read or @code NULL@ on end of file or error */ cupsFileGetConf(cups_file_t *fp, /* I - CUPS file */ char *buf, /* O - String buffer */ size_t buflen, /* I - Size of string buffer */ char **value, /* O - Pointer to value */ int *linenum) /* IO - Current line number */ { char *ptr; /* Pointer into line */ /* * Range check input... */ DEBUG_printf(("2cupsFileGetConf(fp=%p, buf=%p, buflen=" CUPS_LLFMT ", value=%p, linenum=%p)", (void *)fp, (void *)buf, CUPS_LLCAST buflen, (void *)value, (void *)linenum)); if (!fp || (fp->mode != 'r' && fp->mode != 's') || !buf || buflen < 2 || !value) { if (value) *value = NULL; return (NULL); } /* * Read the next non-comment line... */ *value = NULL; while (cupsFileGets(fp, buf, buflen)) { (*linenum) ++; /* * Strip any comments... */ if ((ptr = strchr(buf, '#')) != NULL) { if (ptr > buf && ptr[-1] == '\\') { // Unquote the #... _cups_strcpy(ptr - 1, ptr); } else { // Strip the comment and any trailing whitespace... while (ptr > buf) { if (!_cups_isspace(ptr[-1])) break; ptr --; } *ptr = '\0'; } } /* * Strip leading whitespace... */ for (ptr = buf; _cups_isspace(*ptr); ptr ++); if (ptr > buf) _cups_strcpy(buf, ptr); /* * See if there is anything left... */ if (buf[0]) { /* * Yes, grab any value and return... */ for (ptr = buf; *ptr; ptr ++) if (_cups_isspace(*ptr)) break; if (*ptr) { /* * Have a value, skip any other spaces... */ while (_cups_isspace(*ptr)) *ptr++ = '\0'; if (*ptr) *value = ptr; /* * Strip trailing whitespace and > for lines that begin with <... */ ptr += strlen(ptr) - 1; if (buf[0] == '<' && *ptr == '>') *ptr-- = '\0'; else if (buf[0] == '<' && *ptr != '>') { /* * Syntax error... */ *value = NULL; return (buf); } while (ptr > *value && _cups_isspace(*ptr)) *ptr-- = '\0'; } /* * Return the line... */ return (buf); } } return (NULL); } /* * 'cupsFileGetLine()' - Get a CR and/or LF-terminated line that may * contain binary data. * * This function differs from @link cupsFileGets@ in that the trailing CR * and LF are preserved, as is any binary data on the line. The buffer is * nul-terminated, however you should use the returned length to determine * the number of bytes on the line. * * @since CUPS 1.2/macOS 10.5@ */ size_t /* O - Number of bytes on line or 0 on end of file */ cupsFileGetLine(cups_file_t *fp, /* I - File to read from */ char *buf, /* I - Buffer */ size_t buflen) /* I - Size of buffer */ { int ch; /* Character from file */ char *ptr, /* Current position in line buffer */ *end; /* End of line buffer */ /* * Range check input... */ DEBUG_printf(("2cupsFileGetLine(fp=%p, buf=%p, buflen=" CUPS_LLFMT ")", (void *)fp, (void *)buf, CUPS_LLCAST buflen)); if (!fp || (fp->mode != 'r' && fp->mode != 's') || !buf || buflen < 3) return (0); /* * Now loop until we have a valid line... */ for (ptr = buf, end = buf + buflen - 2; ptr < end ;) { if (fp->ptr >= fp->end) if (cups_fill(fp) <= 0) break; *ptr++ = ch = *(fp->ptr)++; fp->pos ++; if (ch == '\r') { /* * Check for CR LF... */ if (fp->ptr >= fp->end) if (cups_fill(fp) <= 0) break; if (*(fp->ptr) == '\n') { *ptr++ = *(fp->ptr)++; fp->pos ++; } break; } else if (ch == '\n') { /* * Line feed ends a line... */ break; } } *ptr = '\0'; DEBUG_printf(("4cupsFileGetLine: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); return ((size_t)(ptr - buf)); } /* * 'cupsFileGets()' - Get a CR and/or LF-terminated line. * * @since CUPS 1.2/macOS 10.5@ */ char * /* O - Line read or @code NULL@ on end of file or error */ cupsFileGets(cups_file_t *fp, /* I - CUPS file */ char *buf, /* O - String buffer */ size_t buflen) /* I - Size of string buffer */ { int ch; /* Character from file */ char *ptr, /* Current position in line buffer */ *end; /* End of line buffer */ /* * Range check input... */ DEBUG_printf(("2cupsFileGets(fp=%p, buf=%p, buflen=" CUPS_LLFMT ")", (void *)fp, (void *)buf, CUPS_LLCAST buflen)); if (!fp || (fp->mode != 'r' && fp->mode != 's') || !buf || buflen < 2) return (NULL); /* * Now loop until we have a valid line... */ for (ptr = buf, end = buf + buflen - 1; ptr < end ;) { if (fp->ptr >= fp->end) if (cups_fill(fp) <= 0) { if (ptr == buf) return (NULL); else break; } ch = *(fp->ptr)++; fp->pos ++; if (ch == '\r') { /* * Check for CR LF... */ if (fp->ptr >= fp->end) if (cups_fill(fp) <= 0) break; if (*(fp->ptr) == '\n') { fp->ptr ++; fp->pos ++; } break; } else if (ch == '\n') { /* * Line feed ends a line... */ break; } else *ptr++ = (char)ch; } *ptr = '\0'; DEBUG_printf(("4cupsFileGets: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); return (buf); } /* * 'cupsFileLock()' - Temporarily lock access to a file. * * @since CUPS 1.2/macOS 10.5@ */ int /* O - 0 on success, -1 on error */ cupsFileLock(cups_file_t *fp, /* I - CUPS file */ int block) /* I - 1 to wait for the lock, 0 to fail right away */ { /* * Range check... */ if (!fp || fp->mode == 's') return (-1); /* * Try the lock... */ #ifdef _WIN32 return (_locking(fp->fd, block ? _LK_LOCK : _LK_NBLCK, 0)); #else return (lockf(fp->fd, block ? F_LOCK : F_TLOCK, 0)); #endif /* _WIN32 */ } /* * 'cupsFileNumber()' - Return the file descriptor associated with a CUPS file. * * @since CUPS 1.2/macOS 10.5@ */ int /* O - File descriptor */ cupsFileNumber(cups_file_t *fp) /* I - CUPS file */ { if (fp) return (fp->fd); else return (-1); } /* * 'cupsFileOpen()' - Open a CUPS file. * * The "mode" parameter can be "r" to read, "w" to write, overwriting any * existing file, "a" to append to an existing file or create a new file, * or "s" to open a socket connection. * * When opening for writing ("w"), an optional number from 1 to 9 can be * supplied which enables Flate compression of the file. Compression is * not supported for the "a" (append) mode. * * When opening a socket connection, the filename is a string of the form * "address:port" or "hostname:port". The socket will make an IPv4 or IPv6 * connection as needed, generally preferring IPv6 connections when there is * a choice. * * @since CUPS 1.2/macOS 10.5@ */ cups_file_t * /* O - CUPS file or @code NULL@ if the file or socket cannot be opened */ cupsFileOpen(const char *filename, /* I - Name of file */ const char *mode) /* I - Open mode */ { cups_file_t *fp; /* New CUPS file */ int fd; /* File descriptor */ char hostname[1024], /* Hostname */ *portname; /* Port "name" (number or service) */ http_addrlist_t *addrlist; /* Host address list */ DEBUG_printf(("cupsFileOpen(filename=\"%s\", mode=\"%s\")", filename, mode)); /* * Range check input... */ if (!filename || !mode || (*mode != 'r' && *mode != 'w' && *mode != 'a' && *mode != 's') || (*mode == 'a' && isdigit(mode[1] & 255))) return (NULL); /* * Open the file... */ switch (*mode) { case 'a' : /* Append file */ fd = cups_open(filename, O_RDWR | O_CREAT | O_APPEND | O_LARGEFILE | O_BINARY); break; case 'r' : /* Read file */ fd = open(filename, O_RDONLY | O_LARGEFILE | O_BINARY, 0); break; case 'w' : /* Write file */ fd = cups_open(filename, O_WRONLY | O_LARGEFILE | O_BINARY); if (fd < 0 && errno == ENOENT) { fd = cups_open(filename, O_WRONLY | O_CREAT | O_EXCL | O_LARGEFILE | O_BINARY); if (fd < 0 && errno == EEXIST) fd = cups_open(filename, O_WRONLY | O_LARGEFILE | O_BINARY); } if (fd >= 0) #ifdef _WIN32 _chsize(fd, 0); #else ftruncate(fd, 0); #endif /* _WIN32 */ break; case 's' : /* Read/write socket */ strlcpy(hostname, filename, sizeof(hostname)); if ((portname = strrchr(hostname, ':')) != NULL) *portname++ = '\0'; else return (NULL); /* * Lookup the hostname and service... */ if ((addrlist = httpAddrGetList(hostname, AF_UNSPEC, portname)) == NULL) return (NULL); /* * Connect to the server... */ if (!httpAddrConnect(addrlist, &fd)) { httpAddrFreeList(addrlist); return (NULL); } httpAddrFreeList(addrlist); break; default : /* Remove bogus compiler warning... */ return (NULL); } if (fd < 0) return (NULL); /* * Create the CUPS file structure... */ if ((fp = cupsFileOpenFd(fd, mode)) == NULL) { if (*mode == 's') httpAddrClose(NULL, fd); else close(fd); } /* * Return it... */ return (fp); } /* * 'cupsFileOpenFd()' - Open a CUPS file using a file descriptor. * * The "mode" parameter can be "r" to read, "w" to write, "a" to append, * or "s" to treat the file descriptor as a bidirectional socket connection. * * When opening for writing ("w"), an optional number from 1 to 9 can be * supplied which enables Flate compression of the file. Compression is * not supported for the "a" (append) mode. * * @since CUPS 1.2/macOS 10.5@ */ cups_file_t * /* O - CUPS file or @code NULL@ if the file could not be opened */ cupsFileOpenFd(int fd, /* I - File descriptor */ const char *mode) /* I - Open mode */ { cups_file_t *fp; /* New CUPS file */ DEBUG_printf(("cupsFileOpenFd(fd=%d, mode=\"%s\")", fd, mode)); /* * Range check input... */ if (fd < 0 || !mode || (*mode != 'r' && *mode != 'w' && *mode != 'a' && *mode != 's') || (*mode == 'a' && isdigit(mode[1] & 255))) return (NULL); /* * Allocate memory... */ if ((fp = calloc(1, sizeof(cups_file_t))) == NULL) return (NULL); /* * Open the file... */ fp->fd = fd; switch (*mode) { case 'a' : fp->pos = lseek(fd, 0, SEEK_END); case 'w' : fp->mode = 'w'; fp->ptr = fp->buf; fp->end = fp->buf + sizeof(fp->buf); #ifdef HAVE_LIBZ if (mode[1] >= '1' && mode[1] <= '9') { /* * Open a compressed stream, so write the standard gzip file * header... */ unsigned char header[10]; /* gzip file header */ time_t curtime; /* Current time */ curtime = time(NULL); header[0] = 0x1f; header[1] = 0x8b; header[2] = Z_DEFLATED; header[3] = 0; header[4] = (unsigned char)curtime; header[5] = (unsigned char)(curtime >> 8); header[6] = (unsigned char)(curtime >> 16); header[7] = (unsigned char)(curtime >> 24); header[8] = 0; header[9] = 0x03; cups_write(fp, (char *)header, 10); /* * Initialize the compressor... */ deflateInit2(&(fp->stream), mode[1] - '0', Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); fp->stream.next_out = fp->cbuf; fp->stream.avail_out = sizeof(fp->cbuf); fp->compressed = 1; fp->crc = crc32(0L, Z_NULL, 0); } #endif /* HAVE_LIBZ */ break; case 'r' : fp->mode = 'r'; break; case 's' : fp->mode = 's'; break; default : /* Remove bogus compiler warning... */ return (NULL); } /* * Don't pass this file to child processes... */ #ifndef _WIN32 fcntl(fp->fd, F_SETFD, fcntl(fp->fd, F_GETFD) | FD_CLOEXEC); #endif /* !_WIN32 */ return (fp); } /* * '_cupsFilePeekAhead()' - See if the requested character is buffered up. */ int /* O - 1 if present, 0 otherwise */ _cupsFilePeekAhead(cups_file_t *fp, /* I - CUPS file */ int ch) /* I - Character */ { return (fp && fp->ptr && memchr(fp->ptr, ch, (size_t)(fp->end - fp->ptr))); } /* * 'cupsFilePeekChar()' - Peek at the next character from a file. * * @since CUPS 1.2/macOS 10.5@ */ int /* O - Character or -1 on end of file */ cupsFilePeekChar(cups_file_t *fp) /* I - CUPS file */ { /* * Range check input... */ if (!fp || (fp->mode != 'r' && fp->mode != 's')) return (-1); /* * If the input buffer is empty, try to read more data... */ if (fp->ptr >= fp->end) if (cups_fill(fp) <= 0) return (-1); /* * Return the next character in the buffer... */ return (*(fp->ptr) & 255); } /* * 'cupsFilePrintf()' - Write a formatted string. * * @since CUPS 1.2/macOS 10.5@ */ int /* O - Number of bytes written or -1 on error */ cupsFilePrintf(cups_file_t *fp, /* I - CUPS file */ const char *format, /* I - Printf-style format string */ ...) /* I - Additional args as necessary */ { va_list ap; /* Argument list */ ssize_t bytes; /* Formatted size */ DEBUG_printf(("2cupsFilePrintf(fp=%p, format=\"%s\", ...)", (void *)fp, format)); if (!fp || !format || (fp->mode != 'w' && fp->mode != 's')) return (-1); if (!fp->printf_buffer) { /* * Start with an 1k printf buffer... */ if ((fp->printf_buffer = malloc(1024)) == NULL) return (-1); fp->printf_size = 1024; } va_start(ap, format); bytes = vsnprintf(fp->printf_buffer, fp->printf_size, format, ap); va_end(ap); if (bytes >= (ssize_t)fp->printf_size) { /* * Expand the printf buffer... */ char *temp; /* Temporary buffer pointer */ if (bytes > 65535) return (-1); if ((temp = realloc(fp->printf_buffer, (size_t)(bytes + 1))) == NULL) return (-1); fp->printf_buffer = temp; fp->printf_size = (size_t)(bytes + 1); va_start(ap, format); bytes = vsnprintf(fp->printf_buffer, fp->printf_size, format, ap); va_end(ap); } if (fp->mode == 's') { if (cups_write(fp, fp->printf_buffer, (size_t)bytes) < 0) return (-1); fp->pos += bytes; DEBUG_printf(("4cupsFilePrintf: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); return ((int)bytes); } if ((fp->ptr + bytes) > fp->end) if (cupsFileFlush(fp)) return (-1); fp->pos += bytes; DEBUG_printf(("4cupsFilePrintf: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); if ((size_t)bytes > sizeof(fp->buf)) { #ifdef HAVE_LIBZ if (fp->compressed) return ((int)cups_compress(fp, fp->printf_buffer, (size_t)bytes)); else #endif /* HAVE_LIBZ */ return ((int)cups_write(fp, fp->printf_buffer, (size_t)bytes)); } else { memcpy(fp->ptr, fp->printf_buffer, (size_t)bytes); fp->ptr += bytes; if (fp->is_stdio && cupsFileFlush(fp)) return (-1); else return ((int)bytes); } } /* * 'cupsFilePutChar()' - Write a character. * * @since CUPS 1.2/macOS 10.5@ */ int /* O - 0 on success, -1 on error */ cupsFilePutChar(cups_file_t *fp, /* I - CUPS file */ int c) /* I - Character to write */ { /* * Range check input... */ if (!fp || (fp->mode != 'w' && fp->mode != 's')) return (-1); if (fp->mode == 's') { /* * Send character immediately over socket... */ char ch; /* Output character */ ch = (char)c; if (send(fp->fd, &ch, 1, 0) < 1) return (-1); } else { /* * Buffer it up... */ if (fp->ptr >= fp->end) if (cupsFileFlush(fp)) return (-1); *(fp->ptr) ++ = (char)c; } fp->pos ++; DEBUG_printf(("4cupsFilePutChar: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); return (0); } /* * 'cupsFilePutConf()' - Write a configuration line. * * This function handles any comment escaping of the value. * * @since CUPS 1.4/macOS 10.6@ */ ssize_t /* O - Number of bytes written or -1 on error */ cupsFilePutConf(cups_file_t *fp, /* I - CUPS file */ const char *directive, /* I - Directive */ const char *value) /* I - Value */ { ssize_t bytes, /* Number of bytes written */ temp; /* Temporary byte count */ const char *ptr; /* Pointer into value */ if (!fp || !directive || !*directive) return (-1); if ((bytes = cupsFilePuts(fp, directive)) < 0) return (-1); if (cupsFilePutChar(fp, ' ') < 0) return (-1); bytes ++; if (value && *value) { if ((ptr = strchr(value, '#')) != NULL) { /* * Need to quote the first # in the info string... */ if ((temp = cupsFileWrite(fp, value, (size_t)(ptr - value))) < 0) return (-1); bytes += temp; if (cupsFilePutChar(fp, '\\') < 0) return (-1); bytes ++; if ((temp = cupsFilePuts(fp, ptr)) < 0) return (-1); bytes += temp; } else if ((temp = cupsFilePuts(fp, value)) < 0) return (-1); else bytes += temp; } if (cupsFilePutChar(fp, '\n') < 0) return (-1); else return (bytes + 1); } /* * 'cupsFilePuts()' - Write a string. * * Like the @code fputs@ function, no newline is appended to the string. * * @since CUPS 1.2/macOS 10.5@ */ int /* O - Number of bytes written or -1 on error */ cupsFilePuts(cups_file_t *fp, /* I - CUPS file */ const char *s) /* I - String to write */ { ssize_t bytes; /* Bytes to write */ /* * Range check input... */ if (!fp || !s || (fp->mode != 'w' && fp->mode != 's')) return (-1); /* * Write the string... */ bytes = (ssize_t)strlen(s); if (fp->mode == 's') { if (cups_write(fp, s, (size_t)bytes) < 0) return (-1); fp->pos += bytes; DEBUG_printf(("4cupsFilePuts: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); return ((int)bytes); } if ((fp->ptr + bytes) > fp->end) if (cupsFileFlush(fp)) return (-1); fp->pos += bytes; DEBUG_printf(("4cupsFilePuts: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); if ((size_t)bytes > sizeof(fp->buf)) { #ifdef HAVE_LIBZ if (fp->compressed) return ((int)cups_compress(fp, s, (size_t)bytes)); else #endif /* HAVE_LIBZ */ return ((int)cups_write(fp, s, (size_t)bytes)); } else { memcpy(fp->ptr, s, (size_t)bytes); fp->ptr += bytes; if (fp->is_stdio && cupsFileFlush(fp)) return (-1); else return ((int)bytes); } } /* * 'cupsFileRead()' - Read from a file. * * @since CUPS 1.2/macOS 10.5@ */ ssize_t /* O - Number of bytes read or -1 on error */ cupsFileRead(cups_file_t *fp, /* I - CUPS file */ char *buf, /* O - Buffer */ size_t bytes) /* I - Number of bytes to read */ { size_t total; /* Total bytes read */ ssize_t count; /* Bytes read */ DEBUG_printf(("2cupsFileRead(fp=%p, buf=%p, bytes=" CUPS_LLFMT ")", (void *)fp, (void *)buf, CUPS_LLCAST bytes)); /* * Range check input... */ if (!fp || !buf || (fp->mode != 'r' && fp->mode != 's')) return (-1); if (bytes == 0) return (0); if (fp->eof) { DEBUG_puts("5cupsFileRead: End-of-file!"); return (-1); } /* * Loop until all bytes are read... */ total = 0; while (bytes > 0) { if (fp->ptr >= fp->end) if (cups_fill(fp) <= 0) { DEBUG_printf(("4cupsFileRead: cups_fill() returned -1, total=" CUPS_LLFMT, CUPS_LLCAST total)); if (total > 0) return ((ssize_t)total); else return (-1); } count = (ssize_t)(fp->end - fp->ptr); if (count > (ssize_t)bytes) count = (ssize_t)bytes; memcpy(buf, fp->ptr,(size_t) count); fp->ptr += count; fp->pos += count; DEBUG_printf(("4cupsFileRead: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); /* * Update the counts for the last read... */ bytes -= (size_t)count; total += (size_t)count; buf += count; } /* * Return the total number of bytes read... */ DEBUG_printf(("3cupsFileRead: total=" CUPS_LLFMT, CUPS_LLCAST total)); return ((ssize_t)total); } /* * 'cupsFileRewind()' - Set the current file position to the beginning of the * file. * * @since CUPS 1.2/macOS 10.5@ */ off_t /* O - New file position or -1 on error */ cupsFileRewind(cups_file_t *fp) /* I - CUPS file */ { /* * Range check input... */ DEBUG_printf(("cupsFileRewind(fp=%p)", (void *)fp)); DEBUG_printf(("2cupsFileRewind: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); if (!fp || fp->mode != 'r') return (-1); /* * Handle special cases... */ if (fp->bufpos == 0) { /* * No seeking necessary... */ fp->pos = 0; if (fp->ptr) { fp->ptr = fp->buf; fp->eof = 0; } DEBUG_printf(("2cupsFileRewind: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); return (0); } /* * Otherwise, seek in the file and cleanup any compression buffers... */ #ifdef HAVE_LIBZ if (fp->compressed) { inflateEnd(&fp->stream); fp->compressed = 0; } #endif /* HAVE_LIBZ */ if (lseek(fp->fd, 0, SEEK_SET)) { DEBUG_printf(("1cupsFileRewind: lseek failed: %s", strerror(errno))); return (-1); } fp->bufpos = 0; fp->pos = 0; fp->ptr = NULL; fp->end = NULL; fp->eof = 0; DEBUG_printf(("2cupsFileRewind: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); return (0); } /* * 'cupsFileSeek()' - Seek in a file. * * @since CUPS 1.2/macOS 10.5@ */ off_t /* O - New file position or -1 on error */ cupsFileSeek(cups_file_t *fp, /* I - CUPS file */ off_t pos) /* I - Position in file */ { ssize_t bytes; /* Number bytes in buffer */ DEBUG_printf(("cupsFileSeek(fp=%p, pos=" CUPS_LLFMT ")", (void *)fp, CUPS_LLCAST pos)); DEBUG_printf(("2cupsFileSeek: fp->pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); DEBUG_printf(("2cupsFileSeek: fp->ptr=%p, fp->end=%p", (void *)fp->ptr, (void *)fp->end)); /* * Range check input... */ if (!fp || pos < 0 || fp->mode != 'r') return (-1); /* * Handle special cases... */ if (pos == 0) return (cupsFileRewind(fp)); if (fp->ptr) { bytes = (ssize_t)(fp->end - fp->buf); DEBUG_printf(("2cupsFileSeek: bytes=" CUPS_LLFMT, CUPS_LLCAST bytes)); if (pos >= fp->bufpos && pos < (fp->bufpos + bytes)) { /* * No seeking necessary... */ fp->pos = pos; fp->ptr = fp->buf + pos - fp->bufpos; fp->eof = 0; return (pos); } } #ifdef HAVE_LIBZ if (!fp->compressed && !fp->ptr) { /* * Preload a buffer to determine whether the file is compressed... */ if (cups_fill(fp) <= 0) return (-1); } #endif /* HAVE_LIBZ */ /* * Seek forwards or backwards... */ fp->eof = 0; if (pos < fp->bufpos) { /* * Need to seek backwards... */ DEBUG_puts("2cupsFileSeek: SEEK BACKWARDS"); #ifdef HAVE_LIBZ if (fp->compressed) { inflateEnd(&fp->stream); lseek(fp->fd, 0, SEEK_SET); fp->bufpos = 0; fp->pos = 0; fp->ptr = NULL; fp->end = NULL; while ((bytes = cups_fill(fp)) > 0) if (pos >= fp->bufpos && pos < (fp->bufpos + bytes)) break; if (bytes <= 0) return (-1); fp->ptr = fp->buf + pos - fp->bufpos; fp->pos = pos; } else #endif /* HAVE_LIBZ */ { fp->bufpos = lseek(fp->fd, pos, SEEK_SET); fp->pos = fp->bufpos; fp->ptr = NULL; fp->end = NULL; DEBUG_printf(("2cupsFileSeek: lseek() returned " CUPS_LLFMT, CUPS_LLCAST fp->pos)); } } else { /* * Need to seek forwards... */ DEBUG_puts("2cupsFileSeek: SEEK FORWARDS"); #ifdef HAVE_LIBZ if (fp->compressed) { while ((bytes = cups_fill(fp)) > 0) { if (pos >= fp->bufpos && pos < (fp->bufpos + bytes)) break; } if (bytes <= 0) return (-1); fp->ptr = fp->buf + pos - fp->bufpos; fp->pos = pos; } else #endif /* HAVE_LIBZ */ { fp->bufpos = lseek(fp->fd, pos, SEEK_SET); fp->pos = fp->bufpos; fp->ptr = NULL; fp->end = NULL; DEBUG_printf(("2cupsFileSeek: lseek() returned " CUPS_LLFMT, CUPS_LLCAST fp->pos)); } } DEBUG_printf(("2cupsFileSeek: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); return (fp->pos); } /* * 'cupsFileStderr()' - Return a CUPS file associated with stderr. * * @since CUPS 1.2/macOS 10.5@ */ cups_file_t * /* O - CUPS file */ cupsFileStderr(void) { _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals... */ /* * Open file descriptor 2 as needed... */ if (!cg->stdio_files[2]) { /* * Flush any pending output on the stdio file... */ fflush(stderr); /* * Open file descriptor 2... */ if ((cg->stdio_files[2] = cupsFileOpenFd(2, "w")) != NULL) cg->stdio_files[2]->is_stdio = 1; } return (cg->stdio_files[2]); } /* * 'cupsFileStdin()' - Return a CUPS file associated with stdin. * * @since CUPS 1.2/macOS 10.5@ */ cups_file_t * /* O - CUPS file */ cupsFileStdin(void) { _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals... */ /* * Open file descriptor 0 as needed... */ if (!cg->stdio_files[0]) { /* * Open file descriptor 0... */ if ((cg->stdio_files[0] = cupsFileOpenFd(0, "r")) != NULL) cg->stdio_files[0]->is_stdio = 1; } return (cg->stdio_files[0]); } /* * 'cupsFileStdout()' - Return a CUPS file associated with stdout. * * @since CUPS 1.2/macOS 10.5@ */ cups_file_t * /* O - CUPS file */ cupsFileStdout(void) { _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals... */ /* * Open file descriptor 1 as needed... */ if (!cg->stdio_files[1]) { /* * Flush any pending output on the stdio file... */ fflush(stdout); /* * Open file descriptor 1... */ if ((cg->stdio_files[1] = cupsFileOpenFd(1, "w")) != NULL) cg->stdio_files[1]->is_stdio = 1; } return (cg->stdio_files[1]); } /* * 'cupsFileTell()' - Return the current file position. * * @since CUPS 1.2/macOS 10.5@ */ off_t /* O - File position */ cupsFileTell(cups_file_t *fp) /* I - CUPS file */ { DEBUG_printf(("2cupsFileTell(fp=%p)", (void *)fp)); DEBUG_printf(("3cupsFileTell: pos=" CUPS_LLFMT, CUPS_LLCAST (fp ? fp->pos : -1))); return (fp ? fp->pos : 0); } /* * 'cupsFileUnlock()' - Unlock access to a file. * * @since CUPS 1.2/macOS 10.5@ */ int /* O - 0 on success, -1 on error */ cupsFileUnlock(cups_file_t *fp) /* I - CUPS file */ { /* * Range check... */ DEBUG_printf(("cupsFileUnlock(fp=%p)", (void *)fp)); if (!fp || fp->mode == 's') return (-1); /* * Unlock... */ #ifdef _WIN32 return (_locking(fp->fd, _LK_UNLCK, 0)); #else return (lockf(fp->fd, F_ULOCK, 0)); #endif /* _WIN32 */ } /* * 'cupsFileWrite()' - Write to a file. * * @since CUPS 1.2/macOS 10.5@ */ ssize_t /* O - Number of bytes written or -1 on error */ cupsFileWrite(cups_file_t *fp, /* I - CUPS file */ const char *buf, /* I - Buffer */ size_t bytes) /* I - Number of bytes to write */ { /* * Range check input... */ DEBUG_printf(("2cupsFileWrite(fp=%p, buf=%p, bytes=" CUPS_LLFMT ")", (void *)fp, (void *)buf, CUPS_LLCAST bytes)); if (!fp || !buf || (fp->mode != 'w' && fp->mode != 's')) return (-1); if (bytes == 0) return (0); /* * Write the buffer... */ if (fp->mode == 's') { if (cups_write(fp, buf, bytes) < 0) return (-1); fp->pos += (off_t)bytes; DEBUG_printf(("4cupsFileWrite: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); return ((ssize_t)bytes); } if ((fp->ptr + bytes) > fp->end) if (cupsFileFlush(fp)) return (-1); fp->pos += (off_t)bytes; DEBUG_printf(("4cupsFileWrite: pos=" CUPS_LLFMT, CUPS_LLCAST fp->pos)); if (bytes > sizeof(fp->buf)) { #ifdef HAVE_LIBZ if (fp->compressed) return (cups_compress(fp, buf, bytes)); else #endif /* HAVE_LIBZ */ return (cups_write(fp, buf, bytes)); } else { memcpy(fp->ptr, buf, bytes); fp->ptr += bytes; return ((ssize_t)bytes); } } #ifdef HAVE_LIBZ /* * 'cups_compress()' - Compress a buffer of data. */ static ssize_t /* O - Number of bytes written or -1 */ cups_compress(cups_file_t *fp, /* I - CUPS file */ const char *buf, /* I - Buffer */ size_t bytes) /* I - Number bytes */ { DEBUG_printf(("7cups_compress(fp=%p, buf=%p, bytes=" CUPS_LLFMT ")", (void *)fp, (void *)buf, CUPS_LLCAST bytes)); /* * Update the CRC... */ fp->crc = crc32(fp->crc, (const Bytef *)buf, (uInt)bytes); /* * Deflate the bytes... */ fp->stream.next_in = (Bytef *)buf; fp->stream.avail_in = (uInt)bytes; while (fp->stream.avail_in > 0) { /* * Flush the current buffer... */ DEBUG_printf(("9cups_compress: avail_in=%d, avail_out=%d", fp->stream.avail_in, fp->stream.avail_out)); if (fp->stream.avail_out < (uInt)(sizeof(fp->cbuf) / 8)) { if (cups_write(fp, (char *)fp->cbuf, (size_t)(fp->stream.next_out - fp->cbuf)) < 0) return (-1); fp->stream.next_out = fp->cbuf; fp->stream.avail_out = sizeof(fp->cbuf); } deflate(&(fp->stream), Z_NO_FLUSH); } return ((ssize_t)bytes); } #endif /* HAVE_LIBZ */ /* * 'cups_fill()' - Fill the input buffer. */ static ssize_t /* O - Number of bytes or -1 */ cups_fill(cups_file_t *fp) /* I - CUPS file */ { ssize_t bytes; /* Number of bytes read */ #ifdef HAVE_LIBZ int status; /* Decompression status */ const unsigned char *ptr, /* Pointer into buffer */ *end; /* End of buffer */ #endif /* HAVE_LIBZ */ DEBUG_printf(("7cups_fill(fp=%p)", (void *)fp)); DEBUG_printf(("9cups_fill: fp->ptr=%p, fp->end=%p, fp->buf=%p, fp->bufpos=" CUPS_LLFMT ", fp->eof=%d", (void *)fp->ptr, (void *)fp->end, (void *)fp->buf, CUPS_LLCAST fp->bufpos, fp->eof)); if (fp->ptr && fp->end) fp->bufpos += fp->end - fp->buf; #ifdef HAVE_LIBZ DEBUG_printf(("9cups_fill: fp->compressed=%d", fp->compressed)); while (!fp->ptr || fp->compressed) { /* * Check to see if we have read any data yet; if not, see if we have a * compressed file... */ if (!fp->ptr) { /* * Reset the file position in case we are seeking... */ fp->compressed = 0; /* * Read the first bytes in the file to determine if we have a gzip'd * file... */ if ((bytes = cups_read(fp, (char *)fp->buf, sizeof(fp->buf))) < 0) { /* * Can't read from file! */ DEBUG_printf(("9cups_fill: cups_read() returned " CUPS_LLFMT, CUPS_LLCAST bytes)); fp->eof = 1; return (-1); } if (bytes < 10 || fp->buf[0] != 0x1f || (fp->buf[1] & 255) != 0x8b || fp->buf[2] != 8 || (fp->buf[3] & 0xe0) != 0) { /* * Not a gzip'd file! */ fp->ptr = fp->buf; fp->end = fp->buf + bytes; DEBUG_printf(("9cups_fill: Returning " CUPS_LLFMT, CUPS_LLCAST bytes)); return (bytes); } /* * Parse header junk: extra data, original name, and comment... */ ptr = (unsigned char *)fp->buf + 10; end = (unsigned char *)fp->buf + bytes; if (fp->buf[3] & 0x04) { /* * Skip extra data... */ if ((ptr + 2) > end) { /* * Can't read from file! */ DEBUG_puts("9cups_fill: Extra gzip header data missing, returning -1."); fp->eof = 1; errno = EIO; return (-1); } bytes = ((unsigned char)ptr[1] << 8) | (unsigned char)ptr[0]; ptr += 2 + bytes; if (ptr > end) { /* * Can't read from file! */ DEBUG_puts("9cups_fill: Extra gzip header data does not fit in initial buffer, returning -1."); fp->eof = 1; errno = EIO; return (-1); } } if (fp->buf[3] & 0x08) { /* * Skip original name data... */ while (ptr < end && *ptr) ptr ++; if (ptr < end) ptr ++; else { /* * Can't read from file! */ DEBUG_puts("9cups_fill: Original filename in gzip header data does not fit in initial buffer, returning -1."); fp->eof = 1; errno = EIO; return (-1); } } if (fp->buf[3] & 0x10) { /* * Skip comment data... */ while (ptr < end && *ptr) ptr ++; if (ptr < end) ptr ++; else { /* * Can't read from file! */ DEBUG_puts("9cups_fill: Comment in gzip header data does not fit in initial buffer, returning -1."); fp->eof = 1; errno = EIO; return (-1); } } if (fp->buf[3] & 0x02) { /* * Skip header CRC data... */ ptr += 2; if (ptr > end) { /* * Can't read from file! */ DEBUG_puts("9cups_fill: Header CRC in gzip header data does not fit in initial buffer, returning -1."); fp->eof = 1; errno = EIO; return (-1); } } /* * Copy the flate-compressed data to the compression buffer... */ if ((bytes = end - ptr) > 0) memcpy(fp->cbuf, ptr, (size_t)bytes); /* * Setup the decompressor data... */ fp->stream.zalloc = (alloc_func)0; fp->stream.zfree = (free_func)0; fp->stream.opaque = (voidpf)0; fp->stream.next_in = (Bytef *)fp->cbuf; fp->stream.next_out = NULL; fp->stream.avail_in = (uInt)bytes; fp->stream.avail_out = 0; fp->crc = crc32(0L, Z_NULL, 0); if ((status = inflateInit2(&(fp->stream), -15)) != Z_OK) { DEBUG_printf(("9cups_fill: inflateInit2 returned %d, returning -1.", status)); fp->eof = 1; errno = EIO; return (-1); } fp->compressed = 1; } if (fp->compressed) { /* * If we have reached end-of-file, return immediately... */ if (fp->eof) { DEBUG_puts("9cups_fill: EOF, returning 0."); return (0); } /* * Fill the decompression buffer as needed... */ if (fp->stream.avail_in == 0) { if ((bytes = cups_read(fp, (char *)fp->cbuf, sizeof(fp->cbuf))) <= 0) { DEBUG_printf(("9cups_fill: cups_read error, returning %d.", (int)bytes)); fp->eof = 1; return (bytes); } fp->stream.next_in = fp->cbuf; fp->stream.avail_in = (uInt)bytes; } /* * Decompress data from the buffer... */ fp->stream.next_out = (Bytef *)fp->buf; fp->stream.avail_out = sizeof(fp->buf); status = inflate(&(fp->stream), Z_NO_FLUSH); if (fp->stream.next_out > (Bytef *)fp->buf) fp->crc = crc32(fp->crc, (Bytef *)fp->buf, (uInt)(fp->stream.next_out - (Bytef *)fp->buf)); if (status == Z_STREAM_END) { /* * Read the CRC and length... */ unsigned char trailer[8]; /* Trailer bytes */ uLong tcrc; /* Trailer CRC */ ssize_t tbytes = 0; /* Number of bytes */ if (fp->stream.avail_in > 0) { if (fp->stream.avail_in > sizeof(trailer)) tbytes = (ssize_t)sizeof(trailer); else tbytes = (ssize_t)fp->stream.avail_in; memcpy(trailer, fp->stream.next_in, (size_t)tbytes); fp->stream.next_in += tbytes; fp->stream.avail_in -= (size_t)tbytes; } if (tbytes < (ssize_t)sizeof(trailer)) { if (read(fp->fd, trailer + tbytes, sizeof(trailer) - (size_t)tbytes) < ((ssize_t)sizeof(trailer) - tbytes)) { /* * Can't get it, so mark end-of-file... */ DEBUG_puts("9cups_fill: Unable to read gzip CRC trailer, returning -1."); fp->eof = 1; errno = EIO; return (-1); } } tcrc = ((((((uLong)trailer[3] << 8) | (uLong)trailer[2]) << 8) | (uLong)trailer[1]) << 8) | (uLong)trailer[0]; if (tcrc != fp->crc) { /* * Bad CRC, mark end-of-file... */ DEBUG_printf(("9cups_fill: tcrc=%08x != fp->crc=%08x, returning -1.", (unsigned int)tcrc, (unsigned int)fp->crc)); fp->eof = 1; errno = EIO; return (-1); } /* * Otherwise, reset the compressed flag so that we re-read the * file header... */ inflateEnd(&fp->stream); fp->compressed = 0; } else if (status < Z_OK) { DEBUG_printf(("9cups_fill: inflate returned %d, returning -1.", status)); fp->eof = 1; errno = EIO; return (-1); } bytes = (ssize_t)sizeof(fp->buf) - (ssize_t)fp->stream.avail_out; /* * Return the decompressed data... */ fp->ptr = fp->buf; fp->end = fp->buf + bytes; if (bytes) { DEBUG_printf(("9cups_fill: Returning %d.", (int)bytes)); return (bytes); } } } #endif /* HAVE_LIBZ */ /* * Read a buffer's full of data... */ if ((bytes = cups_read(fp, fp->buf, sizeof(fp->buf))) <= 0) { /* * Can't read from file! */ fp->eof = 1; fp->ptr = fp->buf; fp->end = fp->buf; } else { /* * Return the bytes we read... */ fp->eof = 0; fp->ptr = fp->buf; fp->end = fp->buf + bytes; } DEBUG_printf(("9cups_fill: Not gzip, returning %d.", (int)bytes)); return (bytes); } /* * 'cups_open()' - Safely open a file for writing. * * We don't allow appending to directories or files that are hard-linked or * symlinked. */ static int /* O - File descriptor or -1 otherwise */ cups_open(const char *filename, /* I - Filename */ int mode) /* I - Open mode */ { int fd; /* File descriptor */ struct stat fileinfo; /* File information */ #ifndef _WIN32 struct stat linkinfo; /* Link information */ #endif /* !_WIN32 */ /* * Open the file... */ if ((fd = open(filename, mode, 0666)) < 0) return (-1); /* * Then verify that the file descriptor doesn't point to a directory or hard- * linked file. */ if (fstat(fd, &fileinfo)) { close(fd); return (-1); } if (fileinfo.st_nlink != 1) { close(fd); errno = EPERM; return (-1); } #ifdef _WIN32 if (fileinfo.st_mode & _S_IFDIR) #else if (S_ISDIR(fileinfo.st_mode)) #endif /* _WIN32 */ { close(fd); errno = EISDIR; return (-1); } #ifndef _WIN32 /* * Then use lstat to determine whether the filename is a symlink... */ if (lstat(filename, &linkinfo)) { close(fd); return (-1); } if (S_ISLNK(linkinfo.st_mode) || fileinfo.st_dev != linkinfo.st_dev || fileinfo.st_ino != linkinfo.st_ino || #ifdef HAVE_ST_GEN fileinfo.st_gen != linkinfo.st_gen || #endif /* HAVE_ST_GEN */ fileinfo.st_nlink != linkinfo.st_nlink || fileinfo.st_mode != linkinfo.st_mode) { /* * Yes, don't allow! */ close(fd); errno = EPERM; return (-1); } #endif /* !_WIN32 */ return (fd); } /* * 'cups_read()' - Read from a file descriptor. */ static ssize_t /* O - Number of bytes read or -1 */ cups_read(cups_file_t *fp, /* I - CUPS file */ char *buf, /* I - Buffer */ size_t bytes) /* I - Number bytes */ { ssize_t total; /* Total bytes read */ DEBUG_printf(("7cups_read(fp=%p, buf=%p, bytes=" CUPS_LLFMT ")", (void *)fp, (void *)buf, CUPS_LLCAST bytes)); /* * Loop until we read at least 0 bytes... */ for (;;) { #ifdef _WIN32 if (fp->mode == 's') total = (ssize_t)recv(fp->fd, buf, (unsigned)bytes, 0); else total = (ssize_t)read(fp->fd, buf, (unsigned)bytes); #else if (fp->mode == 's') total = recv(fp->fd, buf, bytes, 0); else total = read(fp->fd, buf, bytes); #endif /* _WIN32 */ DEBUG_printf(("9cups_read: total=" CUPS_LLFMT, CUPS_LLCAST total)); if (total >= 0) break; /* * Reads can be interrupted by signals and unavailable resources... */ if (errno == EAGAIN || errno == EINTR) continue; else return (-1); } /* * Return the total number of bytes read... */ return (total); } /* * 'cups_write()' - Write to a file descriptor. */ static ssize_t /* O - Number of bytes written or -1 */ cups_write(cups_file_t *fp, /* I - CUPS file */ const char *buf, /* I - Buffer */ size_t bytes) /* I - Number bytes */ { size_t total; /* Total bytes written */ ssize_t count; /* Count this time */ DEBUG_printf(("7cups_write(fp=%p, buf=%p, bytes=" CUPS_LLFMT ")", (void *)fp, (void *)buf, CUPS_LLCAST bytes)); /* * Loop until all bytes are written... */ total = 0; while (bytes > 0) { #ifdef _WIN32 if (fp->mode == 's') count = (ssize_t)send(fp->fd, buf, (unsigned)bytes, 0); else count = (ssize_t)write(fp->fd, buf, (unsigned)bytes); #else if (fp->mode == 's') count = send(fp->fd, buf, bytes, 0); else count = write(fp->fd, buf, bytes); #endif /* _WIN32 */ DEBUG_printf(("9cups_write: count=" CUPS_LLFMT, CUPS_LLCAST count)); if (count < 0) { /* * Writes can be interrupted by signals and unavailable resources... */ if (errno == EAGAIN || errno == EINTR) continue; else return (-1); } /* * Update the counts for the last write call... */ bytes -= (size_t)count; total += (size_t)count; buf += count; } /* * Return the total number of bytes written... */ return ((ssize_t)total); }