/* * PPD/driver support for CUPS. * * This program handles listing and installing static PPD files, PPD files * created from driver information files, and dynamically generated PPD files * using driver helper programs. * * Copyright © 2021-2023 by OpenPrinting. * Copyright © 2007-2019 by Apple Inc. * Copyright © 1997-2007 by Easy Software Products. * * Licensed under Apache License v2.0. See the file "LICENSE" for more * information. */ /* * Include necessary headers... */ #include "util.h" #include #include #include #include #include /* * Constants... */ #define PPD_SYNC 0x50504441 /* Sync word for ppds.dat (PPDA) */ #define PPD_MAX_LANG 32 /* Maximum languages */ #define PPD_MAX_PROD 32 /* Maximum products */ #define PPD_MAX_VERS 32 /* Maximum versions */ #define PPD_TYPE_POSTSCRIPT 0 /* PostScript PPD */ #define PPD_TYPE_PDF 1 /* PDF PPD */ #define PPD_TYPE_RASTER 2 /* CUPS raster PPD */ #define PPD_TYPE_FAX 3 /* Facsimile/MFD PPD */ #define PPD_TYPE_UNKNOWN 4 /* Other/hybrid PPD */ #define PPD_TYPE_DRV 5 /* Driver info file */ #define PPD_TYPE_ARCHIVE 6 /* Archive file */ #define TAR_BLOCK 512 /* Number of bytes in a block */ #define TAR_BLOCKS 10 /* Blocking factor */ #define TAR_MAGIC "ustar" /* 5 chars and a null */ #define TAR_VERSION "00" /* POSIX tar version */ #define TAR_OLDNORMAL '\0' /* Normal disk file, Unix compat */ #define TAR_NORMAL '0' /* Normal disk file */ #define TAR_LINK '1' /* Link to previously dumped file */ #define TAR_SYMLINK '2' /* Symbolic link */ #define TAR_CHR '3' /* Character special file */ #define TAR_BLK '4' /* Block special file */ #define TAR_DIR '5' /* Directory */ #define TAR_FIFO '6' /* FIFO special file */ #define TAR_CONTIG '7' /* Contiguous file */ /* * PPD information structures... */ typedef struct /**** PPD record ****/ { time_t mtime; /* Modification time */ off_t size; /* Size in bytes */ int model_number; /* cupsModelNumber */ int type; /* ppd-type */ char filename[512], /* Filename */ name[256], /* PPD name */ languages[PPD_MAX_LANG][6], /* LanguageVersion/cupsLanguages */ products[PPD_MAX_PROD][128], /* Product strings */ psversions[PPD_MAX_VERS][32], /* PSVersion strings */ make[128], /* Manufacturer */ make_and_model[128], /* NickName/ModelName */ device_id[256], /* IEEE 1284 Device ID */ scheme[128]; /* PPD scheme */ } ppd_rec_t; typedef struct /**** In-memory record ****/ { int found; /* 1 if PPD is found */ int matches; /* Match count */ ppd_rec_t record; /* PPDs.dat record */ } ppd_info_t; typedef union /**** TAR record format ****/ { unsigned char all[TAR_BLOCK]; /* Raw data block */ struct { char pathname[100], /* Destination path */ mode[8], /* Octal file permissions */ uid[8], /* Octal user ID */ gid[8], /* Octal group ID */ size[12], /* Octal size in bytes */ mtime[12], /* Octal modification time */ chksum[8], /* Octal checksum value */ linkflag, /* File type */ linkname[100], /* Source path for link */ magic[6], /* Magic string */ version[2], /* Format version */ uname[32], /* User name */ gname[32], /* Group name */ devmajor[8], /* Octal device major number */ devminor[8], /* Octal device minor number */ prefix[155]; /* Prefix for long filenames */ } header; } tar_rec_t; /* * Globals... */ static cups_array_t *Inodes = NULL, /* Inodes of directories we've visited */ *PPDsByName = NULL, /* PPD files sorted by filename and name */ *PPDsByMakeModel = NULL; /* PPD files sorted by make and model */ static int ChangedPPD; /* Did we change the PPD database? */ static const char * const PPDTypes[] = /* ppd-type values */ { "postscript", "pdf", "raster", "fax", "object", "object-direct", "object-storage", "unknown", "drv", "archive" }; /* * Local functions... */ static ppd_info_t *add_ppd(const char *filename, const char *name, const char *language, const char *make, const char *make_and_model, const char *device_id, const char *product, const char *psversion, time_t mtime, size_t size, int model_number, int type, const char *scheme); static int cat_drv(const char *name, int request_id); static void cat_ppd(const char *name, int request_id) _CUPS_NORETURN; static int cat_static(const char *name, int request_id); static int cat_tar(const char *name, int request_id); static int compare_inodes(struct stat *a, struct stat *b); static int compare_matches(const ppd_info_t *p0, const ppd_info_t *p1); static int compare_names(const ppd_info_t *p0, const ppd_info_t *p1); static int compare_ppds(const ppd_info_t *p0, const ppd_info_t *p1); static void dump_ppds_dat(const char *filename) _CUPS_NORETURN; static void free_array(cups_array_t *a); static cups_file_t *get_file(const char *name, int request_id, const char *subdir, char *buffer, size_t bufsize, char **subfile); static void list_ppds(int request_id, int limit, const char *opt) _CUPS_NORETURN; static int load_drivers(cups_array_t *include, cups_array_t *exclude); static int load_drv(const char *filename, const char *name, cups_file_t *fp, time_t mtime, off_t size); static void load_ppd(const char *filename, const char *name, const char *scheme, struct stat *fileinfo, ppd_info_t *ppd, cups_file_t *fp, off_t end); static int load_ppds(const char *d, const char *p, int descend); static void load_ppds_dat(char *filename, size_t filesize, int verbose); static int load_tar(const char *filename, const char *name, cups_file_t *fp, time_t mtime, off_t size); static int read_tar(cups_file_t *fp, char *name, size_t namesize, struct stat *info); static regex_t *regex_device_id(const char *device_id); static regex_t *regex_string(const char *s); /* * 'main()' - Scan for drivers and return an IPP response. * * Usage: * * cups-driverd request_id limit options */ int /* O - Exit code */ main(int argc, /* I - Number of command-line args */ char *argv[]) /* I - Command-line arguments */ { /* * Install or list PPDs... */ if (argc == 3 && !strcmp(argv[1], "cat")) cat_ppd(argv[2], 0); else if ((argc == 2 || argc == 3) && !strcmp(argv[1], "dump")) dump_ppds_dat(argv[2]); else if (argc == 4 && !strcmp(argv[1], "get")) cat_ppd(argv[3], atoi(argv[2])); else if (argc == 5 && !strcmp(argv[1], "list")) list_ppds(atoi(argv[2]), atoi(argv[3]), argv[4]); else { fputs("Usage: cups-driverd cat ppd-name\n", stderr); fputs("Usage: cups-driverd dump\n", stderr); fputs("Usage: cups-driverd get request_id ppd-name\n", stderr); fputs("Usage: cups-driverd list request_id limit options\n", stderr); return (1); } } /* * 'add_ppd()' - Add a PPD file. */ static ppd_info_t * /* O - PPD */ add_ppd(const char *filename, /* I - PPD filename */ const char *name, /* I - PPD name */ const char *language, /* I - LanguageVersion */ const char *make, /* I - Manufacturer */ const char *make_and_model, /* I - NickName/ModelName */ const char *device_id, /* I - 1284DeviceID */ const char *product, /* I - Product */ const char *psversion, /* I - PSVersion */ time_t mtime, /* I - Modification time */ size_t size, /* I - File size */ int model_number, /* I - Model number */ int type, /* I - Driver type */ const char *scheme) /* I - PPD scheme */ { ppd_info_t *ppd; /* PPD */ char *recommended; /* Foomatic driver string */ /* * Add a new PPD file... */ if ((ppd = (ppd_info_t *)calloc(1, sizeof(ppd_info_t))) == NULL) { fprintf(stderr, "ERROR: [cups-driverd] Ran out of memory for %d PPD files!\n", cupsArrayCount(PPDsByName)); return (NULL); } /* * Zero-out the PPD data and copy the values over... */ ppd->found = 1; ppd->record.mtime = mtime; ppd->record.size = (off_t)size; ppd->record.model_number = model_number; ppd->record.type = type; strlcpy(ppd->record.filename, filename, sizeof(ppd->record.filename)); strlcpy(ppd->record.name, name, sizeof(ppd->record.name)); strlcpy(ppd->record.languages[0], language, sizeof(ppd->record.languages[0])); strlcpy(ppd->record.products[0], product, sizeof(ppd->record.products[0])); strlcpy(ppd->record.psversions[0], psversion, sizeof(ppd->record.psversions[0])); strlcpy(ppd->record.make, make, sizeof(ppd->record.make)); strlcpy(ppd->record.make_and_model, make_and_model, sizeof(ppd->record.make_and_model)); strlcpy(ppd->record.device_id, device_id, sizeof(ppd->record.device_id)); strlcpy(ppd->record.scheme, scheme, sizeof(ppd->record.scheme)); /* * Strip confusing (and often wrong) "recommended" suffix added by * Foomatic drivers... */ if ((recommended = strstr(ppd->record.make_and_model, " (recommended)")) != NULL) *recommended = '\0'; /* * Add the PPD to the PPD arrays... */ cupsArrayAdd(PPDsByName, ppd); cupsArrayAdd(PPDsByMakeModel, ppd); /* * Return the new PPD pointer... */ return (ppd); } /* * 'cat_drv()' - Generate a PPD from a driver info file. */ static int /* O - Exit code */ cat_drv(const char *name, /* I - PPD name */ int request_id) /* I - Request ID for response? */ { cups_file_t *fp; // File pointer ppdcSource *src; // PPD source file data ppdcDriver *d; // Current driver cups_file_t *out; // Stdout via CUPS file API char message[2048], // status-message filename[1024], // Full path to .drv file(s) scheme[32], // URI scheme ("drv") userpass[256], // User/password info (unused) host[2], // Hostname (unused) resource[1024], // Resource path (/dir/to/filename.drv) *pc_file_name; // Filename portion of URI int port; // Port number (unused) // Pull out the path to the .drv file... if (httpSeparateURI(HTTP_URI_CODING_ALL, name, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)) < HTTP_URI_OK) { fprintf(stderr, "ERROR: Bad PPD name \"%s\".\n", name); if (request_id) { snprintf(message, sizeof(message), "Bad PPD name \"%s\".", name); cupsdSendIPPHeader(IPP_NOT_FOUND, request_id); cupsdSendIPPGroup(IPP_TAG_OPERATION); cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8"); cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US"); cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message); cupsdSendIPPTrailer(); } return (1); } if ((fp = get_file(resource, request_id, "drv", filename, sizeof(filename), &pc_file_name)) == NULL || !pc_file_name) return (1); src = new ppdcSource(filename, fp); for (d = (ppdcDriver *)src->drivers->first(); d; d = (ppdcDriver *)src->drivers->next()) if (!strcmp(pc_file_name, d->pc_file_name->value) || (d->file_name && !strcmp(pc_file_name, d->file_name->value))) break; if (d) { ppdcArray *locales; // Locale names ppdcCatalog *catalog; // Message catalog in .drv file fprintf(stderr, "DEBUG2: [cups-driverd] %u locales defined in \"%s\"...\n", (unsigned)src->po_files->count, filename); locales = new ppdcArray(); for (catalog = (ppdcCatalog *)src->po_files->first(); catalog; catalog = (ppdcCatalog *)src->po_files->next()) { fprintf(stderr, "DEBUG2: [cups-driverd] Adding locale \"%s\"...\n", catalog->locale->value); catalog->locale->retain(); locales->add(catalog->locale); } if (request_id) { cupsdSendIPPHeader(IPP_OK, request_id); cupsdSendIPPGroup(IPP_TAG_OPERATION); cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8"); cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US"); cupsdSendIPPTrailer(); fflush(stdout); } out = cupsFileStdout(); d->write_ppd_file(out, NULL, locales, src, PPDC_LFONLY); cupsFileClose(out); locales->release(); } else { fprintf(stderr, "ERROR: PPD \"%s\" not found.\n", name); if (request_id) { snprintf(message, sizeof(message), "PPD \"%s\" not found.", name); cupsdSendIPPHeader(IPP_NOT_FOUND, request_id); cupsdSendIPPGroup(IPP_TAG_OPERATION); cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8"); cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US"); cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message); cupsdSendIPPTrailer(); } } src->release(); cupsFileClose(fp); return (!d); } /* * 'cat_ppd()' - Copy a PPD file to stdout. */ static void cat_ppd(const char *name, /* I - PPD name */ int request_id) /* I - Request ID for response? */ { char scheme[256], /* Scheme from PPD name */ *sptr, /* Pointer into scheme */ line[1024], /* Line/filename */ message[2048]; /* status-message */ /* * Figure out if this is a static or dynamic PPD file... */ if (strstr(name, "../")) { fputs("ERROR: Invalid PPD name.\n", stderr); exit(1); } strlcpy(scheme, name, sizeof(scheme)); if ((sptr = strchr(scheme, ':')) != NULL) { *sptr = '\0'; if (!strcmp(scheme, "file")) { /* * "file:name" == "name"... */ name += 5; while (*name == '/') name ++; if (!strstr(name, ".tar/") && !strstr(name, ".tar.gz/")) scheme[0] = '\0'; } } else scheme[0] = '\0'; if (request_id > 0) puts("Content-Type: application/ipp\n"); if (!scheme[0]) exit(cat_static(name, request_id)); else if (!strcmp(scheme, "drv")) exit(cat_drv(name, request_id)); else if (!strcmp(scheme, "file")) exit(cat_tar(name, request_id)); else { /* * Dynamic PPD, see if we have a driver program to support it... */ const char *serverbin; /* CUPS_SERVERBIN env var */ char *argv[4]; /* Arguments for program */ if ((serverbin = getenv("CUPS_SERVERBIN")) == NULL) serverbin = CUPS_SERVERBIN; snprintf(line, sizeof(line), "%s/driver/%s", serverbin, scheme); if (access(line, X_OK)) { /* * File does not exist or is not executable... */ fprintf(stderr, "ERROR: [cups-driverd] Unable to access \"%s\" - %s\n", line, strerror(errno)); if (request_id > 0) { snprintf(message, sizeof(message), "Unable to access \"%s\" - %s", line, strerror(errno)); cupsdSendIPPHeader(IPP_NOT_FOUND, request_id); cupsdSendIPPGroup(IPP_TAG_OPERATION); cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8"); cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US"); cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message); cupsdSendIPPTrailer(); } exit(1); } /* * Yes, let it cat the PPD file... */ if (request_id) { cupsdSendIPPHeader(IPP_OK, request_id); cupsdSendIPPGroup(IPP_TAG_OPERATION); cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8"); cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US"); cupsdSendIPPTrailer(); } argv[0] = scheme; argv[1] = (char *)"cat"; argv[2] = (char *)name; argv[3] = NULL; if (cupsdExec(line, argv)) { /* * Unable to execute driver... */ fprintf(stderr, "ERROR: [cups-driverd] Unable to execute \"%s\" - %s\n", line, strerror(errno)); exit(1); } } /* * Exit with no errors... */ exit(0); } /* * 'copy_static()' - Copy a static PPD file to stdout. */ static int /* O - Exit code */ cat_static(const char *name, /* I - PPD name */ int request_id) /* I - Request ID for response? */ { cups_file_t *fp; /* PPD file */ char filename[1024], /* PPD filename */ line[1024]; /* Line buffer */ if ((fp = get_file(name, request_id, "model", filename, sizeof(filename), NULL)) == NULL) return (1); if (request_id) { cupsdSendIPPHeader(IPP_OK, request_id); cupsdSendIPPGroup(IPP_TAG_OPERATION); cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8"); cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US"); cupsdSendIPPTrailer(); } /* * Now copy the file to stdout... */ while (cupsFileGets(fp, line, sizeof(line))) puts(line); cupsFileClose(fp); return (0); } /* * 'cat_tar()' - Copy an archived PPD file to stdout. */ static int /* O - Exit code */ cat_tar(const char *name, /* I - PPD name */ int request_id) /* I - Request ID */ { cups_file_t *fp; /* Archive file pointer */ char filename[1024], /* Archive filename */ *ppdname, /* PPD filename in archive */ curname[256], /* Current name in archive */ buffer[8192]; /* Copy buffer */ struct stat curinfo; /* Current file info in archive */ off_t total, /* Total bytes copied */ next; /* Offset for next record in archive */ ssize_t bytes; /* Bytes read */ /* * Open the archive file... */ if ((fp = get_file(name, request_id, "model", filename, sizeof(filename), &ppdname)) == NULL || !ppdname) return (1); /* * Scan the archive for the PPD... */ while (read_tar(fp, curname, sizeof(curname), &curinfo)) { next = cupsFileTell(fp) + ((curinfo.st_size + TAR_BLOCK - 1) & ~(TAR_BLOCK - 1)); if (!strcmp(ppdname, curname)) { if (request_id) { cupsdSendIPPHeader(IPP_OK, request_id); cupsdSendIPPGroup(IPP_TAG_OPERATION); cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8"); cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US"); cupsdSendIPPTrailer(); } for (total = 0; total < curinfo.st_size; total += bytes) { if ((size_t)(bytes = (curinfo.st_size - total)) > sizeof(buffer)) bytes = sizeof(buffer); if ((bytes = cupsFileRead(fp, buffer, (size_t)bytes)) < 0) { if (errno == EINTR || errno == EAGAIN) { bytes = 0; } else { perror("ERROR: [cups-driverd] Read error"); break; } } else if (bytes > 0 && fwrite(buffer, (size_t)bytes, 1, stdout) != 1) break; } cupsFileClose(fp); return (0); } if (cupsFileTell(fp) != next) { if (cupsFileSeek(fp, next) != next) break; } } cupsFileClose(fp); fprintf(stderr, "ERROR: PPD \"%s\" not found.\n", name); if (request_id) { snprintf(buffer, sizeof(buffer), "PPD \"%s\" not found.", name); cupsdSendIPPHeader(IPP_NOT_FOUND, request_id); cupsdSendIPPGroup(IPP_TAG_OPERATION); cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8"); cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US"); cupsdSendIPPString(IPP_TAG_TEXT, "status-message", buffer); cupsdSendIPPTrailer(); } return (1); } /* * 'compare_inodes()' - Compare two inodes. */ static int /* O - Result of comparison */ compare_inodes(struct stat *a, /* I - First inode */ struct stat *b) /* I - Second inode */ { if (a->st_dev != b->st_dev) return (a->st_dev - b->st_dev); else return (a->st_ino - b->st_ino); } /* * 'compare_matches()' - Compare PPD match scores for sorting. */ static int compare_matches(const ppd_info_t *p0, /* I - First PPD */ const ppd_info_t *p1) /* I - Second PPD */ { if (p1->matches != p0->matches) return (p1->matches - p0->matches); else return (cupsdCompareNames(p0->record.make_and_model, p1->record.make_and_model)); } /* * 'compare_names()' - Compare PPD filenames for sorting. */ static int /* O - Result of comparison */ compare_names(const ppd_info_t *p0, /* I - First PPD file */ const ppd_info_t *p1) /* I - Second PPD file */ { int diff; /* Difference between strings */ if ((diff = strcmp(p0->record.filename, p1->record.filename)) != 0) return (diff); else return (strcmp(p0->record.name, p1->record.name)); } /* * 'compare_ppds()' - Compare PPD file make and model names for sorting. */ static int /* O - Result of comparison */ compare_ppds(const ppd_info_t *p0, /* I - First PPD file */ const ppd_info_t *p1) /* I - Second PPD file */ { int diff; /* Difference between strings */ /* * First compare manufacturers... */ if ((diff = _cups_strcasecmp(p0->record.make, p1->record.make)) != 0) return (diff); else if ((diff = cupsdCompareNames(p0->record.make_and_model, p1->record.make_and_model)) != 0) return (diff); else if ((diff = strcmp(p0->record.languages[0], p1->record.languages[0])) != 0) return (diff); else return (compare_names(p0, p1)); } /* * 'dump_ppds_dat()' - Dump the contents of the ppds.dat file. */ static void dump_ppds_dat(const char *filename) /* I - Filename */ { char temp[1024]; /* ppds.dat filename */ ppd_info_t *ppd; /* Current PPD */ /* * See if we a PPD database file... */ if (filename) strlcpy(temp, filename, sizeof(temp)); else temp[0] = '\0'; load_ppds_dat(temp, sizeof(temp), 0); puts("mtime,size,model_number,type,filename,name,languages0,products0," "psversions0,make,make_and_model,device_id,scheme"); for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByName); ppd; ppd = (ppd_info_t *)cupsArrayNext(PPDsByName)) printf("%d,%ld,%d,%d,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"," "\"%s\",\"%s\"\n", (int)ppd->record.mtime, (long)ppd->record.size, ppd->record.model_number, ppd->record.type, ppd->record.filename, ppd->record.name, ppd->record.languages[0], ppd->record.products[0], ppd->record.psversions[0], ppd->record.make, ppd->record.make_and_model, ppd->record.device_id, ppd->record.scheme); exit(0); } /* * 'free_array()' - Free an array of strings. */ static void free_array(cups_array_t *a) /* I - Array to free */ { char *ptr; /* Pointer to string */ for (ptr = (char *)cupsArrayFirst(a); ptr; ptr = (char *)cupsArrayNext(a)) free(ptr); cupsArrayDelete(a); } /* * 'get_file()' - Get the filename associated with a request. */ static cups_file_t * /* O - File pointer or NULL */ get_file(const char *name, /* I - Name */ int request_id, /* I - Request ID */ const char *subdir, /* I - Subdirectory for file */ char *buffer, /* I - Filename buffer */ size_t bufsize, /* I - Size of filename buffer */ char **subfile) /* O - Sub-filename */ { cups_file_t *fp; /* File pointer */ const char *datadir; /* CUPS_DATADIR env var */ char *bufptr, /* Pointer into filename buffer */ message[2048]; /* status-message */ #ifdef __APPLE__ const char *printerDriver, /* Pointer to .printerDriver extension */ *slash; /* Pointer to next slash */ #endif /* __APPLE__ */ if (subfile) *subfile = NULL; while (*name == '/') name ++; if (strstr(name, "../") || strstr(name, "/..")) { /* * Bad name... */ fprintf(stderr, "ERROR: [cups-driverd] Bad PPD name \"%s\".\n", name); if (request_id) { snprintf(message, sizeof(message), "Bad PPD name \"%s\".", name); cupsdSendIPPHeader(IPP_NOT_FOUND, request_id); cupsdSendIPPGroup(IPP_TAG_OPERATION); cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8"); cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US"); cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message); cupsdSendIPPTrailer(); } return (NULL); } /* * Try opening the file... */ #ifdef __APPLE__ if (!strncmp(name, "System/Library/Printers/PPDs/Contents/Resources/", 48) || !strncmp(name, "Library/Printers/PPDs/Contents/Resources/", 41) || (!strncmp(name, "System/Library/Printers/", 24) && (printerDriver = strstr(name + 24, ".printerDriver/Contents/Resources/PPDs")) != NULL && (slash = strchr(name + 24, '/')) != NULL && slash > printerDriver) || (!strncmp(name, "Library/Printers/", 17) && (printerDriver = strstr(name + 17, ".printerDriver/Contents/Resources/PPDs")) != NULL && (slash = strchr(name + 17, '/')) != NULL && slash > printerDriver)) { /* * Map ppd-name to macOS standard locations... */ snprintf(buffer, bufsize, "/%s", name); } else #elif defined(__linux) if (!strncmp(name, "lsb/usr/", 8)) { /* * Map ppd-name to LSB standard /usr/share/ppd location... */ snprintf(buffer, bufsize, "/usr/share/ppd/%s", name + 8); } else if (!strncmp(name, "lsb/opt/", 8)) { /* * Map ppd-name to LSB standard /opt/share/ppd location... */ snprintf(buffer, bufsize, "/opt/share/ppd/%s", name + 8); } else if (!strncmp(name, "lsb/local/", 10)) { /* * Map ppd-name to LSB standard /usr/local/share/ppd location... */ snprintf(buffer, bufsize, "/usr/local/share/ppd/%s", name + 10); } else #endif /* __APPLE__ */ { if ((datadir = getenv("CUPS_DATADIR")) == NULL) datadir = CUPS_DATADIR; snprintf(buffer, bufsize, "%s/%s/%s", datadir, subdir, name); } /* * Strip anything after ".drv/", ".drv.gz/", ".tar/", or ".tar.gz/"... */ if (subfile) { if ((bufptr = strstr(buffer, ".drv/")) != NULL) bufptr += 4; else if ((bufptr = strstr(buffer, ".drv.gz/")) != NULL) bufptr += 7; else if ((bufptr = strstr(buffer, ".tar/")) != NULL) bufptr += 4; else if ((bufptr = strstr(buffer, ".tar.gz/")) != NULL) bufptr += 7; if (bufptr) { *bufptr++ = '\0'; *subfile = bufptr; } } /* * Try opening the file... */ if ((fp = cupsFileOpen(buffer, "r")) == NULL) { fprintf(stderr, "ERROR: [cups-driverd] Unable to open \"%s\" - %s\n", buffer, strerror(errno)); if (request_id) { snprintf(message, sizeof(message), "Unable to open \"%s\" - %s", buffer, strerror(errno)); cupsdSendIPPHeader(IPP_NOT_FOUND, request_id); cupsdSendIPPGroup(IPP_TAG_OPERATION); cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8"); cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US"); cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message); cupsdSendIPPTrailer(); } return (NULL); } return (fp); } /* * 'list_ppds()' - List PPD files. */ static void list_ppds(int request_id, /* I - Request ID */ int limit, /* I - Limit */ const char *opt) /* I - Option argument */ { int i; /* Looping vars */ int count; /* Number of PPDs to send */ ppd_info_t *ppd; /* Current PPD file */ cups_file_t *fp; /* ppds.dat file */ char filename[1024], /* ppds.dat filename */ model[1024]; /* Model directory */ const char *cups_datadir; /* CUPS_DATADIR environment variable */ int num_options; /* Number of options */ cups_option_t *options; /* Options */ cups_array_t *requested, /* requested-attributes values */ *include, /* PPD schemes to include */ *exclude; /* PPD schemes to exclude */ const char *device_id, /* ppd-device-id option */ *language, /* ppd-natural-language option */ *make, /* ppd-make option */ *make_and_model, /* ppd-make-and-model option */ *model_number_str, /* ppd-model-number option */ *product, /* ppd-product option */ *psversion, /* ppd-psversion option */ *type_str; /* ppd-type option */ int model_number, /* ppd-model-number value */ type, /* ppd-type value */ send_device_id, /* Send ppd-device-id? */ send_make, /* Send ppd-make? */ send_make_and_model, /* Send ppd-make-and-model? */ send_model_number, /* Send ppd-model-number? */ send_name, /* Send ppd-name? */ send_natural_language, /* Send ppd-natural-language? */ send_product, /* Send ppd-product? */ send_psversion, /* Send ppd-psversion? */ send_type, /* Send ppd-type? */ sent_header; /* Sent the IPP header? */ size_t make_and_model_len, /* Length of ppd-make-and-model */ product_len; /* Length of ppd-product */ regex_t *device_id_re, /* Regular expression for matching device ID */ *make_and_model_re; /* Regular expression for matching make and model */ regmatch_t re_matches[6]; /* Regular expression matches */ cups_array_t *matches; /* Matching PPDs */ fprintf(stderr, "DEBUG2: [cups-driverd] list_ppds(request_id=%d, limit=%d, " "opt=\"%s\"\n", request_id, limit, opt); /* * See if we a PPD database file... */ filename[0] = '\0'; load_ppds_dat(filename, sizeof(filename), 1); /* * Load all PPDs in the specified directory and below... */ if ((cups_datadir = getenv("CUPS_DATADIR")) == NULL) cups_datadir = CUPS_DATADIR; Inodes = cupsArrayNew((cups_array_func_t)compare_inodes, NULL); snprintf(model, sizeof(model), "%s/model", cups_datadir); load_ppds(model, "", 1); snprintf(model, sizeof(model), "%s/drv", cups_datadir); load_ppds(model, "", 1); #ifdef __APPLE__ /* * Load PPDs from standard macOS locations... */ load_ppds("/Library/Printers", "Library/Printers", 0); load_ppds("/Library/Printers/PPDs/Contents/Resources", "Library/Printers/PPDs/Contents/Resources", 0); load_ppds("/Library/Printers/PPDs/Contents/Resources/en.lproj", "Library/Printers/PPDs/Contents/Resources/en.lproj", 0); load_ppds("/System/Library/Printers", "System/Library/Printers", 0); load_ppds("/System/Library/Printers/PPDs/Contents/Resources", "System/Library/Printers/PPDs/Contents/Resources", 0); load_ppds("/System/Library/Printers/PPDs/Contents/Resources/en.lproj", "System/Library/Printers/PPDs/Contents/Resources/en.lproj", 0); #elif defined(__linux) /* * Load PPDs from LSB-defined locations... */ if (!access("/usr/local/share/ppd", 0)) load_ppds("/usr/local/share/ppd", "lsb/local", 1); if (!access("/usr/share/ppd", 0)) load_ppds("/usr/share/ppd", "lsb/usr", 1); if (!access("/opt/share/ppd", 0)) load_ppds("/opt/share/ppd", "lsb/opt", 1); #endif /* __APPLE__ */ /* * Cull PPD files that are no longer present... */ for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByName); ppd; ppd = (ppd_info_t *)cupsArrayNext(PPDsByName)) if (!ppd->found) { /* * Remove this PPD file from the list... */ cupsArrayRemove(PPDsByName, ppd); cupsArrayRemove(PPDsByMakeModel, ppd); free(ppd); ChangedPPD = 1; } /* * Write the new ppds.dat file... */ fprintf(stderr, "DEBUG: [cups-driverd] ChangedPPD=%d\n", ChangedPPD); if (ChangedPPD) { char newname[1024]; /* New filename */ snprintf(newname, sizeof(newname), "%s.%d", filename, (int)getpid()); if ((fp = cupsFileOpen(newname, "w")) != NULL) { unsigned ppdsync = PPD_SYNC; /* Sync word */ cupsFileWrite(fp, (char *)&ppdsync, sizeof(ppdsync)); for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByName); ppd; ppd = (ppd_info_t *)cupsArrayNext(PPDsByName)) cupsFileWrite(fp, (char *)&(ppd->record), sizeof(ppd_rec_t)); cupsFileClose(fp); if (rename(newname, filename)) fprintf(stderr, "ERROR: [cups-driverd] Unable to rename \"%s\" - %s\n", newname, strerror(errno)); else fprintf(stderr, "INFO: [cups-driverd] Wrote \"%s\", %d PPDs...\n", filename, cupsArrayCount(PPDsByName)); } else fprintf(stderr, "ERROR: [cups-driverd] Unable to write \"%s\" - %s\n", filename, strerror(errno)); } else fputs("INFO: [cups-driverd] No new or changed PPDs...\n", stderr); /* * Scan for dynamic PPD files... */ num_options = cupsParseOptions(opt, 0, &options); exclude = cupsdCreateStringsArray(cupsGetOption("exclude-schemes", num_options, options)); include = cupsdCreateStringsArray(cupsGetOption("include-schemes", num_options, options)); load_drivers(include, exclude); /* * Add the raw driver... */ add_ppd("", "raw", "en", "Raw", "Raw Queue", "", "", "", 0, 0, 0, PPD_TYPE_UNKNOWN, "raw"); /* * Send IPP attributes... */ requested = cupsdCreateStringsArray( cupsGetOption("requested-attributes", num_options, options)); device_id = cupsGetOption("ppd-device-id", num_options, options); language = cupsGetOption("ppd-natural-language", num_options, options); make = cupsGetOption("ppd-make", num_options, options); make_and_model = cupsGetOption("ppd-make-and-model", num_options, options); model_number_str = cupsGetOption("ppd-model-number", num_options, options); product = cupsGetOption("ppd-product", num_options, options); psversion = cupsGetOption("ppd-psversion", num_options, options); type_str = cupsGetOption("ppd-type", num_options, options); if (make_and_model) make_and_model_len = strlen(make_and_model); else make_and_model_len = 0; if (product) product_len = strlen(product); else product_len = 0; if (model_number_str) model_number = atoi(model_number_str); else model_number = 0; if (type_str) { for (type = 0; type < (int)(sizeof(PPDTypes) / sizeof(PPDTypes[0])); type ++) if (!strcmp(type_str, PPDTypes[type])) break; if (type >= (int)(sizeof(PPDTypes) / sizeof(PPDTypes[0]))) { fprintf(stderr, "ERROR: [cups-driverd] Bad ppd-type=\"%s\" ignored!\n", type_str); type_str = NULL; } } else type = 0; for (i = 0; i < num_options; i ++) fprintf(stderr, "DEBUG2: [cups-driverd] %s=\"%s\"\n", options[i].name, options[i].value); if (!requested || cupsArrayFind(requested, (void *)"all") != NULL) { send_name = 1; send_make = 1; send_make_and_model = 1; send_model_number = 1; send_natural_language = 1; send_device_id = 1; send_product = 1; send_psversion = 1; send_type = 1; } else { send_name = cupsArrayFind(requested, (void *)"ppd-name") != NULL; send_make = cupsArrayFind(requested, (void *)"ppd-make") != NULL; send_make_and_model = cupsArrayFind(requested, (void *)"ppd-make-and-model") != NULL; send_model_number = cupsArrayFind(requested, (void *)"ppd-model-number") != NULL; send_natural_language = cupsArrayFind(requested, (void *)"ppd-natural-language") != NULL; send_device_id = cupsArrayFind(requested, (void *)"ppd-device-id") != NULL; send_product = cupsArrayFind(requested, (void *)"ppd-product") != NULL; send_psversion = cupsArrayFind(requested, (void *)"ppd-psversion") != NULL; send_type = cupsArrayFind(requested, (void *)"ppd-type") != NULL; } /* * Send the content type header to the scheduler; request_id can only be * 0 when run manually since the scheduler enforces the IPP requirement for * a request ID from 1 to 2^31-1... */ if (request_id > 0) puts("Content-Type: application/ipp\n"); sent_header = 0; if (limit <= 0 || limit > cupsArrayCount(PPDsByMakeModel)) count = cupsArrayCount(PPDsByMakeModel); else count = limit; if (device_id || language || make || make_and_model || model_number_str || product) { matches = cupsArrayNew((cups_array_func_t)compare_matches, NULL); if (device_id) device_id_re = regex_device_id(device_id); else device_id_re = NULL; if (make_and_model) make_and_model_re = regex_string(make_and_model); else make_and_model_re = NULL; for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByMakeModel); ppd; ppd = (ppd_info_t *)cupsArrayNext(PPDsByMakeModel)) { /* * Filter PPDs based on make, model, product, language, model number, * and/or device ID using the "matches" score value. An exact match * for product, make-and-model, or device-id adds 3 to the score. * Partial matches for make-and-model yield 1 or 2 points, and matches * for the make and language add a single point. Results are then sorted * by score, highest score first. */ if (ppd->record.type < PPD_TYPE_POSTSCRIPT || ppd->record.type >= PPD_TYPE_DRV) continue; if (cupsArrayFind(exclude, ppd->record.scheme) || (include && !cupsArrayFind(include, ppd->record.scheme))) continue; ppd->matches = 0; if (device_id_re && !regexec(device_id_re, ppd->record.device_id, (size_t)(sizeof(re_matches) / sizeof(re_matches[0])), re_matches, 0)) { /* * Add the number of matching values from the device ID - it will be * at least 2 (manufacturer and model), and as much as 3 (command set). */ for (i = 1; i < (int)(sizeof(re_matches) / sizeof(re_matches[0])); i ++) if (re_matches[i].rm_so >= 0) ppd->matches ++; } if (language) { for (i = 0; i < PPD_MAX_LANG; i ++) if (!ppd->record.languages[i][0]) break; else if (!strcmp(ppd->record.languages[i], language)) { ppd->matches ++; break; } } if (make && !_cups_strcasecmp(ppd->record.make, make)) ppd->matches ++; if (make_and_model_re && !regexec(make_and_model_re, ppd->record.make_and_model, (size_t)(sizeof(re_matches) / sizeof(re_matches[0])), re_matches, 0)) { // See how much of the make-and-model string we matched... if (re_matches[0].rm_so == 0) { if ((size_t)re_matches[0].rm_eo == make_and_model_len) ppd->matches += 3; // Exact match else ppd->matches += 2; // Prefix match } else ppd->matches ++; // Infix match } if (model_number_str && ppd->record.model_number == model_number) ppd->matches ++; if (product) { for (i = 0; i < PPD_MAX_PROD; i ++) if (!ppd->record.products[i][0]) break; else if (!_cups_strcasecmp(ppd->record.products[i], product)) { ppd->matches += 3; break; } else if (!_cups_strncasecmp(ppd->record.products[i], product, product_len)) { ppd->matches += 2; break; } } if (psversion) { for (i = 0; i < PPD_MAX_VERS; i ++) if (!ppd->record.psversions[i][0]) break; else if (!_cups_strcasecmp(ppd->record.psversions[i], psversion)) { ppd->matches ++; break; } } if (type_str && ppd->record.type == type) ppd->matches ++; if (ppd->matches) { fprintf(stderr, "DEBUG2: [cups-driverd] %s matches with score %d!\n", ppd->record.name, ppd->matches); cupsArrayAdd(matches, ppd); } } } else if (include || exclude) { matches = cupsArrayNew((cups_array_func_t)compare_ppds, NULL); for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByMakeModel); ppd; ppd = (ppd_info_t *)cupsArrayNext(PPDsByMakeModel)) { /* * Filter PPDs based on the include/exclude lists. */ if (ppd->record.type < PPD_TYPE_POSTSCRIPT || ppd->record.type >= PPD_TYPE_DRV) continue; if (cupsArrayFind(exclude, ppd->record.scheme) || (include && !cupsArrayFind(include, ppd->record.scheme))) continue; cupsArrayAdd(matches, ppd); } } else matches = PPDsByMakeModel; for (ppd = (ppd_info_t *)cupsArrayFirst(matches); count > 0 && ppd; ppd = (ppd_info_t *)cupsArrayNext(matches)) { /* * Skip invalid PPDs... */ if (ppd->record.type < PPD_TYPE_POSTSCRIPT || ppd->record.type >= PPD_TYPE_DRV) continue; /* * Send this PPD... */ if (!sent_header) { sent_header = 1; if (request_id) { cupsdSendIPPHeader(IPP_OK, request_id); cupsdSendIPPGroup(IPP_TAG_OPERATION); cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8"); cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US"); } } fprintf(stderr, "DEBUG2: [cups-driverd] Sending %s (%s)...\n", ppd->record.name, ppd->record.make_and_model); count --; if (request_id) { cupsdSendIPPGroup(IPP_TAG_PRINTER); if (send_name) cupsdSendIPPString(IPP_TAG_NAME, "ppd-name", ppd->record.name); if (send_natural_language) { cupsdSendIPPString(IPP_TAG_LANGUAGE, "ppd-natural-language", ppd->record.languages[0]); for (i = 1; i < PPD_MAX_LANG && ppd->record.languages[i][0]; i ++) cupsdSendIPPString(IPP_TAG_LANGUAGE, "", ppd->record.languages[i]); } if (send_make) cupsdSendIPPString(IPP_TAG_TEXT, "ppd-make", ppd->record.make); if (send_make_and_model) cupsdSendIPPString(IPP_TAG_TEXT, "ppd-make-and-model", ppd->record.make_and_model); if (send_device_id) cupsdSendIPPString(IPP_TAG_TEXT, "ppd-device-id", ppd->record.device_id); if (send_product) { cupsdSendIPPString(IPP_TAG_TEXT, "ppd-product", ppd->record.products[0]); for (i = 1; i < PPD_MAX_PROD && ppd->record.products[i][0]; i ++) cupsdSendIPPString(IPP_TAG_TEXT, "", ppd->record.products[i]); } if (send_psversion) { cupsdSendIPPString(IPP_TAG_TEXT, "ppd-psversion", ppd->record.psversions[0]); for (i = 1; i < PPD_MAX_VERS && ppd->record.psversions[i][0]; i ++) cupsdSendIPPString(IPP_TAG_TEXT, "", ppd->record.psversions[i]); } if (send_type) { if (ppd->record.type < PPD_TYPE_POSTSCRIPT || ppd->record.type > PPD_TYPE_ARCHIVE) { /* * This cache file is corrupted, remove it! */ unlink(filename); cupsdSendIPPString(IPP_TAG_KEYWORD, "ppd-type", PPDTypes[PPD_TYPE_UNKNOWN]); } else cupsdSendIPPString(IPP_TAG_KEYWORD, "ppd-type", PPDTypes[ppd->record.type]); } if (send_model_number) cupsdSendIPPInteger(IPP_TAG_INTEGER, "ppd-model-number", ppd->record.model_number); } else printf("%s (%s)\n", ppd->record.name, ppd->record.make_and_model); /* * If we have only requested the ppd-make attribute, then skip * the remaining PPDs with this make... */ if (cupsArrayFind(requested, (void *)"ppd-make") && cupsArrayCount(requested) == 1) { const char *this_make; /* This ppd-make */ for (this_make = ppd->record.make, ppd = (ppd_info_t *)cupsArrayNext(matches); ppd; ppd = (ppd_info_t *)cupsArrayNext(matches)) if (_cups_strcasecmp(this_make, ppd->record.make)) break; cupsArrayPrev(matches); } } if (!sent_header && request_id) { cupsdSendIPPHeader(IPP_NOT_FOUND, request_id); cupsdSendIPPGroup(IPP_TAG_OPERATION); cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8"); cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US"); } if (request_id) cupsdSendIPPTrailer(); exit(0); } /* * 'load_drv()' - Load the PPDs from a driver information file. */ static int /* O - 1 on success, 0 on failure */ load_drv(const char *filename, /* I - Actual filename */ const char *name, /* I - Name to the rest of the world */ cups_file_t *fp, /* I - File to read from */ time_t mtime, /* I - Mod time of driver info file */ off_t size) /* I - Size of driver info file */ { ppdcSource *src; // Driver information file ppdcDriver *d; // Current driver ppdcAttr *device_id, // 1284DeviceID attribute *product, // Current product value *ps_version, // PSVersion attribute *cups_fax, // cupsFax attribute *nick_name; // NickName attribute ppdcFilter *filter; // Current filter ppd_info_t *ppd; // Current PPD int products_found; // Number of products found char uri[1024], // Driver URI make_model[1024]; // Make and model int type; // Driver type /* * Load the driver info file... */ src = new ppdcSource(filename, fp); if (src->drivers->count == 0) { fprintf(stderr, "ERROR: [cups-driverd] Bad driver information file \"%s\"!\n", filename); src->release(); return (0); } /* * Add a dummy entry for the file... */ add_ppd(name, name, "", "", "", "", "", "", mtime, (size_t)size, 0, PPD_TYPE_DRV, "drv"); ChangedPPD = 1; /* * Then the drivers in the file... */ for (d = (ppdcDriver *)src->drivers->first(); d; d = (ppdcDriver *)src->drivers->next()) { httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "drv", "", "", 0, "/%s/%s", name, d->file_name ? d->file_name->value : d->pc_file_name->value); device_id = d->find_attr("1284DeviceID", NULL); ps_version = d->find_attr("PSVersion", NULL); nick_name = d->find_attr("NickName", NULL); if (nick_name) strlcpy(make_model, nick_name->value->value, sizeof(make_model)); else if (_cups_strncasecmp(d->model_name->value, d->manufacturer->value, strlen(d->manufacturer->value))) snprintf(make_model, sizeof(make_model), "%s %s, %s", d->manufacturer->value, d->model_name->value, d->version->value); else snprintf(make_model, sizeof(make_model), "%s, %s", d->model_name->value, d->version->value); if ((cups_fax = d->find_attr("cupsFax", NULL)) != NULL && !_cups_strcasecmp(cups_fax->value->value, "true")) type = PPD_TYPE_FAX; else if (d->type == PPDC_DRIVER_PS) type = PPD_TYPE_POSTSCRIPT; else if (d->type != PPDC_DRIVER_CUSTOM) type = PPD_TYPE_RASTER; else { for (filter = (ppdcFilter *)d->filters->first(), type = PPD_TYPE_POSTSCRIPT; filter; filter = (ppdcFilter *)d->filters->next()) if (_cups_strcasecmp(filter->mime_type->value, "application/vnd.cups-raster")) type = PPD_TYPE_RASTER; else if (_cups_strcasecmp(filter->mime_type->value, "application/vnd.cups-pdf")) type = PPD_TYPE_PDF; } for (product = (ppdcAttr *)d->attrs->first(), products_found = 0, ppd = NULL; product; product = (ppdcAttr *)d->attrs->next()) if (!strcmp(product->name->value, "Product")) { if (!products_found) ppd = add_ppd(name, uri, "en", d->manufacturer->value, make_model, device_id ? device_id->value->value : "", product->value->value, ps_version ? ps_version->value->value : "(3010) 0", mtime, (size_t)size, d->model_number, type, "drv"); else if (products_found < PPD_MAX_PROD) strlcpy(ppd->record.products[products_found], product->value->value, sizeof(ppd->record.products[0])); else break; products_found ++; } if (!products_found) add_ppd(name, uri, "en", d->manufacturer->value, make_model, device_id ? device_id->value->value : "", d->model_name->value, ps_version ? ps_version->value->value : "(3010) 0", mtime, (size_t)size, d->model_number, type, "drv"); } src->release(); return (1); } /* * 'load_drivers()' - Load driver-generated PPD files. */ static int /* O - 1 on success, 0 on failure */ load_drivers(cups_array_t *include, /* I - Drivers to include */ cups_array_t *exclude) /* I - Drivers to exclude */ { int i; /* Looping var */ char *start, /* Start of value */ *ptr; /* Pointer into string */ const char *server_bin, /* CUPS_SERVERBIN env variable */ *scheme, /* Scheme for this driver */ *scheme_end; /* Pointer to end of scheme */ char drivers[1024]; /* Location of driver programs */ int pid; /* Process ID for driver program */ cups_file_t *fp; /* Pipe to driver program */ cups_dir_t *dir; /* Directory pointer */ cups_dentry_t *dent; /* Directory entry */ char *argv[3], /* Arguments for command */ filename[1024], /* Name of driver */ line[2048], /* Line from driver */ name[256], /* ppd-name */ make[128], /* ppd-make */ make_and_model[128], /* ppd-make-and-model */ device_id[256], /* ppd-device-id */ languages[128], /* ppd-natural-language */ product[128], /* ppd-product */ psversion[128], /* ppd-psversion */ type_str[128]; /* ppd-type */ int type; /* PPD type */ ppd_info_t *ppd; /* Newly added PPD */ /* * Try opening the driver directory... */ if ((server_bin = getenv("CUPS_SERVERBIN")) == NULL) server_bin = CUPS_SERVERBIN; snprintf(drivers, sizeof(drivers), "%s/driver", server_bin); if ((dir = cupsDirOpen(drivers)) == NULL) { fprintf(stderr, "ERROR: [cups-driverd] Unable to open driver directory " "\"%s\": %s\n", drivers, strerror(errno)); return (0); } /* * Loop through all of the device drivers... */ argv[1] = (char *)"list"; argv[2] = NULL; while ((dent = cupsDirRead(dir)) != NULL) { /* * Only look at executable files... */ if (!(dent->fileinfo.st_mode & 0111) || !S_ISREG(dent->fileinfo.st_mode)) continue; /* * Include/exclude specific drivers... */ if (exclude) { /* * Look for "scheme" or "scheme*" (prefix match), and skip any matches. */ for (scheme = (char *)cupsArrayFirst(exclude); scheme; scheme = (char *)cupsArrayNext(exclude)) { fprintf(stderr, "DEBUG: [cups-driverd] Exclude \"%s\" with \"%s\"?\n", dent->filename, scheme); scheme_end = scheme + strlen(scheme) - 1; if ((scheme_end > scheme && *scheme_end == '*' && !strncmp(scheme, dent->filename, (size_t)(scheme_end - scheme))) || !strcmp(scheme, dent->filename)) { fputs("DEBUG: [cups-driverd] Yes, exclude!\n", stderr); break; } } if (scheme) continue; } if (include) { /* * Look for "scheme" or "scheme*" (prefix match), and skip any non-matches. */ for (scheme = (char *)cupsArrayFirst(include); scheme; scheme = (char *)cupsArrayNext(include)) { fprintf(stderr, "DEBUG: [cups-driverd] Include \"%s\" with \"%s\"?\n", dent->filename, scheme); scheme_end = scheme + strlen(scheme) - 1; if ((scheme_end > scheme && *scheme_end == '*' && !strncmp(scheme, dent->filename, (size_t)(scheme_end - scheme))) || !strcmp(scheme, dent->filename)) { fputs("DEBUG: [cups-driverd] Yes, include!\n", stderr); break; } } if (!scheme) continue; } else scheme = dent->filename; /* * Run the driver with no arguments and collect the output... */ snprintf(filename, sizeof(filename), "%s/%s", drivers, dent->filename); if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_PROGRAM, !geteuid(), _cupsFileCheckFilter, NULL)) continue; argv[0] = dent->filename; if ((fp = cupsdPipeCommand(&pid, filename, argv, 0)) != NULL) { while (cupsFileGets(fp, line, sizeof(line))) { /* * Each line is of the form: * * "ppd-name" ppd-natural-language "ppd-make" "ppd-make-and-model" \ * "ppd-device-id" "ppd-product" "ppd-psversion" */ device_id[0] = '\0'; product[0] = '\0'; psversion[0] = '\0'; strlcpy(type_str, "postscript", sizeof(type_str)); if (sscanf(line, "\"%255[^\"]\"%127s%*[ \t]\"%127[^\"]\"" "%*[ \t]\"%127[^\"]\"%*[ \t]\"%255[^\"]\"" "%*[ \t]\"%127[^\"]\"%*[ \t]\"%127[^\"]\"" "%*[ \t]\"%127[^\"]\"", name, languages, make, make_and_model, device_id, product, psversion, type_str) < 4) { /* * Bad format; strip trailing newline and write an error message. */ if (line[strlen(line) - 1] == '\n') line[strlen(line) - 1] = '\0'; fprintf(stderr, "ERROR: [cups-driverd] Bad line from \"%s\": %s\n", dent->filename, line); break; } else { /* * Add the device to the array of available devices... */ if ((start = strchr(languages, ',')) != NULL) *start++ = '\0'; for (type = 0; type < (int)(sizeof(PPDTypes) / sizeof(PPDTypes[0])); type ++) if (!strcmp(type_str, PPDTypes[type])) break; if (type >= (int)(sizeof(PPDTypes) / sizeof(PPDTypes[0]))) { fprintf(stderr, "ERROR: [cups-driverd] Bad ppd-type \"%s\" ignored!\n", type_str); type = PPD_TYPE_UNKNOWN; } ppd = add_ppd(filename, name, languages, make, make_and_model, device_id, product, psversion, 0, 0, 0, type, scheme); if (!ppd) { cupsDirClose(dir); cupsFileClose(fp); return (0); } if (start && *start) { for (i = 1; i < PPD_MAX_LANG && *start; i ++) { if ((ptr = strchr(start, ',')) != NULL) *ptr++ = '\0'; else ptr = start + strlen(start); strlcpy(ppd->record.languages[i], start, sizeof(ppd->record.languages[0])); start = ptr; } } fprintf(stderr, "DEBUG2: [cups-driverd] Added dynamic PPD \"%s\"...\n", name); } } cupsFileClose(fp); } else fprintf(stderr, "WARNING: [cups-driverd] Unable to execute \"%s\": %s\n", filename, strerror(errno)); } cupsDirClose(dir); return (1); } /* * 'load_ppd()' - Load a PPD file. */ static void load_ppd(const char *filename, /* I - Real filename */ const char *name, /* I - Virtual filename */ const char *scheme, /* I - PPD scheme */ struct stat *fileinfo, /* I - File information */ ppd_info_t *ppd, /* I - Existing PPD file or NULL */ cups_file_t *fp, /* I - File to read from */ off_t end) /* I - End of file position or 0 */ { int i; /* Looping var */ char line[256], /* Line from file */ *ptr, /* Pointer into line */ lang_version[64], /* PPD LanguageVersion */ lang_encoding[64], /* PPD LanguageEncoding */ country[64], /* Country code */ manufacturer[256], /* Manufacturer */ make_model[256], /* Make and Model */ model_name[256], /* ModelName */ nick_name[256], /* NickName */ device_id[256], /* 1284DeviceID */ product[256], /* Product */ psversion[256], /* PSVersion */ temp[512]; /* Temporary make and model */ int install_group, /* In the installable options group? */ model_number, /* cupsModelNumber */ type; /* ppd-type */ cups_array_t *products, /* Product array */ *psversions, /* PSVersion array */ *cups_languages; /* cupsLanguages array */ struct /* LanguageVersion translation table */ { const char *version, /* LanguageVersion string */ *language; /* Language code */ } languages[] = { { "chinese", "zh" }, { "czech", "cs" }, { "danish", "da" }, { "dutch", "nl" }, { "english", "en" }, { "finnish", "fi" }, { "french", "fr" }, { "german", "de" }, { "greek", "el" }, { "hungarian", "hu" }, { "italian", "it" }, { "japanese", "ja" }, { "korean", "ko" }, { "norwegian", "no" }, { "polish", "pl" }, { "portuguese", "pt" }, { "russian", "ru" }, { "simplified chinese", "zh_CN" }, { "slovak", "sk" }, { "spanish", "es" }, { "swedish", "sv" }, { "traditional chinese", "zh_TW" }, { "turkish", "tr" } }; /* * Now read until we get the required fields... */ cups_languages = cupsArrayNew(NULL, NULL); products = cupsArrayNew(NULL, NULL); psversions = cupsArrayNew(NULL, NULL); model_name[0] = '\0'; nick_name[0] = '\0'; manufacturer[0] = '\0'; device_id[0] = '\0'; lang_encoding[0] = '\0'; strlcpy(lang_version, "en", sizeof(lang_version)); model_number = 0; install_group = 0; type = PPD_TYPE_POSTSCRIPT; while ((end == 0 || cupsFileTell(fp) < end) && cupsFileGets(fp, line, sizeof(line))) { if (!strncmp(line, "*Manufacturer:", 14)) sscanf(line, "%*[^\"]\"%255[^\"]", manufacturer); else if (!strncmp(line, "*ModelName:", 11)) sscanf(line, "%*[^\"]\"%127[^\"]", model_name); else if (!strncmp(line, "*LanguageEncoding:", 18)) sscanf(line, "%*[^:]:%63s", lang_encoding); else if (!strncmp(line, "*LanguageVersion:", 17)) sscanf(line, "%*[^:]:%63s", lang_version); else if (!strncmp(line, "*NickName:", 10)) sscanf(line, "%*[^\"]\"%255[^\"]", nick_name); else if (!_cups_strncasecmp(line, "*1284DeviceID:", 14)) { sscanf(line, "%*[^\"]\"%255[^\"]", device_id); // Make sure device ID ends with a semicolon... if (device_id[0] && device_id[strlen(device_id) - 1] != ';') strlcat(device_id, ";", sizeof(device_id)); } else if (!strncmp(line, "*Product:", 9)) { if (sscanf(line, "%*[^\"]\"(%255[^\"]", product) == 1) { /* * Make sure the value ends with a right parenthesis - can't stop at * the first right paren since the product name may contain escaped * parenthesis... */ ptr = product + strlen(product) - 1; if (ptr > product && *ptr == ')') { /* * Yes, ends with a parenthesis, so remove it from the end and * add the product to the list... */ *ptr = '\0'; cupsArrayAdd(products, strdup(product)); } } } else if (!strncmp(line, "*PSVersion:", 11)) { sscanf(line, "%*[^\"]\"%255[^\"]", psversion); cupsArrayAdd(psversions, strdup(psversion)); } else if (!strncmp(line, "*cupsLanguages:", 15)) { char *start; /* Start of language */ for (start = line + 15; *start && isspace(*start & 255); start ++); if (*start++ == '\"') { while (*start) { for (ptr = start + 1; *ptr && *ptr != '\"' && !isspace(*ptr & 255); ptr ++); if (*ptr) { *ptr++ = '\0'; while (isspace(*ptr & 255)) *ptr++ = '\0'; } cupsArrayAdd(cups_languages, strdup(start)); start = ptr; } } } else if (!strncmp(line, "*cupsFax:", 9)) { for (ptr = line + 9; isspace(*ptr & 255); ptr ++); if (!_cups_strncasecmp(ptr, "true", 4)) type = PPD_TYPE_FAX; } else if ((!strncmp(line, "*cupsFilter:", 12) || !strncmp(line, "*cupsFilter2:", 13)) && type == PPD_TYPE_POSTSCRIPT) { if (strstr(line + 12, "application/vnd.cups-raster")) type = PPD_TYPE_RASTER; else if (strstr(line + 12, "application/vnd.cups-pdf")) type = PPD_TYPE_PDF; } else if (!strncmp(line, "*cupsModelNumber:", 17)) sscanf(line, "*cupsModelNumber:%d", &model_number); else if (!strncmp(line, "*OpenGroup: Installable", 23)) install_group = 1; else if (!strncmp(line, "*CloseGroup:", 12)) install_group = 0; else if (!strncmp(line, "*OpenUI", 7)) { /* * Stop early if we have a NickName or ModelName attributes * before the first non-installable OpenUI... */ if (!install_group && (model_name[0] || nick_name[0]) && cupsArrayCount(products) > 0 && cupsArrayCount(psversions) > 0) break; } } /* * See if we got all of the required info... */ if (nick_name[0]) cupsCharsetToUTF8((cups_utf8_t *)make_model, nick_name, sizeof(make_model), _ppdGetEncoding(lang_encoding)); else strlcpy(make_model, model_name, sizeof(make_model)); while (isspace(make_model[0] & 255)) _cups_strcpy(make_model, make_model + 1); if (!make_model[0] || cupsArrayCount(products) == 0 || cupsArrayCount(psversions) == 0) { /* * We don't have all the info needed, so skip this file... */ if (!make_model[0]) fprintf(stderr, "WARNING: Missing NickName and ModelName in %s!\n", filename); if (cupsArrayCount(products) == 0) fprintf(stderr, "WARNING: Missing Product in %s!\n", filename); if (cupsArrayCount(psversions) == 0) fprintf(stderr, "WARNING: Missing PSVersion in %s!\n", filename); free_array(products); free_array(psversions); free_array(cups_languages); return; } if (model_name[0]) cupsArrayAdd(products, strdup(model_name)); /* * Normalize the make and model string... */ while (isspace(manufacturer[0] & 255)) _cups_strcpy(manufacturer, manufacturer + 1); if (!_cups_strncasecmp(make_model, manufacturer, strlen(manufacturer))) strlcpy(temp, make_model, sizeof(temp)); else snprintf(temp, sizeof(temp), "%s %s", manufacturer, make_model); _ppdNormalizeMakeAndModel(temp, make_model, sizeof(make_model)); /* * See if we got a manufacturer... */ if (!manufacturer[0] || !strcmp(manufacturer, "ESP")) { /* * Nope, copy the first part of the make and model then... */ strlcpy(manufacturer, make_model, sizeof(manufacturer)); /* * Truncate at the first space, dash, or slash, or make the * manufacturer "Other"... */ for (ptr = manufacturer; *ptr; ptr ++) if (*ptr == ' ' || *ptr == '-' || *ptr == '/') break; if (*ptr && ptr > manufacturer) *ptr = '\0'; else strlcpy(manufacturer, "Other", sizeof(manufacturer)); } else if (!_cups_strncasecmp(manufacturer, "LHAG", 4) || !_cups_strncasecmp(manufacturer, "linotype", 8)) strlcpy(manufacturer, "LHAG", sizeof(manufacturer)); else if (!_cups_strncasecmp(manufacturer, "Hewlett", 7)) strlcpy(manufacturer, "HP", sizeof(manufacturer)); /* * Fix the lang_version as needed... */ if ((ptr = strchr(lang_version, '-')) != NULL) *ptr++ = '\0'; else if ((ptr = strchr(lang_version, '_')) != NULL) *ptr++ = '\0'; if (ptr) { /* * Set the country suffix... */ country[0] = '_'; _cups_strcpy(country + 1, ptr); } else { /* * No country suffix... */ country[0] = '\0'; } for (i = 0; i < (int)(sizeof(languages) / sizeof(languages[0])); i ++) if (!_cups_strcasecmp(languages[i].version, lang_version)) break; if (i < (int)(sizeof(languages) / sizeof(languages[0]))) { /* * Found a known language... */ snprintf(lang_version, sizeof(lang_version), "%s%s", languages[i].language, country); } else { /* * Unknown language; use "xx"... */ strlcpy(lang_version, "xx", sizeof(lang_version)); } /* * Record the PPD file... */ if (!ppd) { /* * Add new PPD file... */ fprintf(stderr, "DEBUG2: [cups-driverd] Adding ppd \"%s\"...\n", name); ppd = add_ppd(name, name, lang_version, manufacturer, make_model, device_id, (char *)cupsArrayFirst(products), (char *)cupsArrayFirst(psversions), fileinfo->st_mtime, (size_t)fileinfo->st_size, model_number, type, scheme); if (!ppd) return; } else { /* * Update existing record... */ fprintf(stderr, "DEBUG2: [cups-driverd] Updating ppd \"%s\"...\n", name); memset(ppd, 0, sizeof(ppd_info_t)); ppd->found = 1; ppd->record.mtime = fileinfo->st_mtime; ppd->record.size = fileinfo->st_size; ppd->record.model_number = model_number; ppd->record.type = type; strlcpy(ppd->record.filename, name, sizeof(ppd->record.filename)); strlcpy(ppd->record.name, name, sizeof(ppd->record.name)); strlcpy(ppd->record.languages[0], lang_version, sizeof(ppd->record.languages[0])); strlcpy(ppd->record.products[0], (char *)cupsArrayFirst(products), sizeof(ppd->record.products[0])); strlcpy(ppd->record.psversions[0], (char *)cupsArrayFirst(psversions), sizeof(ppd->record.psversions[0])); strlcpy(ppd->record.make, manufacturer, sizeof(ppd->record.make)); strlcpy(ppd->record.make_and_model, make_model, sizeof(ppd->record.make_and_model)); strlcpy(ppd->record.device_id, device_id, sizeof(ppd->record.device_id)); strlcpy(ppd->record.scheme, scheme, sizeof(ppd->record.scheme)); } /* * Add remaining products, versions, and languages... */ for (i = 1; i < PPD_MAX_PROD && (ptr = (char *)cupsArrayNext(products)) != NULL; i ++) strlcpy(ppd->record.products[i], ptr, sizeof(ppd->record.products[0])); for (i = 1; i < PPD_MAX_VERS && (ptr = (char *)cupsArrayNext(psversions)) != NULL; i ++) strlcpy(ppd->record.psversions[i], ptr, sizeof(ppd->record.psversions[0])); for (i = 1, ptr = (char *)cupsArrayFirst(cups_languages); i < PPD_MAX_LANG && ptr; i ++, ptr = (char *)cupsArrayNext(cups_languages)) strlcpy(ppd->record.languages[i], ptr, sizeof(ppd->record.languages[0])); /* * Free products, versions, and languages... */ free_array(cups_languages); free_array(products); free_array(psversions); ChangedPPD = 1; } /* * 'load_ppds()' - Load PPD files recursively. */ static int /* O - 1 on success, 0 on failure */ load_ppds(const char *d, /* I - Actual directory */ const char *p, /* I - Virtual path in name */ int descend) /* I - Descend into directories? */ { struct stat dinfo, /* Directory information */ *dinfoptr; /* Pointer to match */ cups_file_t *fp; /* Pointer to file */ cups_dir_t *dir; /* Directory pointer */ cups_dentry_t *dent; /* Directory entry */ char filename[1024], /* Name of PPD or directory */ line[256], /* Line from file */ *ptr, /* Pointer into name */ name[256]; /* Name of PPD file */ ppd_info_t *ppd, /* New PPD file */ key; /* Search key */ /* * See if we've loaded this directory before... */ if (stat(d, &dinfo)) { if (errno != ENOENT) fprintf(stderr, "ERROR: [cups-driverd] Unable to stat \"%s\": %s\n", d, strerror(errno)); return (0); } else if (cupsArrayFind(Inodes, &dinfo)) { fprintf(stderr, "ERROR: [cups-driverd] Skipping \"%s\": loop detected!\n", d); return (1); } /* * Nope, add it to the Inodes array and continue... */ if ((dinfoptr = (struct stat *)malloc(sizeof(struct stat))) == NULL) { fputs("ERROR: [cups-driverd] Unable to allocate memory for directory info.\n", stderr); exit(1); } memcpy(dinfoptr, &dinfo, sizeof(struct stat)); cupsArrayAdd(Inodes, dinfoptr); /* * Check permissions... */ if (_cupsFileCheck(d, _CUPS_FILE_CHECK_DIRECTORY, !geteuid(), _cupsFileCheckFilter, NULL)) return (0); if ((dir = cupsDirOpen(d)) == NULL) { if (errno != ENOENT) fprintf(stderr, "ERROR: [cups-driverd] Unable to open PPD directory \"%s\": %s\n", d, strerror(errno)); return (0); } fprintf(stderr, "DEBUG: [cups-driverd] Loading \"%s\"...\n", d); while ((dent = cupsDirRead(dir)) != NULL) { /* * Skip files/directories starting with "."... */ if (dent->filename[0] == '.') continue; /* * See if this is a file... */ snprintf(filename, sizeof(filename), "%s/%s", d, dent->filename); if (p[0]) snprintf(name, sizeof(name), "%s/%s", p, dent->filename); else strlcpy(name, dent->filename, sizeof(name)); if (S_ISDIR(dent->fileinfo.st_mode)) { /* * Do subdirectory... */ if (descend) { if (!load_ppds(filename, name, 1)) { cupsDirClose(dir); return (1); } } else if ((ptr = filename + strlen(filename) - 14) > filename && !strcmp(ptr, ".printerDriver")) { /* * Load PPDs in a printer driver bundle. */ if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_DIRECTORY, !geteuid(), _cupsFileCheckFilter, NULL)) continue; strlcat(filename, "/Contents/Resources/PPDs", sizeof(filename)); strlcat(name, "/Contents/Resources/PPDs", sizeof(name)); load_ppds(filename, name, 0); } continue; } else if (strstr(filename, ".plist")) { /* * Skip plist files in the PPDs directory... */ continue; } else if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_FILE_ONLY, !geteuid(), _cupsFileCheckFilter, NULL)) continue; /* * See if this file has been scanned before... */ strlcpy(key.record.filename, name, sizeof(key.record.filename)); strlcpy(key.record.name, name, sizeof(key.record.name)); ppd = (ppd_info_t *)cupsArrayFind(PPDsByName, &key); if (ppd && ppd->record.size == dent->fileinfo.st_size && ppd->record.mtime == dent->fileinfo.st_mtime) { /* * Rewind to the first entry for this file... */ while ((ppd = (ppd_info_t *)cupsArrayPrev(PPDsByName)) != NULL && !strcmp(ppd->record.filename, name)); /* * Then mark all of the matches for this file as found... */ while ((ppd = (ppd_info_t *)cupsArrayNext(PPDsByName)) != NULL && !strcmp(ppd->record.filename, name)) ppd->found = 1; continue; } /* * No, file is new/changed, so re-scan it... */ if ((fp = cupsFileOpen(filename, "r")) == NULL) continue; /* * Now see if this is a PPD file... */ line[0] = '\0'; cupsFileGets(fp, line, sizeof(line)); if (!strncmp(line, "*PPD-Adobe:", 11)) { /* * Yes, load it... */ load_ppd(filename, name, "file", &dent->fileinfo, ppd, fp, 0); } else { /* * Nope, treat it as a driver information file or archive... */ cupsFileRewind(fp); if ((ptr = strstr(filename, ".tar")) != NULL && (!strcmp(ptr, ".tar") || !strcmp(ptr, ".tar.gz"))) load_tar(filename, name, fp, dent->fileinfo.st_mtime, dent->fileinfo.st_size); else load_drv(filename, name, fp, dent->fileinfo.st_mtime, dent->fileinfo.st_size); } /* * Close the file... */ cupsFileClose(fp); } cupsDirClose(dir); return (1); } /* * 'load_ppds_dat()' - Load the ppds.dat file. */ static void load_ppds_dat(char *filename, /* I - Filename buffer */ size_t filesize, /* I - Size of filename buffer */ int verbose) /* I - Be verbose? */ { ppd_info_t *ppd; /* Current PPD file */ cups_file_t *fp; /* ppds.dat file */ struct stat fileinfo; /* ppds.dat information */ const char *cups_cachedir; /* CUPS_CACHEDIR environment variable */ PPDsByName = cupsArrayNew((cups_array_func_t)compare_names, NULL); PPDsByMakeModel = cupsArrayNew((cups_array_func_t)compare_ppds, NULL); ChangedPPD = 0; if (!filename[0]) { if ((cups_cachedir = getenv("CUPS_CACHEDIR")) == NULL) cups_cachedir = CUPS_CACHEDIR; snprintf(filename, filesize, "%s/ppds.dat", cups_cachedir); } if ((fp = cupsFileOpen(filename, "r")) != NULL) { /* * See if we have the right sync word... */ unsigned ppdsync; /* Sync word */ int num_ppds; /* Number of PPDs */ if ((size_t)cupsFileRead(fp, (char *)&ppdsync, sizeof(ppdsync)) == sizeof(ppdsync) && ppdsync == PPD_SYNC && !stat(filename, &fileinfo) && (((size_t)fileinfo.st_size - sizeof(ppdsync)) % sizeof(ppd_rec_t)) == 0 && (num_ppds = ((size_t)fileinfo.st_size - sizeof(ppdsync)) / sizeof(ppd_rec_t)) > 0) { /* * We have a ppds.dat file, so read it! */ for (; num_ppds > 0; num_ppds --) { if ((ppd = (ppd_info_t *)calloc(1, sizeof(ppd_info_t))) == NULL) { fputs("ERROR: [cups-driverd] Unable to allocate memory for PPD!\n", stderr); exit(1); } if (cupsFileRead(fp, (char *)&(ppd->record), sizeof(ppd_rec_t)) > 0) { cupsArrayAdd(PPDsByName, ppd); cupsArrayAdd(PPDsByMakeModel, ppd); } else { free(ppd); break; } } if (verbose) fprintf(stderr, "INFO: [cups-driverd] Read \"%s\", %d PPDs...\n", filename, cupsArrayCount(PPDsByName)); } cupsFileClose(fp); } } /* * 'load_tar()' - Load archived PPD files. */ static int /* O - 1 on success, 0 on failure */ load_tar(const char *filename, /* I - Actual filename */ const char *name, /* I - Name to the rest of the world */ cups_file_t *fp, /* I - File to read from */ time_t mtime, /* I - Mod time of driver info file */ off_t size) /* I - Size of driver info file */ { char curname[256], /* Current archive file name */ uri[1024]; /* Virtual file URI */ const char *curext; /* Extension on file */ struct stat curinfo; /* Current archive file information */ off_t next; /* Position for next header */ /* * Add a dummy entry for the file... */ (void)filename; add_ppd(name, name, "", "", "", "", "", "", mtime, (size_t)size, 0, PPD_TYPE_ARCHIVE, "file"); ChangedPPD = 1; /* * Scan for PPDs in the archive... */ while (read_tar(fp, curname, sizeof(curname), &curinfo)) { next = cupsFileTell(fp) + ((curinfo.st_size + TAR_BLOCK - 1) & ~(TAR_BLOCK - 1)); if ((curext = strrchr(curname, '.')) != NULL && !_cups_strcasecmp(curext, ".ppd")) { httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "file", "", "", 0, "/%s/%s", name, curname); load_ppd(name, uri, "file", &curinfo, NULL, fp, next); } if (cupsFileTell(fp) != next) cupsFileSeek(fp, next); } return (1); } /* * 'read_tar()' - Read a file header from an archive. * * This function skips all directories and special files. */ static int /* O - 1 if found, 0 on EOF */ read_tar(cups_file_t *fp, /* I - Archive to read */ char *name, /* I - Filename buffer */ size_t namesize, /* I - Size of filename buffer */ struct stat *info) /* O - File information */ { tar_rec_t record; /* Record from file */ while ((size_t)cupsFileRead(fp, (char *)&record, sizeof(record)) == sizeof(record)) { /* * Check for a valid tar header... */ if (memcmp(record.header.magic, TAR_MAGIC, 6) || memcmp(record.header.version, TAR_VERSION, 2)) { if (record.header.magic[0] || memcmp(record.header.magic, record.header.magic + 1, 5)) fputs("ERROR: [cups-driverd] Bad tar magic/version.\n", stderr); break; } /* * Ignore non-files... */ if (record.header.linkflag != TAR_OLDNORMAL && record.header.linkflag != TAR_NORMAL) continue; /* * Grab size and name from tar header and return... */ if (record.header.prefix[0]) snprintf(name, namesize, "%s/%s", record.header.prefix, record.header.pathname); else strlcpy(name, record.header.pathname, namesize); info->st_mtime = strtol(record.header.mtime, NULL, 8); info->st_size = strtoll(record.header.size, NULL, 8); return (1); } return (0); } /* * 'regex_device_id()' - Compile a regular expression based on the 1284 device * ID. */ static regex_t * /* O - Regular expression */ regex_device_id(const char *device_id) /* I - IEEE-1284 device ID */ { char res[2048], /* Regular expression string */ *ptr; /* Pointer into string */ regex_t *re; /* Regular expression */ int cmd; /* Command set string? */ fprintf(stderr, "DEBUG: [cups-driverd] regex_device_id(\"%s\")\n", device_id); /* * Scan the device ID string and insert class, command set, manufacturer, and * model attributes to match. We assume that the device ID in the PPD and the * device ID reported by the device itself use the same attribute names and * order of attributes. */ ptr = res; while (*device_id && ptr < (res + sizeof(res) - 6)) { cmd = !_cups_strncasecmp(device_id, "COMMAND SET:", 12) || !_cups_strncasecmp(device_id, "CMD:", 4); if (cmd || !_cups_strncasecmp(device_id, "MANUFACTURER:", 13) || !_cups_strncasecmp(device_id, "MFG:", 4) || !_cups_strncasecmp(device_id, "MFR:", 4) || !_cups_strncasecmp(device_id, "MODEL:", 6) || !_cups_strncasecmp(device_id, "MDL:", 4)) { if (ptr > res) { *ptr++ = '.'; *ptr++ = '*'; } *ptr++ = '('; while (*device_id && *device_id != ';' && ptr < (res + sizeof(res) - 8)) { if (strchr("[]{}().*\\|", *device_id)) *ptr++ = '\\'; if (*device_id == ':') { /* * KEY:.*value */ *ptr++ = *device_id++; *ptr++ = '.'; *ptr++ = '*'; } else *ptr++ = *device_id++; } if (*device_id == ';' || !*device_id) { /* * KEY:.*value.*; */ *ptr++ = '.'; *ptr++ = '*'; *ptr++ = ';'; } *ptr++ = ')'; if (cmd) *ptr++ = '?'; } else if ((device_id = strchr(device_id, ';')) == NULL) break; else device_id ++; } *ptr = '\0'; fprintf(stderr, "DEBUG: [cups-driverd] regex_device_id: \"%s\"\n", res); /* * Compile the regular expression and return... */ if (res[0] && (re = (regex_t *)calloc(1, sizeof(regex_t))) != NULL) { if (!regcomp(re, res, REG_EXTENDED | REG_ICASE)) { fputs("DEBUG: [cups-driverd] regex_device_id: OK\n", stderr); return (re); } free(re); } return (NULL); } /* * 'regex_string()' - Construct a regular expression to compare a simple string. */ static regex_t * /* O - Regular expression */ regex_string(const char *s) /* I - String to compare */ { char res[2048], /* Regular expression string */ *ptr; /* Pointer into string */ regex_t *re; /* Regular expression */ fprintf(stderr, "DEBUG: [cups-driverd] regex_string(\"%s\")\n", s); /* * Convert the string to a regular expression, escaping special characters * as needed. */ ptr = res; while (*s && ptr < (res + sizeof(res) - 2)) { if (strchr("[]{}().*\\", *s)) *ptr++ = '\\'; *ptr++ = *s++; } *ptr = '\0'; fprintf(stderr, "DEBUG: [cups-driverd] regex_string: \"%s\"\n", res); /* * Create a case-insensitive regular expression... */ if (res[0] && (re = (regex_t *)calloc(1, sizeof(regex_t))) != NULL) { if (!regcomp(re, res, REG_ICASE)) { fputs("DEBUG: [cups-driverd] regex_string: OK\n", stderr); return (re); } free(re); } return (NULL); }