/*** This file is part of cups-filters. This file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with avahi; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #if defined(__OpenBSD__) #include #endif /* __OpenBSD__ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_AVAHI #include #include #include #include #include #endif /* HAVE_AVAHI */ #include #ifdef HAVE_LDAP # ifdef __sun # include # endif /* __sun */ # include # ifdef HAVE_LDAP_SSL_H # include # endif /* HAVE_LDAP_SSL_H */ #endif /* HAVE_LDAP */ #ifdef HAVE_LDAP LDAP *BrowseLDAPHandle = NULL; /* Handle to LDAP server */ char *BrowseLDAPBindDN = NULL, /* LDAP login DN */ *BrowseLDAPDN = NULL, /* LDAP search DN */ *BrowseLDAPPassword = NULL, /* LDAP login password */ *BrowseLDAPServer = NULL, /* LDAP server to use */ *BrowseLDAPFilter = NULL; /* LDAP query filter */ int BrowseLDAPUpdate = TRUE, /* enables LDAP updates */ BrowseLDAPInitialised = FALSE; /* the init stuff has been done */ # ifdef HAVE_LDAP_SSL char *BrowseLDAPCACertFile = NULL; /* LDAP CA CERT file to use */ # endif /* HAVE_LDAP_SSL */ #endif /* HAVE_LDAP */ #ifdef HAVE_LDAP #define LDAP_BROWSE_FILTER "(objectclass=cupsPrinter)" static LDAP *ldap_new_connection(void); static LDAP *ldap_reconnect(void); static void ldap_disconnect(LDAP *ld); static int ldap_search_rec(LDAP *ld, char *base, int scope, char *filter, char *attrs[], int attrsonly, LDAPMessage **res); static int ldap_getval_firststring(LDAP *ld, LDAPMessage *entry, char *attr, char *retval, unsigned long maxsize); static void ldap_freeres(LDAPMessage *entry); # ifdef HAVE_LDAP_REBIND_PROC # if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000) static int ldap_rebind_proc(LDAP *RebindLDAPHandle, LDAP_CONST char *refsp, ber_tag_t request, ber_int_t msgid, void *params); # else static int ldap_rebind_proc(LDAP *RebindLDAPHandle, char **dnp, char **passwdp, int *authmethodp, int freeit, void *arg); # endif /* defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000) */ # endif /* HAVE_LDAP_REBIND_PROC */ #endif /* HAVE_LDAP */ #include #include #include #include #include #include "cups-notifier.h" /* Attribute to mark a CUPS queue as created by us */ #define CUPS_BROWSED_MARK "cups-browsed" #define AUTO_OPTION "auto" /* Attribute to tell the implicitclass backend the destination queue for the current job */ #define CUPS_BROWSED_DEST_PRINTER "cups-browsed-dest-printer" /* Timeout values in sec */ #define TIMEOUT_IMMEDIATELY -1 #define TIMEOUT_CONFIRM 10 #define TIMEOUT_RETRY 10 #define TIMEOUT_REMOVE -1 #define TIMEOUT_CHECK_LIST 2 #define CUPS_DBUS_NAME "org.cups.cupsd.Notifier" #define CUPS_DBUS_PATH "/org/cups/cupsd/Notifier" #define CUPS_DBUS_INTERFACE "org.cups.cupsd.Notifier" #define DEFAULT_CACHEDIR "/var/cache/cups" #define DEFAULT_LOGDIR "/var/log/cups" #define LOCAL_DEFAULT_PRINTER_FILE "/cups-browsed-local-default-printer" #define REMOTE_DEFAULT_PRINTER_FILE "/cups-browsed-remote-default-printer" #define SAVE_OPTIONS_FILE "/cups-browsed-options-%s" #define DEBUG_LOG_FILE "/cups-browsed_log" #define DEBUG_LOG_FILE_2 "/cups-browsed_previous_logs" /* Status of remote printer */ typedef enum printer_status_e { STATUS_UNCONFIRMED = 0, /* Generated in a previous session */ STATUS_CONFIRMED, /* Avahi confirms UNCONFIRMED printer */ STATUS_TO_BE_CREATED, /* Scheduled for creation */ STATUS_DISAPPEARED, /* Scheduled for removal */ STATUS_TO_BE_RELEASED /* Scheduled for release from cups-browsed */ } printer_status_t; /* Data structure for taking note of each time the remote printer appears as a discovered IPP service */ typedef struct ipp_discovery_s { char *interface; char *type; int family; } ipp_discovery_t; /* Data structure for remote printers */ typedef struct remote_printer_s { char *queue_name; char *location; char *info; char *uri; char *make_model; char *pdl; int color; int duplex; ipp_t *prattrs; char *nickname; int num_options; cups_option_t *options; printer_status_t status; time_t timeout; void *slave_of; int last_printer; char *host; char *ip; int port; char *resource; char *service_name; char *type; char *domain; cups_array_t *ipp_discoveries; int no_autosave; int overwritten; int netprinter; int is_legacy; int timeouted; } remote_printer_t; /* Data structure for network interfaces */ typedef struct netif_s { char *address; http_addr_t broadcast; } netif_t; /* Data structures for browse allow/deny rules */ typedef enum browse_order_e { ORDER_ALLOW_DENY, ORDER_DENY_ALLOW } browse_order_t; typedef enum allow_type_e { ALLOW_IP, ALLOW_NET, ALLOW_INVALID } allow_type_t; typedef enum allow_sense_e { ALLOW_ALLOW, ALLOW_DENY } allow_sense_t; typedef struct allow_s { allow_type_t type; allow_sense_t sense; http_addr_t addr; http_addr_t mask; } allow_t; /* Data structures for browse filter rules */ typedef enum filter_sense_s { FILTER_MATCH, FILTER_NOT_MATCH } filter_sense_t; typedef struct browse_filter_s { filter_sense_t sense; char *field; char *regexp; regex_t *cregexp; } browse_filter_t; /* Data structure for a printer discovered using BrowsePoll */ typedef struct browsepoll_printer_s { char *uri_supported; char *location; char *info; } browsepoll_printer_t; /* Data structure for a BrowsePoll server */ typedef struct browsepoll_s { char *server; int port; int major; int minor; gboolean can_subscribe; int subscription_id; int sequence_number; /* Remember which printers we discovered. This way we can just ask * if anything has changed, and if not we know these printers are * still there. */ GList *printers; /* of browsepoll_printer_t */ } browsepoll_t; /* Data structure for destination list obtained with cupsEnumDests() */ typedef struct dest_list_s { int num_dests; cups_dest_t *dests; } dest_list_t; /* Local printer (key is name) */ typedef struct local_printer_s { char *device_uri; char *uuid; gboolean cups_browsed_controlled; } local_printer_t; /* Browse data to send for local printer */ typedef struct browse_data_s { int type; int state; char *uri; char *location; char *info; char *make_model; char *browse_options; } browse_data_t; /* Data structure for manual definition of load-balancing clusters */ typedef struct cluster_s { char *local_queue_name; cups_array_t *members; } cluster_t; /* Ways how to represent the remote printer's IP in the device URI */ typedef enum ip_based_uris_e { IP_BASED_URIS_NO, IP_BASED_URIS_ANY, IP_BASED_URIS_IPV4_ONLY, IP_BASED_URIS_IPV6_ONLY } ip_based_uris_t; /* Ways how to name local queues for remote printers */ typedef enum local_queue_naming_e { LOCAL_QUEUE_NAMING_DNSSD, LOCAL_QUEUE_NAMING_MAKE_MODEL, LOCAL_QUEUE_NAMING_REMOTE_NAME } local_queue_naming_t; /* Automatically create queues for IPP network printers: No, only for IPP printers, for all printers */ typedef enum create_ipp_printer_queues_e { IPP_PRINTERS_NO, IPP_PRINTERS_LOCAL_ONLY, IPP_PRINTERS_PWGRASTER, IPP_PRINTERS_APPLERASTER, IPP_PRINTERS_PCLM, IPP_PRINTERS_PDF, IPP_PRINTERS_DRIVERLESS, IPP_PRINTERS_ALL } create_ipp_printer_queues_t; /* Ways how to set up a queue for an IPP network printer */ typedef enum ipp_queue_type_e { PPD_YES, PPD_NO } ipp_queue_type_t; /* Ways how we can do load balancing on remote queues with the same name */ typedef enum load_balancing_type_e { QUEUE_ON_CLIENT, QUEUE_ON_SERVERS } load_balancing_type_t; /* Ways how inactivity for auto-shutdown is defined */ typedef enum autoshutdown_inactivity_type_e { NO_QUEUES, NO_JOBS } autoshutdown_inactivity_type_t; typedef struct media_size_s{ int x; int y; }media_size_t; typedef struct pagesize_range_s{ int x_dim_min; int x_dim_max; int y_dim_min; int y_dim_max; }pagesize_range_t; typedef struct media_col_s{ int x,y,top_margin,bottom_margin,left_margin,right_margin; char *media_source,*media_type; }media_col_t; typedef struct default_str_attribute_s{ char* value; int count; }default_str_attribute_t; typedef struct resolution_count_s{ res_t *res; int count; }resolution_count_t; typedef struct mediacol_count_s{ media_col_t *data; int count; }mediacol_count_t; typedef struct pagesize_count_s{ char* pagesize; int count; }pagesize_count_t; cups_array_t *remote_printers; static char *alt_config_file = NULL; static cups_array_t *command_line_config; static cups_array_t *netifs; static cups_array_t *local_hostnames; static cups_array_t *browseallow; static gboolean browseallow_all = FALSE; static gboolean browsedeny_all = FALSE; static browse_order_t browse_order; static cups_array_t *browsefilter; static GHashTable *local_printers; static GHashTable *cups_supported_remote_printers; static browsepoll_t *local_printers_context = NULL; static http_t *local_conn = NULL; static gboolean inhibit_local_printers_update = FALSE; static GList *browse_data = NULL; static CupsNotifier *cups_notifier = NULL; static GMainLoop *gmainloop = NULL; #ifdef HAVE_AVAHI static AvahiGLibPoll *glib_poll = NULL; static AvahiClient *client = NULL; static AvahiServiceBrowser *sb1 = NULL, *sb2 = NULL; static int avahi_present = 0; #endif /* HAVE_AVAHI */ #ifdef HAVE_LDAP static const char * const ldap_attrs[] =/* CUPS LDAP attributes */ { "printerDescription", "printerLocation", "printerMakeAndModel", "printerType", "printerURI", NULL }; #endif /* HAVE_LDAP */ static guint queues_timer_id = 0; static int browsesocket = -1; #define BROWSE_DNSSD (1<<0) #define BROWSE_CUPS (1<<1) #define BROWSE_LDAP (1<<2) static unsigned int BrowseLocalProtocols = 0; static unsigned int BrowseRemoteProtocols = BROWSE_DNSSD; static unsigned int BrowseInterval = 60; static unsigned int BrowseTimeout = 300; static uint16_t BrowsePort = 631; static browsepoll_t **BrowsePoll = NULL; static unsigned int NewBrowsePollQueuesShared = 0; static unsigned int AllowResharingRemoteCUPSPrinters = 0; static unsigned int DebugLogFileSize = 300; static size_t NumBrowsePoll = 0; static guint update_netifs_sourceid = 0; static char local_server_str[1024]; static char *DomainSocket = NULL; static unsigned int HttpLocalTimeout = 5; static unsigned int HttpRemoteTimeout = 10; static unsigned int HttpMaxRetries = 5; static unsigned int DNSSDBasedDeviceURIs = 1; static ip_based_uris_t IPBasedDeviceURIs = IP_BASED_URIS_NO; #ifdef NAMING_MAKE_MODEL static local_queue_naming_t LocalQueueNamingRemoteCUPS = LOCAL_QUEUE_NAMING_MAKE_MODEL; #else # ifdef NAMING_REMOTE_NAME static local_queue_naming_t LocalQueueNamingRemoteCUPS = LOCAL_QUEUE_NAMING_REMOTE_NAME; # else static local_queue_naming_t LocalQueueNamingRemoteCUPS = LOCAL_QUEUE_NAMING_DNSSD; # endif #endif static local_queue_naming_t LocalQueueNamingIPPPrinter=LOCAL_QUEUE_NAMING_DNSSD; static unsigned int OnlyUnsupportedByCUPS = 0; static unsigned int UseCUPSGeneratedPPDs = 0; static unsigned int CreateRemoteRawPrinterQueues = 0; static unsigned int CreateRemoteCUPSPrinterQueues = 1; #ifdef ONLY_LOCAL_IPP_PRINTERS_AUTO_SETUP static create_ipp_printer_queues_t CreateIPPPrinterQueues = IPP_PRINTERS_LOCAL_ONLY; #else #ifdef ONLY_DRIVERLESS_IPP_PRINTERS_AUTO_SETUP static create_ipp_printer_queues_t CreateIPPPrinterQueues = IPP_PRINTERS_DRIVERLESS; #else static create_ipp_printer_queues_t CreateIPPPrinterQueues = IPP_PRINTERS_ALL; #endif #endif #ifdef SAVING_CREATED_QUEUES static unsigned int KeepGeneratedQueuesOnShutdown = 1; #else static unsigned int KeepGeneratedQueuesOnShutdown = 0; #endif static ipp_queue_type_t IPPPrinterQueueType = PPD_YES; static int NewIPPPrinterQueuesShared = 0; static int AutoClustering = 1; static cups_array_t *clusters; static load_balancing_type_t LoadBalancingType = QUEUE_ON_CLIENT; static char *DefaultOptions = NULL; static int update_cups_queues_max_per_call = 10; static int pause_between_cups_queue_updates = 1; static remote_printer_t *deleted_master = NULL; static int terminating = 0; /* received SIGTERM, ignore callbacks, break loops */ static int in_shutdown = 0; static int autoshutdown = 0; static int autoshutdown_avahi = 0; static int autoshutdown_timeout = 30; static autoshutdown_inactivity_type_t autoshutdown_on = NO_QUEUES; static guint autoshutdown_exec_id = 0; static const char *default_printer = NULL; static unsigned int notify_lease_duration = 86400; static int debug_stderr = 0; static int debug_logfile = 0; static FILE *lfp = NULL; static char cachedir[1024]; static char logdir[1024]; static char local_default_printer_file[2048]; static char remote_default_printer_file[2048]; static char save_options_file[2048]; static char debug_log_file[2048]; static char debug_log_file_bckp[2048]; /*Contains ppd keywords which are written by ppdgenerator.c in the ppd file.*/ static char* ppd_keywords[] = { "PageSize", "PageRegion", "InputSlot", "MediaType", "ColorModel", "Duplex", "OutputBin", "StapleLocation", "FoldType", "PunchMedia", "Booklet", "cupsFinishingTemplate", "cupsPrintQuality", "print-content-optimize", "print-rendering-intent", "print-scaling", }; /* Static global variable for indicating we have reached the HTTP timeout */ static int timeout_reached = 0; static void recheck_timer (void); static void browse_poll_create_subscription (browsepoll_t *context, http_t *conn); static gboolean browse_poll_get_notifications (browsepoll_t *context, http_t *conn); static remote_printer_t *examine_discovered_printer_record(const char *host, const char *ip, uint16_t port, char *resource, const char *service_name, const char *location, const char *info, const char *type, const char *domain, const char *interface, int family, void *txt); #if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5) #define HAVE_CUPS_1_6 1 #endif /* * Option 'printer-is-shared' cannot be set on remote CUPS * queue and requests for setting it ends with error since * 2.1.1. Define HAVE_CUPS_2_2 to do not send IPP request * for setting 'printer-is-shared' option on remote CUPS queues * for newer versions of CUPS. */ #if (CUPS_VERSION_MAJOR > 2) || (CUPS_VERSION_MINOR > 1) #define HAVE_CUPS_2_2 1 #else #define HAVE_CUPS_2_2 0 #endif /* * CUPS 1.6 makes various structures private and * introduces these ippGet and ippSet functions * for all of the fields in these structures. * http://www.cups.org/str.php?L3928 * We define (same signatures) our own accessors when CUPS < 1.6. */ #ifndef HAVE_CUPS_1_6 const char * ippGetName(ipp_attribute_t *attr) { return (attr->name); } ipp_op_t ippGetOperation(ipp_t *ipp) { return (ipp->request.op.operation_id); } ipp_status_t ippGetStatusCode(ipp_t *ipp) { return (ipp->request.status.status_code); } ipp_tag_t ippGetGroupTag(ipp_attribute_t *attr) { return (attr->group_tag); } ipp_tag_t ippGetValueTag(ipp_attribute_t *attr) { return (attr->value_tag); } int ippGetCount(ipp_attribute_t *attr) { return (attr->num_values); } int ippGetInteger(ipp_attribute_t *attr, int element) { return (attr->values[element].integer); } int ippGetBoolean(ipp_attribute_t *attr, int element) { return (attr->values[element].boolean); } const char * ippGetString(ipp_attribute_t *attr, int element, const char **language) { return (attr->values[element].string.text); } ipp_attribute_t * ippFirstAttribute(ipp_t *ipp) { if (!ipp) return (NULL); return (ipp->current = ipp->attrs); } ipp_attribute_t * ippNextAttribute(ipp_t *ipp) { if (!ipp || !ipp->current) return (NULL); return (ipp->current = ipp->current->next); } int ippSetVersion(ipp_t *ipp, int major, int minor) { if (!ipp || major < 0 || minor < 0) return (0); ipp->request.any.version[0] = major; ipp->request.any.version[1] = minor; return (1); } #endif #if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 6) #define HAVE_CUPS_1_7 1 #endif /* * The httpAddrPort() function was only introduced in CUPS 1.7.x */ #ifndef HAVE_CUPS_1_7 int /* O - Port number */ httpAddrPort(http_addr_t *addr) /* I - Address */ { if (!addr) return (-1); #ifdef AF_INET6 else if (addr->addr.sa_family == AF_INET6) return (ntohs(addr->ipv6.sin6_port)); #endif /* AF_INET6 */ else if (addr->addr.sa_family == AF_INET) return (ntohs(addr->ipv4.sin_port)); else return (0); } #endif #if (CUPS_VERSION_MAJOR > 1) #define HAVE_CUPS_2_0 1 #endif void start_debug_logging() { if (debug_log_file[0] == '\0') return; if (lfp == NULL) lfp = fopen(debug_log_file, "a+"); if (lfp == NULL) { fprintf(stderr, "cups-browsed: ERROR: Failed creating debug log file %s\n", debug_log_file); exit(1); } } void stop_debug_logging() { debug_logfile = 0; if (lfp) fclose(lfp); lfp = NULL; } // returns the size of debug log file long int findLogFileSize() { FILE* fp = fopen(debug_log_file, "r"); if (fp == NULL) { return -1; } fseek(fp, 0L, SEEK_END); long int res = ftell(fp); fclose(fp); return res; } void copyToFile(FILE **fp1, FILE **fp2){ int buffer_size = 2048; char *buf = (char*) malloc(sizeof(char)*buffer_size); if(!buf){ fprintf(stderr,"Error creating buffer for debug logging\n"); return; } fseek(*fp1, 0, SEEK_SET); size_t r; do { r = fread(buf, sizeof(char), buffer_size, *fp1); fwrite(buf, sizeof(char), r, *fp2); } while(r==buffer_size); } void debug_printf(const char *format, ...) { if (debug_stderr || debug_logfile) { time_t curtime = time(NULL); char buf[64]; ctime_r(&curtime, buf); while(isspace(buf[strlen(buf)-1])) buf[strlen(buf)-1] = '\0'; va_list arglist; if (debug_stderr) { va_start(arglist, format); fprintf(stderr, "%s ", buf); vfprintf(stderr, format, arglist); fflush(stderr); va_end(arglist); } if (debug_logfile && lfp) { va_start(arglist, format); fprintf(lfp, "%s ", buf); vfprintf(lfp, format, arglist); fflush(lfp); va_end(arglist); } long int log_file_size = findLogFileSize(); if(DebugLogFileSize>0 && log_file_size>(long int)DebugLogFileSize*1024){ fclose(lfp); FILE *fp1 = fopen(debug_log_file, "r"); FILE *fp2 = fopen(debug_log_file_bckp, "w"); copyToFile(&fp1,&fp2); fclose(fp1); fclose(fp2); lfp = fopen(debug_log_file, "w"); } } } void debug_log_out(char *log) { if (debug_stderr || debug_logfile) { time_t curtime = time(NULL); char buf[64]; char *ptr1, *ptr2; ctime_r(&curtime, buf); while(isspace(buf[strlen(buf)-1])) buf[strlen(buf)-1] = '\0'; ptr1 = log; while(ptr1) { ptr2 = strchr(ptr1, '\n'); if (ptr2) *ptr2 = '\0'; if (debug_stderr) fprintf(stderr, "%s %s\n", buf, ptr1); if (debug_logfile && lfp) fprintf(lfp, "%s %s\n", buf, ptr1); if (ptr2) *ptr2 = '\n'; ptr1 = ptr2 ? (ptr2 + 1) : NULL; } } } /* * 'create_media_size()' - Create a media-size value. */ static ipp_t * /* O - media-col collection */ create_media_size(int width, /* I - x-dimension in 2540ths */ int length) /* I - y-dimension in 2540ths */ { ipp_t *media_size = ippNew(); /* media-size value */ ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "x-dimension", width); ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "y-dimension", length); return (media_size); } /* * 'create_media_range()' - Create a pagesize-range value. */ static ipp_t * create_media_range(int x_dim_min_width, int x_dim_max_width, int y_dim_min_height, int y_dim_max_height) { ipp_t *media_size = ippNew(); ippAddRange(media_size, IPP_TAG_PRINTER, "x-dimension", x_dim_min_width, x_dim_max_width); ippAddRange(media_size, IPP_TAG_PRINTER, "y-dimension", y_dim_min_height, y_dim_max_height); return (media_size); } void * copy_media_size(void *size, void *user_data) { media_size_t *data = (media_size_t *)size; media_size_t *copy; copy = (media_size_t *)calloc(1, sizeof(media_size_t)); if (copy) { copy->x = data->x; copy->y = data->y; } return copy; } void* copy_range_size(void *range, void* user_data) { pagesize_range_t *data = (pagesize_range_t *)range; pagesize_range_t *copy; copy = (pagesize_range_t *)calloc(1,sizeof(pagesize_range_t)); if (copy) { copy->x_dim_min = data->x_dim_min; copy->x_dim_max = data->x_dim_max; copy->y_dim_min = data->y_dim_min; copy->y_dim_max = data->y_dim_max; } return copy; } void * copy_media(void *media, void *user_data) { media_col_t *data = (media_col_t *)media; media_col_t *copy; copy = (media_col_t *)calloc(1, sizeof(media_col_t)); if (copy) { copy->x = data->x; copy->y = data->y; copy->left_margin=data->left_margin; copy->right_margin=data->right_margin; copy->top_margin=data->top_margin; copy->bottom_margin=data->bottom_margin; copy->media_source = NULL; copy->media_type = NULL; if (data->media_source != NULL) { copy->media_source = (char *)malloc(sizeof(char)*32); strcpy(copy->media_source,data->media_source); } if (data->media_type != NULL) { copy->media_type = (char *)malloc(sizeof(char)*32);; strcpy(copy->media_type,data->media_type); } } return copy; } void* copy_media_count(void *media, void* user_data) { mediacol_count_t *prev = (mediacol_count_t *)media; mediacol_count_t *copy; copy = (mediacol_count_t*)calloc(1,sizeof(mediacol_count_t)); if (copy) { copy->data = copy_media(prev->data,NULL); copy->count = prev->count; } return copy; } void* copy_pagesize_count(void *pagesize_count, void* user_data) { pagesize_count_t *prev = (pagesize_count_t *)pagesize_count; pagesize_count_t *copy; copy = (pagesize_count_t*)calloc(1,sizeof(pagesize_count_t)); copy->pagesize = malloc(sizeof(char)*32); if (copy) { strcpy(copy->pagesize,prev->pagesize); copy->count = prev->count; } return copy; } int compare_pagesize_count(void* pagesize_a, void* pagesize_b,void* user_data) { pagesize_count_t *a = (pagesize_count_t *) pagesize_a; pagesize_count_t *b = (pagesize_count_t *) pagesize_b; if (!strcmp(a->pagesize,b->pagesize)) return 0; return 1; } /* * 'create_media_col()' - Create a media-col value. */ static ipp_t * create_media_col(int width, int length, int left_margin, int right_margin, int top_margin, int bottom_margin, char *media_source, char *media_type) { ipp_t *media_col = ippNew(), /* media-col value */ *media_size = create_media_size(width, length); ippAddCollection(media_col, IPP_TAG_PRINTER, "media-size", media_size); ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin",bottom_margin); ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin", left_margin); ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin",right_margin); ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin", top_margin); if (media_source != NULL) ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-source", NULL,media_source); if (media_type != NULL) ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type", NULL,media_type); ippDelete(media_size); return (media_col); } int compare_mediasize(void *media_a, void *media_b, void *user_data) { media_size_t *a=(media_size_t *)media_a; media_size_t *b=(media_size_t *)media_b; if (a->x < b->x) return -1; else if (a->x > b->x) return 1; else{ if (a->y == b->y) return 0; else if (a->y < b->y) return -1; return 1; } } int compare_int(int a, int b) { if (a < b) return -1; else if (a > b) return 1; return 0; } int compare_rangesize(void *range_a,void *range_b, void *user_data) { pagesize_range_t *a = (pagesize_range_t *)range_a; pagesize_range_t *b = (pagesize_range_t *)range_b; int value; if ((value = compare_int(a->x_dim_min, b->x_dim_min)) == 0) { if ((value = compare_int(a->x_dim_max, b->x_dim_max)) == 0) { if ((value = compare_int(a->y_dim_min, b->y_dim_min)) == 0) { if ((value = compare_int(a->y_dim_max, b->y_dim_max)) == 0) { return 0; } } } } return value; } int compare_media(void *media_a, void *media_b, void *user_data) { media_col_t *a=(media_col_t *)media_a; media_col_t *b=(media_col_t *)media_b; int value; if ((value = compare_int(a->x, b->x)) == 0) { if ((value = compare_int(a->y, b->y)) == 0) { if ((value = compare_int(a->top_margin, b->top_margin)) == 0) { if ((value = compare_int(a->bottom_margin, b->bottom_margin)) == 0) { if ((value = compare_int(a->right_margin, b->right_margin)) == 0) { if ((value = compare_int(a->left_margin, b->left_margin)) == 0) { if (a->media_source == NULL && b->media_source == NULL) { if (a->media_type == NULL && b->media_type == NULL) return 0; if (a->media_type == NULL) return -1; if (b->media_type == NULL) return 1; return strcmp(a->media_type, b->media_type); } if (a->media_source == NULL) return -1; if (b->media_source == NULL) return 1; if (!strcmp(a->media_source, b->media_source)) { if (a->media_type==NULL && b->media_type == NULL) return 0; if (a->media_type==NULL) return -1; if (b->media_type==NULL) return 1; return strcmp(a->media_type, b->media_type); } else return strcmp(a->media_source, b->media_source); } } } } } } return value; } int compare_media_count(void* media_a, void* media_b,void* user_data) { mediacol_count_t *a = (mediacol_count_t*) media_a; mediacol_count_t *b = (mediacol_count_t*) media_b; return (compare_media(a->data, b->data, NULL)); } void * copy_default_str(void *data, void *user_data) { default_str_attribute_t *prev = (default_str_attribute_t *)data; default_str_attribute_t *copy; copy = (default_str_attribute_t *)calloc(1, sizeof(default_str_attribute_t)); if (copy) { copy->value = (char *)malloc(sizeof(char)*100); copy->value = strdup(prev->value); copy->count = prev->count; } return copy; } char *strstrip(char *s) { size_t size; char *end; size = strlen(s); if (!size) return s; end = s + size - 1; while (end >= s && isspace(*end)) end--; *(end + 1) = '\0'; while (*s && isspace(*s)) s++; return s; } int compare_default_str(void *defstr_a, void *defstr_b, void *user_data) { default_str_attribute_t *a=(default_str_attribute_t *)defstr_a; default_str_attribute_t *b=(default_str_attribute_t *)defstr_b; return strcmp(a->value, b->value); } void * copy_counted_res(void *data, void *user_data) { resolution_count_t *prev = (resolution_count_t *)data; resolution_count_t *copy; copy = (resolution_count_t *)calloc(1, sizeof(resolution_count_t)); if (copy) { copy->res = (res_t *)malloc(sizeof(res_t)); copy->res->x = prev->res->x; copy->res->y = prev->res->y; copy->count = prev->count; } return copy; } int compare_counted_res(void *defres_a, void *defres_b, void *user_data) { resolution_count_t *a=(resolution_count_t *)defres_a; resolution_count_t *b=(resolution_count_t *)defres_b; return compare_resolutions(a->res, b->res, NULL); } /* * 'pwg_compare_sizes()' - Compare two media sizes... */ static int /* O - Result of comparison */ pwg_compare_sizes(cups_size_t *a, /* I - First media size */ cups_size_t *b) /* I - Second media size */ { return (strcmp(a->media, b->media)); } /* * 'pwg_copy_size()' - Copy a media size. */ static cups_size_t * /* O - New media size */ pwg_copy_size(cups_size_t *size) /* I - Media size to copy */ { cups_size_t *newsize = (cups_size_t *)calloc(1, sizeof(cups_size_t)); /* New media size */ if (newsize) memcpy(newsize, size, sizeof(cups_size_t)); return (newsize); } /* Function returns number of jobs queued on printer*/ int /* O - Number of jobs */ get_number_of_jobs(http_t *http, /* I - Connection to server */ const char *uri, /* I - uri of printer */ int myjobs, /* I - 0 = all users, 1 = mine */ int whichjobs) /* I - CUPS_WHICHJOBS_ALL, CUPS_WHICHJOBS_ACTIVE, or CUPS_WHICHJOBS_COMPLETED */ { int n; /* Number of jobs */ ipp_t *request, /* IPP Request */ *response; /* IPP Response */ ipp_attribute_t *attr; /* Current attribute */ int id; /* job-id */ static const char * const attrs[] = /* Requested attributes */ { "job-id" }; httpReconnect2(http, 30000, NULL); /* * Build an IPP_GET_JOBS request, which requires the following * attributes: * * attributes-charset * attributes-natural-language * printer-uri * requesting-user-name * which-jobs * my-jobs * requested-attributes */ /* Generating IPP Request */ request = ippNewRequest(IPP_OP_GET_JOBS); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); if (myjobs) ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1); if (whichjobs == CUPS_WHICHJOBS_COMPLETED) ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "which-jobs", NULL, "completed"); else if (whichjobs == CUPS_WHICHJOBS_ALL) ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "which-jobs", NULL, "all"); ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", sizeof(attrs) / sizeof(attrs[0]), NULL, attrs); /* Do the request and get back a response... */ n = 0; if ((response = cupsDoRequest(http, request, "/")) != NULL) { for (attr = ippFirstAttribute(response); attr; attr = ippNextAttribute(response)){ /* Skip leading attributes until we hit a job... */ while (attr && ippGetGroupTag(attr) != IPP_TAG_JOB) attr = ippNextAttribute(response); if (!attr) break; /* Pull the needed attributes from this job */ id = 0; while (attr && ippGetGroupTag(attr) == IPP_TAG_JOB) { if (!strcmp(ippGetName(attr), "job-id") && ippGetValueTag(attr) == IPP_TAG_INTEGER) id = ippGetInteger(attr,0); attr = ippNextAttribute(response); } /* See if we have everything needed */ if (!id){ if (!attr) break; else continue; } /* Incrementing number of jobs*/ n ++; if (!attr) break; } ippDelete(response); } if (n == 0) return (-1); else return (n); } static const char * password_callback (const char *prompt, http_t *http, const char *method, const char *resource, void *user_data) { return NULL; } http_t * httpConnectEncryptShortTimeout(const char *host, int port, http_encryption_t encryption) { return (httpConnect2(host, port, NULL, AF_UNSPEC, encryption, 1, 3000, NULL)); } int http_timeout_cb(http_t *http, void *user_data) { debug_printf("HTTP timeout! (consider increasing HttpLocalTimeout/HttpRemoteTimeout value)\n"); timeout_reached = 1; return 0; } static http_t * http_connect_local (void) { const char *server = cupsServer(); int port = ippPort(); if (!local_conn) { if (server[0] == '/') debug_printf("cups-browsed: Creating http connection to local CUPS daemon via domain socket: %s\n", server); else debug_printf("cups-browsed: Creating http connection to local CUPS daemon: %s:%d\n", server, port); local_conn = httpConnectEncryptShortTimeout(server, port, cupsEncryption()); } if (local_conn) httpSetTimeout(local_conn, HttpLocalTimeout, http_timeout_cb, NULL); else { if (server[0] == '/') debug_printf("cups-browsed: Failed creating http connection to local CUPS daemon via domain socket: %s\n", server); else debug_printf("cups-browsed: Failed creating http connection to local CUPS daemon: %s:%d\n", server, port); } return local_conn; } static void http_close_local (void) { if (local_conn) { httpClose (local_conn); local_conn = NULL; } } int /* O - 1 on match, 0 otherwise */ _cups_isalpha(int ch) /* I - Character to test */ { return ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')); } int /* O - 1 on match, 0 otherwise */ _cups_islower(int ch) /* I - Character to test */ { return (ch >= 'a' && ch <= 'z'); } int /* O - Converted character */ _cups_toupper(int ch) /* I - Character to convert */ { return (_cups_islower(ch) ? ch - 'a' + 'A' : ch); } static void pwg_ppdize_name(const char *ipp, /* I - IPP keyword */ char *name, /* I - Name buffer */ size_t namesize) /* I - Size of name buffer */ { char *ptr, /* Pointer into name buffer */ *end; /* End of name buffer */ *name = (char)toupper(*ipp++); for (ptr = name + 1, end = name + namesize - 1; *ipp && ptr < end;) { if (*ipp == '-' && _cups_isalpha(ipp[1])) { ipp ++; *ptr++ = (char)toupper(*ipp++ & 255); } else *ptr++ = *ipp++; } *ptr = '\0'; } void add_mimetype_attributes(char* cluster_name, ipp_t **merged_attributes) { int count, i; remote_printer_t *p; const char *str; char *q; cups_array_t *list; ipp_attribute_t *attr; int num_value, attr_no; char* attributes[] = { "document-format-supported" }; for (attr_no = 0; attr_no < 1; attr_no++) { if ((list = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return ; num_value = 0; for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(cluster_name,p->queue_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; if ((attr = ippFindAttribute(p->prattrs,attributes[attr_no], IPP_TAG_MIMETYPE)) != NULL) { count = ippGetCount(attr); for (i = 0; i < count; i ++) { str = ippGetString(attr, i, NULL); if (!cupsArrayFind(list, (void *)str)){ cupsArrayAdd(list, (void *)str); num_value++; } } } } if (num_value != 0) { char *values[num_value]; for (q = (char *)cupsArrayFirst(list),i=0; q; q = (char *)cupsArrayNext(list),i++) { values[i]=malloc(sizeof(char) * (strlen(q) + 1)); snprintf(values[i], strlen(q) + 1, "%s", q); } ippAddStrings(*merged_attributes, IPP_TAG_PRINTER,IPP_TAG_MIMETYPE, attributes[attr_no], num_value, NULL, (const char * const *)values); for (int k = 0; k < i; k++) { free(values[k]); values[k] = NULL; } } cupsArrayDelete(list); list = NULL; } } /*add_tagzero_attributes - Adds attribute to the merged_attribute variable for the cluster. This function adds attribute with value tag IPP_TAG_ZERO */ void add_tagzero_attributes(char* cluster_name, ipp_t **merged_attributes) { int count, i; remote_printer_t *p; const char *str; char *q; cups_array_t *list; ipp_attribute_t *attr; int num_value, attr_no; char* attributes[] = { "media-supported", "output-bin-supported", "print-content-optimize-supported", "print-rendering-intent-supported", "print-scaling-supported" }; for (attr_no = 0; attr_no < 5; attr_no++) { /* Cups Array to store the values for the attribute*/ if ((list = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return ; num_value = 0; /* Iterating over all the printers in the cluster*/ for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(cluster_name, p->queue_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; if ((attr = ippFindAttribute(p->prattrs, attributes[attr_no], IPP_TAG_ZERO)) != NULL) { count = ippGetCount(attr); for(i = 0; i < count; i ++) { /* Pick next format from attribute */ str = ippGetString(attr, i, NULL); /* Add format to list, skip duplicates */ if (!cupsArrayFind(list, (void *)str)){ cupsArrayAdd(list, (void *)str); num_value ++; } } } } if (num_value != 0) { char *values[num_value]; /* Transferring attributes value from cups Array to char* array*/ for (q = (char *)cupsArrayFirst(list), i = 0; q; q = (char *)cupsArrayNext(list), i ++) { values[i] = malloc(sizeof(char) * (strlen(q) + 1)); snprintf(values[i], strlen(q) + 1, "%s", q); } ippAddStrings(*merged_attributes, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, attributes[attr_no], num_value, NULL, (const char * const *)values); for (int k = 0; k < i; k++) { free(values[k]); values[k] = NULL; } } cupsArrayDelete(list); list = NULL; } } /*add_keyword_attributes - Adds attributes to the merged_attribute variable for the cluster. This function adds attributes with value tag IPP_TAG_KEYWORD*/ void add_keyword_attributes(char* cluster_name, ipp_t **merged_attributes) { int count, i; remote_printer_t *p; const char *str; char *q; cups_array_t *list; ipp_attribute_t *attr; int num_value, attr_no; char* attributes[] = { "output-mode-supported", "urf-supported", "pwg-raster-document-type-supported", "media-source-supported", "media-type-supported", "print-color-mode-supported", "sides-supported" }; for (attr_no = 0; attr_no < 7; attr_no ++) { if ((list = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return; num_value = 0; for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(cluster_name, p->queue_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; if ((attr = ippFindAttribute(p->prattrs, attributes[attr_no], IPP_TAG_KEYWORD)) != NULL) { count = ippGetCount(attr); for (i = 0; i < count; i++) { str = ippGetString(attr, i, NULL); if (!cupsArrayFind(list, (void *)str)){ cupsArrayAdd(list, (void *)str); num_value ++; } } } } if (num_value != 0) { char *values[num_value]; for (q = (char *)cupsArrayFirst(list), i=0; q; q = (char *)cupsArrayNext(list), i ++) { values[i] = malloc(sizeof(char) * (strlen(q) + 1)); snprintf(values[i], strlen(q) + 1, "%s", q); } ippAddStrings(*merged_attributes, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, attributes[attr_no], num_value, NULL, (const char * const *)values); for (int k = 0; k < i; k++) { free(values[k]); values[k] = NULL; } } cupsArrayDelete(list); list = NULL; } } /*add_enum_attributes - Adds attributes to the merged_attribute variable for the cluster. This function adds attributes with value tag IPP_TAG_BEGIN_ENUM*/ void add_enum_attributes(char* cluster_name, ipp_t **merged_attributes) { int count, i, value; remote_printer_t *p; char *str = NULL; char *q; cups_array_t *list; ipp_attribute_t *attr; int num_value, attr_no; char* attributes[] = { "finishings-supported", "print-quality-supported", "finishing-template", "finishings-col-database" }; for (attr_no = 0; attr_no < 4; attr_no ++) { if ((list = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return ; str = malloc(sizeof(char) * 10); num_value = 0; for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(cluster_name,p->queue_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; if ((attr = ippFindAttribute(p->prattrs,attributes[attr_no], IPP_TAG_ENUM)) != NULL) { count = ippGetCount(attr); for (i = 0; i < count; i ++) { value = ippGetInteger(attr, i); sprintf(str,"%d",value); if (!cupsArrayFind(list, (void *)str)){ cupsArrayAdd(list, (void *)str); num_value++; } } } } if (num_value != 0){ int values[num_value]; for (q = (char *)cupsArrayFirst(list), i = 0;q; q = (char *)cupsArrayNext(list), i++) { values[i] = atoi(q); } ippAddIntegers(*merged_attributes, IPP_TAG_PRINTER,IPP_TAG_ENUM, attributes[attr_no], num_value,values); } if (str != NULL) { free(str); str = NULL; } cupsArrayDelete(list); list = NULL; } } /*add_margin_attribute - Adds margin attributes to the merged_attribute variable for the cluster.*/ void add_margin_attributes(char* cluster_name, ipp_t **merged_attributes) { int count, i, value; remote_printer_t *p; char *str; char *q; cups_array_t *list; ipp_attribute_t *attr; int num_value, attr_no; char* attributes[] = { "media-bottom-margin-supported", "media-left-margin-supported", "media-top-margin-supported", "media-right-margin-supported" }; for (attr_no = 0; attr_no < 4; attr_no++) { if ((list = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return ; str = malloc(sizeof(char)*10); num_value = 0; for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(cluster_name,p->queue_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; if ((attr = ippFindAttribute(p->prattrs,attributes[attr_no], IPP_TAG_INTEGER)) != NULL) { count = ippGetCount(attr); for (i = 0; i < count; i++) { value = ippGetInteger(attr, i); sprintf(str,"%d",value); if (!cupsArrayFind(list, (void *)str)){ cupsArrayAdd(list, (void *)str); num_value++; } } } } if (num_value != 0){ int values[num_value]; for (q = (char *)cupsArrayFirst(list),i=0;q; q = (char *)cupsArrayNext(list),i++) { values[i] = atoi(q); } ippAddIntegers(*merged_attributes, IPP_TAG_PRINTER,IPP_TAG_INTEGER, attributes[attr_no], num_value,values); } if (str != NULL) { free(str); str = NULL; } cupsArrayDelete(list); list = NULL; } } /*add_resolution_attributes - Adds resolution attributes to the merged_attribute for the cluster*/ void add_resolution_attributes(char* cluster_name, ipp_t **merged_attributes) { int count, i; remote_printer_t *p; ipp_attribute_t *attr; int num_resolution, attr_no; cups_array_t *res_array; res_t *res, *resolution; char* attributes[] = { "printer-resolution-supported", "pwg-raster-document-resolution-supported", "pclm-source-resolution-supported" }; for (attr_no = 0; attr_no < 3; attr_no ++) { res_array = NULL; res_array = resolutionArrayNew(); num_resolution = 0; for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(cluster_name, p->queue_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; if ((attr = ippFindAttribute(p->prattrs, attributes[attr_no], IPP_TAG_RESOLUTION)) != NULL) { for (i = 0, count = ippGetCount(attr); i < count; i ++) { if ((res = ippResolutionToRes(attr, i)) != NULL) { if (cupsArrayFind(res_array, res) == NULL) { cupsArrayAdd(res_array, res); num_resolution ++; } free_resolution(res, NULL); } } } } if (num_resolution) { int xres[num_resolution], yres[num_resolution]; for (i = 0, resolution=cupsArrayFirst(res_array); resolution; i ++, resolution = cupsArrayNext(res_array)) { xres[i] = resolution->x; yres[i] = resolution->y; } ippAddResolutions(*merged_attributes, IPP_TAG_PRINTER, attributes[attr_no], num_resolution, IPP_RES_PER_INCH, xres, yres); } cupsArrayDelete(res_array); res_array = NULL; } } /*add_mediasize_attribute - Adds media sizes to the merged_attribute for the printer*/ void add_mediasize_attributes(char* cluster_name, ipp_t **merged_attributes) { int count, i = 0; remote_printer_t *p; ipp_attribute_t *attr, *media_size_supported, *x_dim, *y_dim; int num_sizes, attr_no,num_ranges; ipp_t *media_size; cups_array_t *sizes, *size_ranges; media_size_t *temp, *media_s; pagesize_range_t *temp_range = NULL, *range = NULL; char* attributes[] = { "media-size-supported", }; sizes = cupsArrayNew3((cups_array_func_t)compare_mediasize, NULL, NULL, 0, (cups_acopy_func_t)copy_media_size, (cups_afree_func_t)free); size_ranges = cupsArrayNew3((cups_array_func_t)compare_rangesize, NULL, NULL, 0, (cups_acopy_func_t)copy_range_size, (cups_afree_func_t)free); temp = (media_size_t *)malloc(sizeof(media_size_t)); temp_range = (pagesize_range_t *)malloc(sizeof(pagesize_range_t)); for (attr_no = 0; attr_no < 1; attr_no ++) { num_sizes = 0; num_ranges = 0; for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(cluster_name,p->queue_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; if ((attr = ippFindAttribute(p->prattrs, attributes[attr_no], IPP_TAG_BEGIN_COLLECTION)) != NULL) { for (i = 0, count = ippGetCount(attr); i < count; i ++) { media_size = ippGetCollection(attr, i); x_dim = ippFindAttribute(media_size, "x-dimension", IPP_TAG_ZERO); y_dim = ippFindAttribute(media_size, "y-dimension", IPP_TAG_ZERO); if (ippGetValueTag(x_dim) == IPP_TAG_RANGE || ippGetValueTag(y_dim) == IPP_TAG_RANGE) { if (ippGetValueTag(x_dim) == IPP_TAG_RANGE) temp_range->x_dim_min = ippGetRange(x_dim, 0, &temp_range->x_dim_max); else temp_range->x_dim_min = temp_range->x_dim_max = ippGetInteger(x_dim, 0); if (ippGetValueTag(y_dim) == IPP_TAG_RANGE) temp_range->y_dim_min = ippGetRange(y_dim, 0, &temp_range->y_dim_max); else temp_range->y_dim_min = temp_range->y_dim_max = ippGetInteger(y_dim, 0); if (!cupsArrayFind(size_ranges,temp_range)) { cupsArrayAdd(size_ranges, temp_range); num_ranges++; } } else { temp->x = ippGetInteger(x_dim,0); temp->y = ippGetInteger(y_dim,0); if (!cupsArrayFind(sizes, temp)){ cupsArrayAdd(sizes, temp); num_sizes++; } } } } } media_size_supported = ippAddCollections(*merged_attributes, IPP_TAG_PRINTER, attributes[attr_no], num_sizes+num_ranges, NULL); if (num_sizes) { for (i = 0, media_s = cupsArrayFirst(sizes); media_s; i ++, media_s = cupsArrayNext(sizes)) { ipp_t *size = create_media_size(media_s->x, media_s->y); ippSetCollection(*merged_attributes, &media_size_supported, i, size); ippDelete(size); } } if (num_ranges) { for (range = cupsArrayFirst(size_ranges); range; i++, range = cupsArrayNext(size_ranges)) { ipp_t *size_range = create_media_range(range->x_dim_min, range->x_dim_max, range->y_dim_min, range->y_dim_max); ippSetCollection(*merged_attributes, &media_size_supported, i, size_range); ippDelete(size_range); } } } free(temp); free(temp_range); cupsArrayDelete(sizes); cupsArrayDelete(size_ranges); } /*add_mediadatabase_attribute - Adds media-col-database attributes for the cluster*/ void add_mediadatabase_attributes(char* cluster_name, ipp_t **merged_attributes) { int count, i; remote_printer_t *p; ipp_attribute_t *attr, *media_attr; int num_database, attr_no; cups_array_t *media_database; media_col_t *temp, *media_data; ipp_t *media_col, *media_size, *current_media; ipp_attribute_t *media_col_database; char media_source[32], media_type[32]; char* attributes[] = { "media-col-database", }; temp = (media_col_t *)malloc(sizeof(media_col_t)); media_database = cupsArrayNew3((cups_array_func_t)compare_media, NULL, NULL, 0, (cups_acopy_func_t)copy_media, (cups_afree_func_t)free); for (attr_no = 0; attr_no < 1; attr_no ++) { num_database = 0; for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(cluster_name, p->queue_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; if ((attr = ippFindAttribute(p->prattrs, attributes[attr_no], IPP_TAG_BEGIN_COLLECTION)) != NULL){ for (i = 0, count = ippGetCount(attr); i < count; i ++) { media_col = ippGetCollection(attr, i); media_size = ippGetCollection(ippFindAttribute(media_col, "media-size", IPP_TAG_BEGIN_COLLECTION), 0); temp->x = ippGetInteger(ippFindAttribute(media_size, "x-dimension", IPP_TAG_ZERO),0); temp->y = ippGetInteger(ippFindAttribute(media_size, "y-dimension", IPP_TAG_ZERO),0); temp->top_margin = ippGetInteger(ippFindAttribute(media_col, "media-top-margin", IPP_TAG_INTEGER), 0); temp->bottom_margin = ippGetInteger(ippFindAttribute(media_col, "media-bottom-margin", IPP_TAG_INTEGER), 0); temp->left_margin = ippGetInteger(ippFindAttribute(media_col, "media-left-margin", IPP_TAG_INTEGER), 0); temp->right_margin = ippGetInteger(ippFindAttribute(media_col, "media-right-margin", IPP_TAG_INTEGER), 0); media_type[0] = '\0'; media_source[0] = '\0'; temp->media_source = NULL; temp->media_type = NULL; if ((media_attr = ippFindAttribute(media_col, "media-type", IPP_TAG_KEYWORD)) != NULL) pwg_ppdize_name(ippGetString(media_attr, 0, NULL), media_type, sizeof(media_type)); if (strlen(media_type) > 1) { temp->media_type = (char*)malloc(sizeof(char) * 32); strcpy(temp->media_type, media_type); } if ((media_attr = ippFindAttribute(media_col, "media-source", IPP_TAG_KEYWORD)) != NULL) { pwg_ppdize_name(ippGetString(media_attr, 0, NULL), media_source, sizeof(media_source)); } if(strlen(media_source) > 1) { temp->media_source = (char*)malloc(sizeof(char) * 32); strcpy(temp->media_source, media_source); } if (!cupsArrayFind(media_database, temp)) { cupsArrayAdd(media_database, temp); num_database ++; } } } } if (num_database != 0) { media_col_database = ippAddCollections(*merged_attributes, IPP_TAG_PRINTER, attributes[attr_no], num_database, NULL); for (i = 0, media_data = cupsArrayFirst(media_database); media_data; i ++, media_data = cupsArrayNext(media_database)) { current_media = create_media_col(media_data->x, media_data->y, media_data->left_margin, media_data->right_margin, media_data->top_margin, media_data->bottom_margin, media_data->media_source, media_data->media_type); ippSetCollection(*merged_attributes, &media_col_database, i, current_media); ippDelete(current_media); } } } free(temp); cupsArrayDelete(media_database); } /*add_jobpresets_attribute - Adds presets attributes for the cluster*/ void add_jobpresets_attribute(char* cluster_name, ipp_t ** merged_attributes) { int count, i, num_preset = 0, preset_no = 0; remote_printer_t *p; cups_array_t *list, *added_presets; ipp_t *preset; ipp_attribute_t *attr; const char *preset_name; ipp_attribute_t *preset_attribute; if ((list = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return; if ((added_presets = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return; for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(cluster_name, p->queue_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; if ((attr = ippFindAttribute(p->prattrs, "job-presets-supported", IPP_TAG_BEGIN_COLLECTION)) != NULL) { for (i = 0, count = ippGetCount(attr); i < count; i ++) { preset = ippGetCollection(attr, i); preset_name = ippGetString(ippFindAttribute(preset, "preset-name", IPP_TAG_ZERO), 0, NULL); if (!cupsArrayFind(list, (void *)preset_name)) { cupsArrayAdd(list, (void *)preset_name); num_preset++; } } } } if (num_preset == 0) { cupsArrayDelete(list); cupsArrayDelete(added_presets); return; } preset_attribute = ippAddCollections(*merged_attributes, IPP_TAG_PRINTER, "job-presets-supported", num_preset, NULL); for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if ((attr = ippFindAttribute(p->prattrs, "job-presets-supported", IPP_TAG_BEGIN_COLLECTION)) != NULL) { for (i = 0, count = ippGetCount(attr); i < count; i ++) { preset = ippGetCollection(attr, i); preset_name = ippGetString(ippFindAttribute(preset, "preset-name", IPP_TAG_ZERO), 0, NULL); if (!cupsArrayFind(added_presets, (void *)preset_name)) { cupsArrayAdd(added_presets, (void *)preset_name); ippSetCollection(*merged_attributes, &preset_attribute, i, preset); preset_no++; } else continue; } } } cupsArrayDelete(list); cupsArrayDelete(added_presets); } /* get_pagesize: Function returns the standard/custom page size using generate_sizes function from ppdgenerator.c*/ static cups_array_t* get_pagesize(ipp_t *printer_attributes) { cups_array_t *sizes, *page_media; cups_size_t *size; ipp_attribute_t *defattr; char *ppdsizename, *temp; int min_length = INT_MAX, min_width = INT_MAX, max_length = 0, max_width = 0, bottom, left, right, top; char ppdname[41]; ppdsizename = (char *)malloc(sizeof(char) * 128); sizes = generate_sizes(printer_attributes, &defattr, &min_length, &min_width, &max_length, &max_width, &bottom, &left, &right, &top,ppdname); if ((page_media = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; for (size = (cups_size_t *)cupsArrayFirst(sizes); size; size = (cups_size_t *)cupsArrayNext(sizes)) { strcpy(ppdsizename, size->media); if (( temp = strchr(ppdsizename, ' ')) != NULL) *temp = '\0'; cupsArrayAdd(page_media, ppdsizename); } free(ppdsizename); cupsArrayDelete(sizes); return page_media; } /*get_mediadata - This function extracts the MediaType, InputSlot and OutputBin supported, using IPP Response message of the printer*/ cups_array_t* get_mediadata(ipp_t *printer_attributes, char* requested_attr) { ipp_attribute_t *attr; int count, i; cups_array_t *media_data; const char *keyword; /* Keyword value */ char ppdname[41]; char requested_option[30]; if (!strcmp(requested_attr, "MediaType")) strcpy(requested_option, "media-type-supported"); else if (!strcmp(requested_attr, "InputSlot")) strcpy(requested_option, "media-source-supported"); else if (!strcmp(requested_attr, "OutputBin")) strcpy(requested_option, "output-bin-supported"); else return NULL; if ((media_data = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, requested_option, IPP_TAG_ZERO)) != NULL && (count = ippGetCount(attr)) > 1) { for (i = 0, count = ippGetCount(attr); i < count; i ++) { keyword = ippGetString(attr, i, NULL); pwg_ppdize_name(keyword, ppdname, sizeof(ppdname)); cupsArrayAdd(media_data, ppdname); } } return media_data; } /*get_mimetype_attribute - Adds attributes to the merged_attribute variable for the cluster. This function adds attribute with value tag IPP_TAG_MIMETYPE*/ cups_array_t* get_mimetype_attributes(ipp_t *printer_attributes) { int count, i; const char *str; cups_array_t *document_formats; ipp_attribute_t *attr; if ((document_formats = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, "document-format-supported", IPP_TAG_MIMETYPE)) != NULL) { count = ippGetCount(attr); for (i = 0; i < count; i ++) { str = ippGetString(attr, i, NULL); if (!cupsArrayFind(document_formats, (void *)str)) cupsArrayAdd(document_formats, (void *)str); } } return document_formats; } /*get_staplelocation: This function returns the supported staple locations of the printer */ cups_array_t* get_staplelocation(ipp_t *printer_attributes) { ipp_attribute_t *attr; int count, value, i; const char *name; cups_array_t *staplelocation; if ((staplelocation = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, "finishings-supported", IPP_TAG_ENUM)) != NULL) { count = ippGetCount(attr); for (i = 0; i < count; i ++) { value = ippGetInteger(attr, i); name = ippEnumString("finishings", value); if (!strncmp(name, "staple-", 7) || !strncmp(name, "bind-", 5) || !strncmp(name, "edge-stitch-", 12) || !strcmp(name, "saddle-stitch")) if (!cupsArrayFind(staplelocation,(void*)name)) cupsArrayAdd(staplelocation,(void*)name); } } return staplelocation; } /*get_foldtype - Function returns the supported foldtype for the printer*/ cups_array_t* get_foldtype(ipp_t *printer_attributes) { ipp_attribute_t *attr; int count, value, i; const char *name; cups_array_t *foldtype; if ((foldtype = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, "finishings-supported", IPP_TAG_ENUM)) != NULL) { count = ippGetCount(attr); for (i = 0; i < count; i ++) { value = ippGetInteger(attr, i); name = ippEnumString("finishings", value); if (!strncmp(name, "fold-", 5)) if (!cupsArrayFind(foldtype, (void*)name)) cupsArrayAdd(foldtype, (void*)name); } } return foldtype; } /*get_finishings - Function returns the supported finishings for the printer*/ cups_array_t* get_finishings(ipp_t *printer_attributes) { ipp_attribute_t *attr; int count, value, i; const char *name; cups_array_t *finishings; if ((finishings = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, "finishings-supported", IPP_TAG_ENUM)) != NULL) { count = ippGetCount(attr); for (i = 0; i < count; i ++) { value = ippGetInteger(attr, i); name = ippEnumString("finishings", value); if (!cupsArrayFind(finishings, (void*)name)) cupsArrayAdd(finishings, (void*)name); } } return finishings; } /*get_punchmedia - Returns the puchmedia supported by the printer*/ cups_array_t* get_punchmedia(ipp_t *printer_attributes) { ipp_attribute_t *attr; int count, value, i; const char *name; cups_array_t *punchmedia; if ((punchmedia = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, "finishings-supported", IPP_TAG_ENUM)) != NULL) { count = ippGetCount(attr); for (i = 0; i < count; i ++) { value = ippGetInteger(attr, i); name = ippEnumString("finishings", value); if (!strncmp(name, "punch-", 6)) if (!cupsArrayFind(punchmedia, (void*)name)) cupsArrayAdd(punchmedia, (void*)name); } } return punchmedia; } /*get_duplex - Function returns whether the printer support Duplex, DuplexTumble, DuplexNoTumble using attributes returned by the IPP Request*/ cups_array_t* get_duplex(ipp_t *printer_attributes) { ipp_attribute_t *attr; int count, i; cups_array_t *duplex_options; const char *str; if ((duplex_options = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, "sides-supported", IPP_TAG_KEYWORD)) != NULL) { count = ippGetCount(attr); for (i = 0; i < count; i ++) { str = ippGetString(attr, i, NULL); if (!strcmp(str, "one-sided")) cupsArrayAdd(duplex_options, "None"); else if (!strcmp(str, "two-sided-long-edge")) cupsArrayAdd(duplex_options, "DuplexNoTumble"); else if (!strcmp(str, "two-sided-short-edge")) cupsArrayAdd(duplex_options, "DuplexTumble"); } } return duplex_options; } /* get_colormodel - Returns the colormodel supported by the printer*/ cups_array_t* get_colormodel(ipp_t *printer_attributes) { ipp_attribute_t *attr; int count, i; cups_array_t *colormodel; const char *keyword; int have_bi_level = 0, have_mono = 0; if ((colormodel = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, "urf-supported", IPP_TAG_KEYWORD)) == NULL) if ((attr = ippFindAttribute(printer_attributes, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD)) == NULL) if ((attr = ippFindAttribute(printer_attributes, "print-color-mode-supported", IPP_TAG_KEYWORD)) == NULL) attr = ippFindAttribute(printer_attributes, "output-mode-supported", IPP_TAG_KEYWORD); if (attr && ippGetCount(attr) > 0) { for (i = 0, count = ippGetCount(attr); i < count; i ++) { keyword = ippGetString(attr, i, NULL); if (!have_bi_level && (!strcasecmp(keyword, "black_1") || !strcmp(keyword, "bi-level") || !strcmp(keyword, "process-bi-level"))) { cupsArrayAdd(colormodel,"FastGray"); have_bi_level = 1; } else if (!have_mono &&(!strcasecmp(keyword, "sgray_8") || !strncmp(keyword, "W8", 2) || !strcmp(keyword, "monochrome") || !strcmp(keyword, "process-monochrome"))) { have_mono = 1; cupsArrayAdd(colormodel,"Gray"); } else if (!strcasecmp(keyword, "sgray_16") || !strncmp(keyword, "W8-16", 5) || !strncmp(keyword, "W16", 3)) cupsArrayAdd(colormodel,"Gray16"); else if (!strcasecmp(keyword, "srgb_8") || !strncmp(keyword, "SRGB24", 6) || !strcmp(keyword, "color")) cupsArrayAdd(colormodel,"RGB"); else if ((!strcasecmp(keyword, "srgb_16") || !strncmp(keyword, "SRGB48", 6)) && !ippContainsString(attr, "srgb_8")) cupsArrayAdd(colormodel,"RGB"); else if (!strcasecmp(keyword, "adobe-rgb_16") || !strncmp(keyword, "ADOBERGB48", 10) || !strncmp(keyword, "ADOBERGB24-48", 13)) cupsArrayAdd(colormodel,"AdobeRGB"); else if ((!strcasecmp(keyword, "adobe-rgb_8") || !strcmp(keyword, "ADOBERGB24")) && !ippContainsString(attr, "adobe-rgb_16")) cupsArrayAdd(colormodel,"AdobeRGB"); else if ((!strcasecmp(keyword, "black_8") && !ippContainsString(attr, "black_16")) || !strcmp(keyword, "DEVW8")) cupsArrayAdd(colormodel,"DeviceGray"); else if (!strcasecmp(keyword, "black_16") || !strcmp(keyword, "DEVW16") || !strcmp(keyword, "DEVW8-16")) cupsArrayAdd(colormodel,"DeviceGray"); else if ((!strcasecmp(keyword, "cmyk_8") && !ippContainsString(attr, "cmyk_16")) || !strcmp(keyword, "DEVCMYK32")) cupsArrayAdd(colormodel,"CMYK"); else if (!strcasecmp(keyword, "cmyk_16") || !strcmp(keyword, "DEVCMYK32-64") || !strcmp(keyword, "DEVCMYK64")) cupsArrayAdd(colormodel,"CMYK"); else if ((!strcasecmp(keyword, "rgb_8") && !ippContainsString(attr, "rgb_16")) || !strcmp(keyword, "DEVRGB24")) cupsArrayAdd(colormodel,"DeviceRGB"); else if (!strcasecmp(keyword, "rgb_16") || !strcmp(keyword, "DEVRGB24-48") || !strcmp(keyword, "DEVRGB48")) cupsArrayAdd(colormodel,"DeviceRGB"); } } return colormodel; } /* get_printquality - Returns the print qualities supported by the printer*/ cups_array_t* get_printquality(ipp_t *printer_attributes) { ipp_attribute_t *quality; cups_array_t *print_qualities; if ((print_qualities = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((quality=ippFindAttribute(printer_attributes, "print-quality-supported", IPP_TAG_ENUM)) != NULL) { if (ippContainsInteger(quality, IPP_QUALITY_DRAFT)) cupsArrayAdd(print_qualities, "3"); if (ippContainsInteger(quality, IPP_QUALITY_HIGH)) cupsArrayAdd(print_qualities, "5"); cupsArrayAdd(print_qualities, "4"); } return print_qualities; } /* get_job_data - Returns the job_sheets,multiple-document-handling supported by the printer*/ cups_array_t* get_job_data(ipp_t *printer_attributes, char* requested_attr) { ipp_attribute_t *attr; cups_array_t *job_data; int i, count; const char* str; if ((job_data = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, requested_attr, IPP_TAG_KEYWORD)) != NULL) { for(i = 0, count = ippGetCount(attr); i < count; i ++) { str = ippGetString(attr, i, NULL); if (!cupsArrayFind(job_data, (void *)str)) cupsArrayAdd(job_data, (void*)str); } } return job_data; } /* get_finishingtemplate - Returns the Finishing Templates supported by the printer*/ cups_array_t* get_finishingtemplate(ipp_t *printer_attributes) { ipp_attribute_t *attr; cups_array_t *finishing_templates; ipp_t *finishing_col; /* Current finishing collection */ int count, i; const char *keyword; if ((finishing_templates = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, "finishings-col-database", IPP_TAG_BEGIN_COLLECTION)) != NULL) { count = ippGetCount(attr); for (i = 0; i < count; i ++) { finishing_col = ippGetCollection(attr, i); keyword = ippGetString(ippFindAttribute(finishing_col, "finishing-template", IPP_TAG_ZERO), 0, NULL); if (!keyword || cupsArrayFind(finishing_templates, (void *)keyword)) continue; if (strncmp(keyword, "fold-", 5) && (strstr(keyword, "-bottom") || strstr(keyword, "-left") || strstr(keyword, "-right") || strstr(keyword, "-top"))) continue; cupsArrayAdd(finishing_templates, (void*)keyword); } } return finishing_templates; } /* get_printing_data - Returns the print-content-optimize,print-rendering-intent and print-scaling attributes for the printer*/ cups_array_t* get_printing_data(ipp_t *printer_attributes,char* requested_attr) { ipp_attribute_t *attr; int count, i; cups_array_t *printing_support; const char *keyword; char requested_option[40]; if(!strcmp(requested_attr, "print-content-optimize")) strcpy(requested_option, "print-content-optimize-supported"); else if (!strcmp(requested_attr, "print-rendering-intent")) strcpy(requested_option, "print-rendering-intent-supported"); else if(!strcmp(requested_attr, "print-scaling")) strcpy(requested_option, "print-scaling-supported"); else if (!strcmp(requested_attr, "job-sheets-supported")) strcpy(requested_option, "job-sheets-supported"); else return NULL; if ((printing_support = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, requested_option, IPP_TAG_ZERO)) != NULL && (count = ippGetCount(attr)) > 1) { for (i = 0, count = ippGetCount(attr); i < count; i ++) { keyword = ippGetString(attr, i, NULL); cupsArrayAdd(printing_support, (void *)keyword); } } return printing_support; } /*get_presets - Returns a list of presets name supported by the printer*/ cups_array_t* get_presets(ipp_t *printer_attributes) { ipp_attribute_t *attr; int count, i; cups_array_t *presets; ipp_t *preset; const char *preset_name; if ((presets = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, "job-presets-supported", IPP_TAG_BEGIN_COLLECTION)) != NULL && (count = ippGetCount(attr)) > 1) { for (i = 0, count = ippGetCount(attr); i < count; i ++) { preset = ippGetCollection(attr, i); preset_name = ippGetString(ippFindAttribute(preset, "preset-name", IPP_TAG_ZERO), 0, NULL); if(!cupsArrayFind(presets, (void*)preset_name)) cupsArrayAdd(presets, (void *)preset_name); } } return presets; } /* get_booklet - Returns True if booklet is supported */ cups_array_t* get_booklet(ipp_t *printer_attributes) { ipp_attribute_t *attr; cups_array_t *booklet; if ((booklet = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; if ((attr = ippFindAttribute(printer_attributes, "finishings-supported", IPP_TAG_ENUM)) != NULL) { if (ippContainsInteger(attr, IPP_FINISHINGS_BOOKLET_MAKER)) { /* Assuming that the printer which supports Booklet also supports printing without Booklet, so for this printer we will return both "True" and "False" */ cupsArrayAdd(booklet, "True"); } } cupsArrayAdd(booklet, "False"); return booklet; } /* get_supported_options - Function returns various attributes supported by the printer, such as PageSize,ColorModel etc.*/ cups_array_t* get_supported_options(ipp_t *printer_attributes, char* option) { if (!strcmp(option, "PageSize") || !strcmp(option, "PageRegion")) return get_pagesize(printer_attributes); else if (!strcmp(option, "MediaType") || !strcmp(option, "InputSlot") || !strcmp(option, "OutputBin")) return get_mediadata(printer_attributes, option); else if (!strcmp(option, "StapleLocation")) return get_staplelocation(printer_attributes); else if (!strcmp(option, "FoldType")) return get_foldtype(printer_attributes); else if (!strcmp(option, "PunchMedia")) return get_punchmedia(printer_attributes); else if (!strcmp(option, "cupsFinishingTemplate")) return get_finishingtemplate(printer_attributes); else if (!strcmp(option, "cupsPrintQuality")) return get_printquality(printer_attributes); else if (!strcmp(option, "job-sheets-supported") || !strcmp(option, "print-content-optimize") || !strcmp(option, "print-rendering-intent") || !strcmp(option, "print-scaling")) return get_printing_data(printer_attributes, option); else if (!strcmp(option, "APPrinterPreset")) return get_presets(printer_attributes); else if(!strcmp(option, "Booklet")) return get_booklet(printer_attributes); else if(!strcmp(option, "ColorModel")) return get_colormodel(printer_attributes); else if (!strcmp(option, "Duplex")) return get_duplex(printer_attributes); else if (!strcmp(option, "multiple-document-handling-supported") || !strcmp(option, "cover-back-supported") || !strcmp(option, "cover-front-supported") || !strcmp(option, "cover-type-supported") || !strcmp(option, "media-type-supported")) return get_job_data(printer_attributes, option); else if (!strcmp(option,"finishings-supported")) return get_finishings(printer_attributes); return NULL; } /*check_printer_with_options - Checks whether a printer in an cluster supports option1 for keyword at value idx_option1 in ppd_keywords[] and option2 for keyword at value idx_option2*/ int check_printer_with_options(char* cluster_name, int idx_option1, char* option1, int idx_option2, char* option2) { remote_printer_t *p; cups_array_t *first_attributes_value; cups_array_t *second_attributes_value; char *borderless_pagesize = NULL; int option1_is_size = 0, option2_is_size = 0; unsigned long int max_length = 0, option1_len = 0, option2_len = 0, t_len = 0; char t[] = ".Borderless"; t_len = strlen(t); if (option1) option1_len = strlen(option1); if (option2) option2_len = strlen(option2); /* Seems to be possible to have both options...*/ max_length = option1_len + option2_len + (2 * t_len) + 1; borderless_pagesize = (char *)malloc(sizeof(char) * max_length); if (borderless_pagesize == NULL) { debug_printf("check_printer_with_options: Run out of memory.\n"); return 0; } memset(borderless_pagesize, 0, max_length); if (!strcmp(ppd_keywords[idx_option1], "PageSize") || !strcmp(ppd_keywords[idx_option1], "PageRegion")) { /* Check that we are generating .Borderless for the correct size, i.e We are generating 4x5.Borderless for 4x5 and not generating 4x5.Borderless.Borderless for 4x5.Borderless */ if (option1_len >= 11 && !strcmp(&option1[option1_len - t_len], t)) ; else { strcat(borderless_pagesize, option1); strcat(borderless_pagesize, t); option1_is_size = 1; } } if (!strcmp(ppd_keywords[idx_option2], "PageSize") || !strcmp(ppd_keywords[idx_option2], "PageRegion")) { if(option2_len >=11 && !strcmp(&option2[option2_len - t_len], t)) ; else { strcat(borderless_pagesize, option2); strcat(borderless_pagesize, t); option2_is_size = 1; } } for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if(strcmp(cluster_name, p->queue_name)) continue; first_attributes_value = get_supported_options(p->prattrs, ppd_keywords[idx_option1]); if(cupsArrayFind(first_attributes_value, (void*)option1) || (option1_is_size && cupsArrayFind(first_attributes_value, (void*)borderless_pagesize))) { second_attributes_value = get_supported_options(p->prattrs, ppd_keywords[idx_option2]); if (cupsArrayFind(second_attributes_value,(void*)option2) || (option2_is_size && cupsArrayFind(second_attributes_value, (void*)borderless_pagesize))) { free(borderless_pagesize); return 1; } } } free(borderless_pagesize); return 0; } /* The function returns a array containint the sizes supported by the cluster*/ cups_array_t* get_cluster_sizes(char* cluster_name) { cups_array_t *sizes = NULL; cups_array_t *cluster_sizes = NULL, *sizes_ppdname; cups_size_t *size; remote_printer_t *p; ipp_attribute_t *defattr; char ppdname[41], pagesize[128]; char* first_space; int min_length, min_width, max_length, max_width, bottom, left, right, top; cluster_sizes = cupsArrayNew3((cups_array_func_t)pwg_compare_sizes, NULL, NULL, 0, (cups_acopy_func_t)pwg_copy_size, (cups_afree_func_t)free); sizes_ppdname = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free); for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (!strcmp(p->queue_name, cluster_name)) { if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; defattr = NULL; min_length = INT_MAX; min_width = INT_MAX; max_length = 0; max_width = 0; bottom = 0; left = 0; right = 0; top = 0; sizes = generate_sizes(p->prattrs, &defattr, &min_length, &min_width, &max_length, &max_width, &bottom, &left, &right, &top, ppdname); for (size = (cups_size_t *)cupsArrayFirst(sizes); size; size = (cups_size_t *)cupsArrayNext(sizes)) { if (!cupsArrayFind(cluster_sizes, size)) { strcpy(pagesize, size->media); if ((first_space = strchr(pagesize, ' ')) != NULL) { *first_space = '\0'; } if (!cupsArrayFind(sizes_ppdname, pagesize)) { cupsArrayAdd(cluster_sizes, size); cupsArrayAdd(sizes_ppdname, pagesize); } } } cupsArrayDelete(sizes); sizes = NULL; } } cupsArrayDelete(sizes_ppdname); return cluster_sizes; } /* generate_cluster_conflicts - Function generates conflicts for the cluster*/ cups_array_t* generate_cluster_conflicts(char* cluster_name, ipp_t *merged_attributes) { remote_printer_t *p; cups_array_t *conflict_pairs = NULL; int i, k, j, no_of_printers = 0, no_of_ppd_keywords; cups_array_t *printer_first_options = NULL, *printer_second_options = NULL; char *opt1, *opt2, constraint[100], *ppdsizename, *temp; cups_array_t *sizes = NULL, *pagesizes; cups_size_t *size; /* Cups Array to store the conflicts*/ ppdsizename = (char *)malloc(sizeof(char) * 128); if ((conflict_pairs = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; /* Storing all the values supported by the cluster in cluster_options*/ no_of_ppd_keywords = sizeof(ppd_keywords) / sizeof(ppd_keywords[0]); cups_array_t *cluster_options[no_of_ppd_keywords]; for(i = 0; i < no_of_ppd_keywords; i ++) { if (strcmp(ppd_keywords[i], "PageSize") && strcmp(ppd_keywords[i], "PageRegion")) cluster_options[i] = get_supported_options(merged_attributes,ppd_keywords[i]); else { sizes = get_cluster_sizes(cluster_name); if ((pagesizes = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) return NULL; for (size = (cups_size_t *)cupsArrayFirst(sizes); size; size = (cups_size_t *)cupsArrayNext(sizes)) { strcpy(ppdsizename, size->media); if ((temp = strchr(ppdsizename, ' ')) != NULL) *temp = '\0'; cupsArrayAdd(pagesizes, ppdsizename); } cluster_options[i] = pagesizes; cupsArrayDelete(sizes); sizes = NULL; } } /* Algorithm to find constraints: We iterate over printer, if we find a value for a keyword which is supported by the cluster but not by the printer, that value can be part of the conflict. With this value v and a new value (for an different keyword, at index more than the index of first keyword), we generate a pair (v,u) and then we check whether some printer satisfy this pair, if no such printer exists then the pair is a conflict, we add it to conflict_pairs array */ no_of_printers = cupsArrayCount(remote_printers); for (j = 0; j < no_of_printers; j ++) { p = (remote_printer_t *)cupsArrayIndex(remote_printers, j); if (strcmp(cluster_name, p->queue_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; for (i = 0; i < no_of_ppd_keywords; i ++) { printer_first_options = get_supported_options(p->prattrs, ppd_keywords[i]); if (i == 0) for (opt1 = cupsArrayFirst(cluster_options[i]); opt1; opt1 = cupsArrayNext(cluster_options[i])) { if (cupsArrayFind(printer_first_options, opt1)) continue; for (k = i + 1; k < no_of_ppd_keywords; k++) { if (!strcmp(ppd_keywords[i], "PageSize") && !strcmp(ppd_keywords[k], "PageRegion")) continue; printer_second_options = get_supported_options(p->prattrs, ppd_keywords[k]); for(opt2 = cupsArrayFirst(printer_second_options); opt2; opt2 = cupsArrayNext(printer_second_options)) { if (check_printer_with_options(cluster_name, i, opt1, k, opt2)) continue; if (!strcasecmp(opt1, AUTO_OPTION) || !strcasecmp(opt2, AUTO_OPTION)) continue; if (!strcmp(opt1, "Gray") || !strcmp(opt2, "Gray")) continue; sprintf(constraint, "*UIConstraints: *%s %s *%s %s\n", ppd_keywords[i], opt1,ppd_keywords[k], opt2); if (!cupsArrayFind(conflict_pairs, constraint)) { cupsArrayAdd(conflict_pairs, constraint); } sprintf(constraint, "*UIConstraints: *%s %s *%s %s\n", ppd_keywords[k], opt2, ppd_keywords[i], opt1); if (!cupsArrayFind(conflict_pairs, constraint)) { cupsArrayAdd(conflict_pairs, constraint); } } cupsArrayDelete(printer_second_options); printer_second_options = NULL; } } cupsArrayDelete(printer_first_options); printer_first_options = NULL; } } for(i = 0; i < no_of_ppd_keywords; i ++) { cupsArrayDelete(cluster_options[i]); } free(ppdsizename); return conflict_pairs; } /*get_cluster_attributes - Returns ipp_t* containing the options supplied by all the printers in the cluster, which can be sent to ppdCreateFromIPP2() to generate the ppd file */ ipp_t* get_cluster_attributes(char* cluster_name) { remote_printer_t *p; ipp_t *merged_attributes = NULL; char printer_make_and_model[256]; ipp_attribute_t *attr; int color_supported = 0, make_model_done = 0, i; char valuebuffer[65536]; merged_attributes = ippNew(); for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(cluster_name, p->queue_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; if (!make_model_done) { strcpy(printer_make_and_model, "Cluster "); strcat(printer_make_and_model, cluster_name); make_model_done = 1; } if (((attr = ippFindAttribute(p->prattrs, "color-supported", IPP_TAG_BOOLEAN)) != NULL && ippGetBoolean(attr, 0))) color_supported = 1; } ippAddString(merged_attributes, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-make-and-model", NULL, printer_make_and_model); ippAddBoolean(merged_attributes, IPP_TAG_PRINTER, "color-supported", color_supported); add_keyword_attributes(cluster_name, &merged_attributes); add_mimetype_attributes(cluster_name, &merged_attributes); add_tagzero_attributes(cluster_name, &merged_attributes); add_enum_attributes(cluster_name, &merged_attributes); add_resolution_attributes(cluster_name, &merged_attributes); add_margin_attributes(cluster_name, &merged_attributes); add_mediasize_attributes(cluster_name, &merged_attributes); add_mediadatabase_attributes(cluster_name, &merged_attributes); add_jobpresets_attribute(cluster_name, &merged_attributes); attr = ippFirstAttribute(merged_attributes); /* Printing merged attributes*/ debug_printf("Merged attributes for the cluster %s : \n", cluster_name); while (attr) { debug_printf(" Attr: %s\n", ippGetName(attr)); ippAttributeString(attr, valuebuffer, sizeof(valuebuffer)); debug_printf(" Value: %s\n", valuebuffer); const char *kw; for (i = 0; i < ippGetCount(attr); i ++) if ((kw = ippGetString(attr, i, NULL)) != NULL) debug_printf(" Keyword: %s\n", kw); attr = ippNextAttribute(merged_attributes); } return merged_attributes; } int cluster_supports_given_attribute(char* cluster_name, ipp_tag_t tag, const char* attribute) { remote_printer_t *p; ipp_attribute_t *attr; int count; for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(cluster_name, p->queue_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; if ((attr = ippFindAttribute(p->prattrs, attribute, tag)) != NULL && (count = ippGetCount(attr)) > 1) return 1; } return 0; } /* Generating the default values for the cluster*/ void get_cluster_default_attributes(ipp_t** merged_attributes, char* cluster_name, char* default_pagesize, const char **default_color) { int max_pages_per_min = 0, pages_per_min; remote_printer_t *p, *def_printer = NULL; int i, count; ipp_attribute_t *attr, *media_attr, *media_col_default, *defattr; ipp_t *media_col, *media_size, *current_media=NULL; char media_source[32], media_type[32]; const char *str; media_col_t *temp; const char *keyword; res_t *res; int xres, yres; int min_length = INT_MAX, min_width = INT_MAX, max_length = 0, max_width = 0, bottom, left, right, top; char ppdname[41]; cups_array_t *sizes; /*The printer with the maximum Throughtput(pages_per_min) is selected as the default printer*/ for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(p->queue_name, cluster_name)) continue; if(p->status == STATUS_DISAPPEARED || p->status == STATUS_UNCONFIRMED || p->status == STATUS_TO_BE_RELEASED ) continue; if ((attr = ippFindAttribute (p->prattrs, "pages-per-minute", IPP_TAG_INTEGER)) != NULL) { pages_per_min = ippGetInteger (attr, 0); if (pages_per_min > max_pages_per_min) { max_pages_per_min = pages_per_min; def_printer = p; } } } /* If none of the printer in the cluster has "pages-per-minute" in the ipp response message, then select the first printer in the cluster */ if (!def_printer) { for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (strcmp(p->queue_name, cluster_name)) continue; else { def_printer = p; break; } } } debug_printf("Selecting printer (%s) as the default for the cluster %s\n", def_printer->uri, cluster_name); debug_printf("Default Attributes of the cluster %s are : \n", cluster_name); /* Generating the default pagesize for the cluster*/ sizes = generate_sizes(def_printer->prattrs, &defattr, &min_length, &min_width, &max_length, &max_width, &bottom, &left, &right, &top, ppdname); strcpy(default_pagesize, ppdname); debug_printf("Default PageSize : %s\n", default_pagesize); /* Generating the default media-col for the cluster*/ if ((attr = ippFindAttribute(def_printer->prattrs, "media-col-default", IPP_TAG_BEGIN_COLLECTION)) != NULL) { media_col = ippGetCollection(attr, 0); media_size = ippGetCollection(ippFindAttribute(media_col, "media-size", IPP_TAG_BEGIN_COLLECTION), 0); temp = (media_col_t *)malloc(sizeof(media_col_t)); temp->x = ippGetInteger(ippFindAttribute(media_size, "x-dimension", IPP_TAG_ZERO), 0); temp->y = ippGetInteger(ippFindAttribute(media_size, "y-dimension", IPP_TAG_ZERO), 0); temp->top_margin = ippGetInteger(ippFindAttribute(media_col, "media-top-margin", IPP_TAG_INTEGER), 0); temp->bottom_margin = ippGetInteger(ippFindAttribute(media_col, "media-bottom-margin", IPP_TAG_INTEGER), 0); temp->left_margin = ippGetInteger(ippFindAttribute(media_col, "media-left-margin", IPP_TAG_INTEGER), 0); temp->right_margin = ippGetInteger(ippFindAttribute(media_col, "media-right-margin", IPP_TAG_INTEGER), 0); media_type[0] = '\0'; media_source[0] = '\0'; temp->media_source = NULL; temp->media_type = NULL; if ((media_attr = ippFindAttribute(media_col, "media-type", IPP_TAG_KEYWORD)) != NULL) pwg_ppdize_name(ippGetString(media_attr, 0, NULL), media_type, sizeof(media_type)); if (strlen(media_type) > 1) { temp->media_type = (char*)malloc(sizeof(char)*32); strcpy(temp->media_type, media_type); debug_printf("Default MediaType: %s\n", media_type); } if (temp->media_type == NULL) { if (cluster_supports_given_attribute(cluster_name, IPP_TAG_KEYWORD, "media-type-supported")) { temp->media_type = (char*)malloc(sizeof(char)*32); strcpy(temp->media_type, AUTO_OPTION); debug_printf("Default MediaType: " AUTO_OPTION "\n"); } } if ((media_attr = ippFindAttribute(media_col, "media-source", IPP_TAG_KEYWORD)) != NULL) { pwg_ppdize_name(ippGetString(media_attr, 0, NULL), media_source, sizeof(media_source)); } if (strlen(media_source) > 1) { temp->media_source = (char*)malloc(sizeof(char)*32); strcpy(temp->media_source, media_source); debug_printf("Default MediaSource: %s\n", media_source); } if (temp->media_source == NULL) { if (cluster_supports_given_attribute(cluster_name, IPP_TAG_KEYWORD, "media-source-supported")) { temp->media_source = (char*)malloc(sizeof(char) * 32); strcpy(temp->media_source, AUTO_OPTION); debug_printf("Default MediaSource: " AUTO_OPTION "\n"); } } media_col_default = ippAddCollection(*merged_attributes, IPP_TAG_PRINTER, "media-col-default", NULL); current_media = create_media_col(temp->x, temp->y, temp->left_margin, temp->right_margin, temp->top_margin, temp->bottom_margin, temp->media_source, temp->media_type); ippSetCollection(*merged_attributes, &media_col_default, 0, current_media); free(temp->media_source); free(temp->media_type); free(temp); ippDelete(current_media); } /*Finding the default colormodel for the cluster*/ if ((attr = ippFindAttribute(def_printer->prattrs, "urf-supported", IPP_TAG_KEYWORD)) == NULL) if ((attr = ippFindAttribute(def_printer->prattrs, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD)) == NULL) if ((attr = ippFindAttribute(def_printer->prattrs, "print-color-mode-supported", IPP_TAG_KEYWORD)) == NULL) attr = ippFindAttribute(def_printer->prattrs, "output-mode-supported", IPP_TAG_KEYWORD); if (attr && ippGetCount(attr) > 0) { *default_color = NULL; for (i = 0, count = ippGetCount(attr); i < count; i ++) { keyword = ippGetString(attr, i, NULL); if ((!strcasecmp(keyword, "black_1") || !strcmp(keyword, "bi-level") || !strcmp(keyword, "process-bi-level"))) { if (!*default_color) *default_color = "FastGray"; } else if ((!strcasecmp(keyword, "sgray_8") || !strncmp(keyword, "W8", 2) || !strcmp(keyword, "monochrome") || !strcmp(keyword, "process-monochrome"))) { if (!*default_color || !strcmp(*default_color, "FastGray")) *default_color = "Gray"; } else if (!strcasecmp(keyword, "sgray_16") || !strncmp(keyword, "W8-16", 5) || !strncmp(keyword, "W16", 3)) { if (!*default_color || !strcmp(*default_color, "FastGray")) *default_color = "Gray16"; } else if (!strcasecmp(keyword, "srgb_8") || !strncmp(keyword, "SRGB24", 6) || !strcmp(keyword, "color")) { *default_color = "RGB"; } else if ((!strcasecmp(keyword, "srgb_16") || !strncmp(keyword, "SRGB48", 6)) && !ippContainsString(attr, "srgb_8")) { *default_color = "RGB"; } else if (!strcasecmp(keyword, "adobe-rgb_16") || !strncmp(keyword, "ADOBERGB48", 10) || !strncmp(keyword, "ADOBERGB24-48", 13)) { if (!*default_color) *default_color = "AdobeRGB"; } else if ((!strcasecmp(keyword, "adobe-rgb_8") || !strcmp(keyword, "ADOBERGB24")) && !ippContainsString(attr, "adobe-rgb_16")) { if (!*default_color) *default_color = "AdobeRGB"; } } if (*default_color) debug_printf("Default ColorModel : %s\n", *default_color); } if ((attr = ippFindAttribute(def_printer->prattrs, "output-bin-default", IPP_TAG_ZERO)) != NULL) { str = ippGetString(attr, 0, NULL); ippAddString(*merged_attributes, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-default", NULL, str); debug_printf("Default OutputBin: %s\n", str); } else { if (cluster_supports_given_attribute(cluster_name,IPP_TAG_ZERO, "output-bin-supported")) { ippAddString(*merged_attributes, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-default", NULL, AUTO_OPTION); debug_printf("Default OutputBin: %s\n", AUTO_OPTION); } } if ((attr = ippFindAttribute(def_printer->prattrs, "print-content-optimize-default", IPP_TAG_ZERO)) != NULL) { str = ippGetString(attr, 0, NULL); ippAddString(*merged_attributes, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "print-content-optimize-default", NULL, str); debug_printf("Default print-content-optimize: %s\n", str); } else { if (cluster_supports_given_attribute(cluster_name, IPP_TAG_ZERO, "print-content-optimize-default")) { ippAddString(*merged_attributes, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "print-content-optimize-default", NULL, AUTO_OPTION); debug_printf("Default print-content-optimize: %s\n", AUTO_OPTION); } } if ((attr = ippFindAttribute(def_printer->prattrs, "print-rendering-intent-default", IPP_TAG_ZERO)) != NULL) { str = ippGetString(attr, 0, NULL); ippAddString(*merged_attributes, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "print-rendering-intent-default", NULL, str); debug_printf("Default print-rendering-intent: %s\n", str); } else { if (cluster_supports_given_attribute(cluster_name, IPP_TAG_ZERO, "print-rendering-intent-default")) { ippAddString(*merged_attributes, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "print-rendering-intent-default", NULL, AUTO_OPTION); debug_printf("Default print-rendering-intent: %s\n", AUTO_OPTION); } } if ((attr = ippFindAttribute(def_printer->prattrs, "print-scaling-default", IPP_TAG_ZERO)) != NULL) { str = ippGetString(attr, 0, NULL); ippAddString(*merged_attributes, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "print-scaling-default", NULL, str); debug_printf("Default print-scaling: %s\n",str); } else { if (cluster_supports_given_attribute(cluster_name, IPP_TAG_ZERO, "print-scaling-default")) { ippAddString(*merged_attributes, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "print-scaling-default", NULL, AUTO_OPTION); debug_printf("Default print-scaling: %s\n", AUTO_OPTION); } } if ((attr = ippFindAttribute(def_printer->prattrs, "printer-resolution-default", IPP_TAG_ZERO)) != NULL) { if ((res = ippResolutionToRes(attr, 0)) != NULL) { xres = res->x; yres = res->y; ippAddResolution(*merged_attributes, IPP_TAG_PRINTER, "printer-resolution-default", IPP_RES_PER_INCH, xres, yres); debug_printf("Default Resolution : %dx%d\n", xres, yres); free_resolution(res, NULL); } } cupsArrayDelete(sizes); } /* Function to see which printer in the cluster supports the requested job attributes*/ int supports_job_attributes_requested(const gchar* printer, int printer_index, int job_id, int *print_quality) { char uri[1024]; http_t *http = NULL; ipp_attribute_t *attr, *attr1; ipp_t *request, *response = NULL; const char *str, *side, *resource; cups_array_t *job_sheet_supported = NULL, *multiple_doc_supported = NULL, *print_qualities = NULL, *media_type_supported = NULL, *staplelocation_supported = NULL, *foldtype_supported = NULL, *punchmedia_supported = NULL, *color_supported = NULL; remote_printer_t *p; int i, count, side_found, orien_req, orien, orien_found; cups_array_t *sizes = NULL; int ret = 1; p = (remote_printer_t *)cupsArrayIndex(remote_printers, printer_index); static const char * const jattrs[] = /* Job attributes we want */ { "all" }; httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", printer); /* Getting the resource */ resource = uri + (strlen(uri) - strlen(printer) - 10); http = http_connect_local(); request = ippNewRequest(IPP_OP_GET_JOB_ATTRIBUTES); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", job_id); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(jattrs) / sizeof(jattrs[0])), NULL, jattrs); response = cupsDoRequest(http, request,resource); attr = ippFirstAttribute(response); /* Document Format */ /* if ((attr = ippFindAttribute(response, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL && ippGetCount(attr) > 0) { str = ippGetString(attr, 0, NULL); debug_printf("The job-document is of the format %s\n.",str); formats_supported = get_mimetype_attributes(p->prattrs); if(!cupsArrayFind(formats_supported, (void *)str)){ debug_printf("Printer %s doesn't support the document format %s\n", printer, str); return 0; } } */ /* Job Sheets */ if ((attr = ippFindAttribute(response, "job-sheets", IPP_TAG_ZERO)) != NULL && ippGetCount(attr) > 0) { str = ippGetString(attr, 0, NULL); debug_printf("The job-sheets %s is requested for the job\n", str); job_sheet_supported = get_supported_options(p->prattrs, "job-sheets-supported"); if (str) { if (!cupsArrayFind(job_sheet_supported, (void *)str) && strcasecmp(str,"none")) { debug_printf("Printer %s doesn't support the job-sheet %s\n", printer, str); ret = 0; goto cleanup; } } } /* Multiple document handling */ /* Can't get multiple-document-handling data from job templates */ if ((attr = ippFindAttribute(response, "multiple-document-handling", IPP_TAG_ZERO)) != NULL && ippGetCount(attr)>0) { str = ippGetString(attr, 0, NULL); debug_printf("The multiple-document-handling type %s is requested\n", str); if (str) { multiple_doc_supported = get_supported_options(p->prattrs, "multiple-document-handling-supported"); if (!cupsArrayFind(multiple_doc_supported, (void *)str)) { debug_printf("Printer %s doesn't support the multiple document handling option %s\n", printer, str); ret = 0; goto cleanup; } } } /* Media Type */ if ((attr = ippFindAttribute(response, "MediaType", IPP_TAG_ZERO)) != NULL && ippGetCount(attr) > 0) { str = ippGetString(attr, 0, NULL); debug_printf("The mediatype %s is requested for the job\n", str); if (str != NULL) { media_type_supported = get_supported_options(p->prattrs, "media-type-supported"); if (!cupsArrayFind(media_type_supported, (void *)str) && strcasecmp(str, AUTO_OPTION)) { debug_printf("Printer %s doesn't support the media-type %s\n", printer, str); ret = 0; goto cleanup; } } } /* Staple Location*/ if ((attr = ippFindAttribute(response, "StapleLocation", IPP_TAG_ZERO)) != NULL && ippGetCount(attr) > 0) { str = ippGetString(attr, 0, NULL); debug_printf("The staple location %s is requested for the job\n", str); if (str != NULL) { staplelocation_supported = get_supported_options(p->prattrs, "StapleLocation"); if (!cupsArrayFind(staplelocation_supported, (void *)str) && strcasecmp(str, "None")) { debug_printf("Printer %s doesn't support the staple location %s\n", printer, str); ret = 0; goto cleanup; } } } /* FoldType */ if ((attr = ippFindAttribute(response, "FoldType", IPP_TAG_ZERO)) != NULL && ippGetCount(attr) > 0) { str = ippGetString(attr, 0, NULL); debug_printf("The FoldType %s is requested for the job\n", str); if (str != NULL) { foldtype_supported = get_supported_options(p->prattrs, "FoldType"); if (!cupsArrayFind(foldtype_supported, (void *)str) && strcasecmp(str, "None")) { debug_printf("Printer %s doesn't support the FoldType %s\n", printer, str); ret = 0; goto cleanup; } } } /* PunchMedia */ if ((attr = ippFindAttribute(response, "PunchMedia", IPP_TAG_ZERO)) != NULL && ippGetCount(attr) > 0) { str = ippGetString(attr, 0, NULL); debug_printf("The PunchMedia %s is requested for the job\n", str); if (str != NULL) { punchmedia_supported = get_supported_options(p->prattrs, "PunchMedia"); if (!cupsArrayFind(punchmedia_supported, (void *)str) && strcasecmp(str, "none")) { debug_printf("Printer %s doesn't support the PunchMedia %s\n", printer, str); ret = 0; goto cleanup; } } } /* ColorModel */ if ((attr = ippFindAttribute(response, "ColorModel", IPP_TAG_ZERO)) != NULL && ippGetCount(attr) > 0) { str = ippGetString(attr, 0, NULL); debug_printf("The ColorModel %s is requested for the job\n", str); if (str != NULL) { color_supported = get_supported_options(p->prattrs, "ColorModel"); if (!cupsArrayFind(color_supported, (void *)str) && strcasecmp(str,"Gray")) { debug_printf("Printer %s doesn't support the ColorModel %s\n", printer, str); ret = 0; goto cleanup; } } } /* Sides supported */ if ((attr = ippFindAttribute(response, "Duplex", IPP_TAG_ZERO)) != NULL) { side_found = 0; str = ippGetString(attr, 0, NULL); if (str) { if ((attr1 = ippFindAttribute(p->prattrs, "sides-supported", IPP_TAG_KEYWORD)) != NULL) { for (i = 0, count = ippGetCount(attr1); i < count; i++) { side = ippGetString(attr1, i, NULL); debug_printf("The duplex option %s is requested\n", side); if (!strcasecmp(str, "None") && !strcmp(side, "one-sided")) { side_found = 1; break; } else if (!strcmp(str, "DuplexNoTumble") && !strcmp(side, "two-sided-long-edge")) { side_found = 1; break; } else if (!strcmp(str, "DuplexTumble") && !strcmp(side, "two-sided-short-edge")) { side_found = 1; break; } } if (!side_found) { debug_printf("Printer %s doesn't support the required duplex options\n", printer); ret = 0; goto cleanup; } } } } /* Orientation Requested */ if ((attr = ippFindAttribute(response, "orientation-requested", IPP_TAG_ENUM)) != NULL) { orien_found = 0; orien_req = ippGetInteger(attr, 0); if ((attr1 = ippFindAttribute(p->prattrs, "orientation-requested-supported", IPP_TAG_ENUM)) != NULL) { for (i = 0, count = ippGetCount(attr1); i < count; i ++) { orien = ippGetInteger(attr1, i); if (orien == orien_req) { orien_found = 1; break; } } if (!orien_found) { debug_printf("Printer %s doesn't support the requested orientation\n", printer); ret = 0; goto cleanup; } } } /* Page Size */ if ((attr = ippFindAttribute(response, "PageSize", IPP_TAG_ZERO)) != NULL && ippGetCount(attr) > 0) { str = ippGetString(attr, 0, NULL); if (str) { sizes = get_pagesize(p->prattrs); if (!cupsArrayFind(sizes, (void*)str)) { debug_printf("Printer %s doesn't support %s PageSize\n", p->uri, str); ret = 0; goto cleanup; } } } /* Print Quality */ *print_quality = 4; if ((attr = ippFindAttribute(response, "cupsPrintQuality", IPP_TAG_ZERO)) != NULL && ippGetCount(attr) > 0) { print_qualities = get_supported_options(p->prattrs, "cupsPrintQuality"); str = ippGetString(attr, 0, NULL); debug_printf("%s\n", str); if (str && !cupsArrayFind(print_qualities, (void*)str)) { debug_printf("In\n"); if(!strcmp(str, "5")) *print_quality = 5; else if (!strcmp(str, "3")) *print_quality = 3; debug_printf("Printer doesn't support %s print quality\n", !strcmp(str, "5") ? "HIGH": "DRAFT"); ret = 0; goto cleanup; } } cleanup: if (response != NULL) ippDelete(response); if (job_sheet_supported != NULL) cupsArrayDelete(job_sheet_supported); if (multiple_doc_supported) cupsArrayDelete(multiple_doc_supported); if (media_type_supported != NULL) cupsArrayDelete(media_type_supported); if (staplelocation_supported != NULL) cupsArrayDelete(staplelocation_supported); if (foldtype_supported != NULL) cupsArrayDelete(foldtype_supported); if (punchmedia_supported != NULL) cupsArrayDelete(punchmedia_supported); if (color_supported != NULL) cupsArrayDelete(color_supported); if (print_qualities != NULL) cupsArrayDelete(print_qualities); if (sizes != NULL) cupsArrayDelete(sizes); return ret; } /* * Remove all illegal characters and replace each group of such characters * by a single separator character (dash or underscore), return a free()-able * string. * * mode = 0: Only allow letters, numbers, dashes, and underscores for * turning make/model info into a valid print queue name or * into a string which can be supplied as option value in a * filter command line without need of quoting. Replace all * groups of illegal characters by single dashes and remove * leading and trailing dashes. * mode = 1: Allow also '/', '.', ',' for cleaning up MIME type * strings (here available Page Description Languages, PDLs) to * supply them on a filter command line without quoting. * Replace all groups of illegal characters by single dashes * and remove leading and trailing dashes. * mode = 2: Keep all locale-free alphanumeric characters (a-z, A-Z, 0-9) * and replace everything else by underscores. Replace all * groups of illegal characters by single underscores. This is * for generating print queue names from DNS-SD service names * to do it exactly as CUPS 2.2.x (or newer) does, so that CUPS * does not create its own temporary queues in addition. * * Especially this prevents from arbitrary code execution by interface scripts * generated for print queues to native IPP printers when a malicious IPP * print service with forged PDL and/or make/model info gets broadcasted into * the local network. */ char * /* O - Cleaned string */ remove_bad_chars(const char *str_orig, /* I - Original string */ int mode) /* I - 0: Make/Model, queue name */ /* 1: MIME types/PDLs */ /* 2: Queue name from DNS-SD */ /* service name */ { int i, j; int havesep = 0; char sep, *str; if (str_orig == NULL) return NULL; str = strdup(str_orig); /* for later str[strlen(str)-1] access */ if (strlen(str) < 1) return str; /* Select separator character */ if (mode == 2) sep = '_'; else sep = '-'; for (i = 0, j = 0; i < strlen(str); i++, j++) { if (((str[i] >= 'A') && (str[i] <= 'Z')) || ((str[i] >= 'a') && (str[i] <= 'z')) || ((str[i] >= '0') && (str[i] <= '9')) || (mode != 2 && (str[i] == '_' || str[i] == '.')) || (mode == 1 && (str[i] == '/' || str[i] == ','))) { /* Allowed character, keep it */ havesep = 0; str[j] = str[i]; } else { /* Replace all other characters by a single separator */ if (havesep == 1) j --; else { havesep = 1; str[j] = sep; } } } /* Add terminating zero */ str[j] = '\0'; /* Cut off trailing separators */ while (strlen(str) > 0 && str[strlen(str) - 1] == sep) str[strlen(str) - 1] = '\0'; /* Cut off leading separators */ i = 0; while (str[i] == sep) i ++; /* Keep a free()-able string. +1 for trailing \0 */ return memmove(str, str + i, strlen(str) - i + 1); } static local_printer_t * new_local_printer (const char *device_uri, const char *uuid, gboolean cups_browsed_controlled) { local_printer_t *printer = g_malloc (sizeof (local_printer_t)); printer->device_uri = strdup (device_uri); printer->uuid = (char*)uuid; printer->cups_browsed_controlled = cups_browsed_controlled; return printer; } static void free_local_printer (gpointer data) { local_printer_t *printer = data; debug_printf("free_local_printer() in THREAD %ld\n", pthread_self()); free (printer->device_uri); if (printer->uuid) free (printer->uuid); free (printer); } static gboolean local_printer_is_same_device (gpointer key, gpointer value, gpointer user_data) { local_printer_t *lprinter = value; remote_printer_t *p = user_data; char lhost[HTTP_MAX_URI], /* Local printer: Hostname */ lresource[HTTP_MAX_URI], /* Local printer: Resource path */ lscheme[32], /* Local printer: URI's scheme */ lusername[64], /* Local printer: URI's username */ *ltype = NULL, /* Local printer: If URI DNS-SD-based */ *ldomain = NULL; /* pointers into lhost for components*/ int lport = 0; /* Local printer: URI's port number */ debug_printf("local_printer_is_same_device() in THREAD %ld\n", pthread_self()); if (!lprinter || !lprinter->device_uri || !p) return (0); /* Separate the local printer's URI into their components */ memset(lscheme, 0, sizeof(lscheme)); memset(lusername, 0, sizeof(lusername)); memset(lhost, 0, sizeof(lhost)); memset(lresource, 0, sizeof(lresource)); httpSeparateURI(HTTP_URI_CODING_ALL, lprinter->device_uri, lscheme, sizeof(lscheme) - 1, lusername, sizeof(lusername) - 1, lhost, sizeof(lhost) - 1, &lport, lresource, sizeof(lresource) - 1); if ((ltype = strstr(lhost, "._ipp._tcp.")) != NULL || (ltype = strstr(lhost, "._ipps._tcp.")) != NULL) { *ltype = '\0'; ltype ++; ldomain = strchr(ltype + 9, '.'); *ldomain = '\0'; ldomain ++; if (*ldomain && ldomain[strlen(ldomain) - 1] == '.') ldomain[strlen(ldomain) - 1] = '\0'; } /* Consider not only absolutely equal URIs as equal but alo URIs which differ only by use of IPP or IPPS and/or have the IPP standard port 631 replaced by the HTTPS standard port 443, as this is common on network printers */ return ((ltype && p->service_name && p->domain && g_str_equal(lhost, p->service_name) && !strncmp(ldomain, p->domain, strlen(ldomain))) || (!ltype && p->host && p->resource && (g_str_equal(lscheme, "ipp") || g_str_equal(lscheme, "ipps")) && !lusername[0] && g_str_equal(lhost, p->host) && ((!p->port && (lport == 631 || lport == 443)) || lport == p->port || (lport == 631 && p->port == 443) || (lport == 443 && p->port == 631)) && g_str_equal(lresource, p->resource))); } static gboolean local_printer_has_uuid (gpointer key, gpointer value, gpointer user_data) { local_printer_t *printer = value; char *uuid = user_data; debug_printf("local_printer_has_uuid() in THREAD %ld\n", pthread_self()); return (printer != NULL && printer->uuid != NULL && uuid != NULL && g_str_equal(printer->uuid, uuid)); } static gboolean local_printer_service_name_matches (gpointer key, gpointer value, gpointer user_data) { char *queue_name = key; char *service_name = user_data; char *p; debug_printf("local_printer_service_name_matches() in THREAD %ld\n", pthread_self()); p = remove_bad_chars(service_name, 2); if (p && strncasecmp(p, queue_name, 63) == 0) { free(p); return TRUE; } if (p) free(p); return FALSE; } static void local_printers_create_subscription (http_t *conn) { char temp[1024]; if (!local_printers_context) { local_printers_context = g_malloc0 (sizeof (browsepoll_t)); /* The httpGetAddr() function was introduced in CUPS 2.0.0 */ #ifdef HAVE_CUPS_2_0 local_printers_context->server = strdup(httpAddrString(httpGetAddress(conn), temp, sizeof(temp))); local_printers_context->port = httpAddrPort(httpGetAddress(conn)); #else local_printers_context->server = cupsServer(); local_printers_context->port = ippPort(); #endif local_printers_context->can_subscribe = TRUE; } browse_poll_create_subscription (local_printers_context, conn); } int add_dest_cb(dest_list_t *user_data, unsigned flags, cups_dest_t *dest) { if (flags & CUPS_DEST_FLAGS_REMOVED) /* Remove destination from array */ user_data->num_dests = cupsRemoveDest(dest->name, dest->instance, user_data->num_dests, &(user_data->dests)); else /* Add destination to array... */ user_data->num_dests = cupsCopyDest(dest, user_data->num_dests, &(user_data->dests)); return (1); } const char * get_printer_uuid(http_t *http_printer, const char* raw_uri) { ipp_t *response = NULL; ipp_attribute_t *attr = NULL; const char * uuid = NULL; const char * const pattrs[] = { "printer-uuid", }; const char * const req_attrs[] = { "printer-uuid", }; if (http_printer == NULL) debug_printf ("HTTP connection for printer with URI %s not set!\n", raw_uri); if ((response = get_printer_attributes2(http_printer, raw_uri, pattrs, 1, req_attrs, 1, 0)) == NULL) { debug_printf ("Printer with URI %s has no \"printer-uuid\" IPP attribute!\n", raw_uri); return NULL; } attr = ippFindAttribute(response, "printer-uuid", IPP_TAG_URI); if (attr) uuid = strdup(ippGetString(attr, 0, NULL) + 9); else { debug_printf ("Printer with URI %s: Cannot read \"printer-uuid\" IPP attribute!\n", raw_uri); } ippDelete(response); return uuid; } static void get_local_printers (void) { dest_list_t dest_list = {0, NULL}; http_t *conn = NULL; conn = http_connect_local (); /* We only want to have a list of actually existing CUPS queues, not of DNS-SD-discovered printers for which CUPS can auto-setup a driverless print queue */ if (OnlyUnsupportedByCUPS) cupsEnumDests(CUPS_DEST_FLAGS_NONE, 1000, NULL, 0, 0, (cups_dest_cb_t)add_dest_cb, &dest_list); else cupsEnumDests(CUPS_DEST_FLAGS_NONE, 1000, NULL, CUPS_PRINTER_LOCAL, CUPS_PRINTER_DISCOVERED, (cups_dest_cb_t)add_dest_cb, &dest_list); debug_printf ("cups-browsed (%s): cupsEnumDests\n", local_server_str); g_hash_table_remove_all (local_printers); if (OnlyUnsupportedByCUPS) g_hash_table_remove_all (cups_supported_remote_printers); int num_dests = dest_list.num_dests; cups_dest_t *dests = dest_list.dests; for (int i = 0; i < num_dests; i++) { const char *val; cups_dest_t *dest = &dests[i]; local_printer_t *printer; gboolean cups_browsed_controlled; gboolean is_temporary; gboolean is_cups_supported_remote; char uri[HTTP_MAX_URI]; const char *device_uri = cupsGetOption ("device-uri", dest->num_options, dest->options); if (device_uri == NULL) device_uri = ""; /* Temporary CUPS queue? */ val = cupsGetOption ("printer-is-temporary", dest->num_options, dest->options); is_temporary = (val && (!strcasecmp (val, "yes") || !strcasecmp (val, "on") || !strcasecmp (val, "true"))); if (OnlyUnsupportedByCUPS) { /* Printer discovered by DNS-SD and supported by CUPS' temporary queues? */ val = cupsGetOption ("printer-uri-supported", dest->num_options, dest->options); /* Printer has no local CUPS queue but CUPS would create a temporary queue on-demand */ is_cups_supported_remote = (val == NULL || is_temporary); } else { is_cups_supported_remote = 0; if (is_temporary) continue; } val = cupsGetOption (CUPS_BROWSED_MARK, dest->num_options, dest->options); cups_browsed_controlled = val && (!strcasecmp (val, "yes") || !strcasecmp (val, "on") || !strcasecmp (val, "true")); httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", dest->name); printer = new_local_printer (device_uri, get_printer_uuid(conn, uri), cups_browsed_controlled); debug_printf ("Printer %s: %s, %s%s%s\n", dest->name, device_uri, printer->uuid, cups_browsed_controlled ? ", cups_browsed" : "", is_cups_supported_remote ? ", temporary" : ""); if (is_cups_supported_remote) g_hash_table_insert (cups_supported_remote_printers, g_ascii_strdown (dest->name, -1), printer); else g_hash_table_insert (local_printers, g_ascii_strdown (dest->name, -1), printer); } cupsFreeDests (num_dests, dests); } static browse_data_t * new_browse_data (int type, int state, const gchar *uri, const gchar *location, const gchar *info, const gchar *make_model, const gchar *browse_options) { browse_data_t *data = g_malloc (sizeof (browse_data_t)); data->type = type; data->state = state; data->uri = g_strdup (uri); data->location = g_strdup (location); data->info = g_strdup (info); data->make_model = g_strdup (make_model); data->browse_options = g_strdup (browse_options); return data; } static void browse_data_free (gpointer data) { browse_data_t *bdata = data; debug_printf("browse_data_free() in THREAD %ld\n", pthread_self()); g_free (bdata->uri); g_free (bdata->location); g_free (bdata->info); g_free (bdata->make_model); g_free (bdata->browse_options); g_free (bdata); } static void prepare_browse_data (void) { static const char * const rattrs[] = { "printer-type", "printer-state", "printer-uri-supported", "printer-info", "printer-location", "printer-make-and-model", "auth-info-required", "printer-uuid", "job-template" }; ipp_t *request, *response = NULL; ipp_attribute_t *attr; http_t *conn = NULL; conn = http_connect_local (); if (conn == NULL) { debug_printf("Browse send failed to connect to localhost\n"); goto fail; } request = ippNewRequest(CUPS_GET_PRINTERS); ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", sizeof (rattrs) / sizeof (rattrs[0]), NULL, rattrs); ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser ()); debug_printf("preparing browse data\n"); response = cupsDoRequest (conn, request, "/"); if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) { debug_printf("browse send failed for localhost: %s\n", cupsLastErrorString ()); goto fail; } g_list_free_full (browse_data, browse_data_free); browse_data = NULL; for (attr = ippFirstAttribute(response); attr; attr = ippNextAttribute(response)) { int type = -1, state = -1; const char *uri = NULL; gchar *location = NULL; gchar *info = NULL; gchar *make_model = NULL; GString *browse_options = g_string_new (""); /* Skip any non-printer attributes */ while (attr && ippGetGroupTag(attr) != IPP_TAG_PRINTER) attr = ippNextAttribute(response); if (!attr) break; while (attr && ippGetGroupTag(attr) == IPP_TAG_PRINTER) { const char *attrname = ippGetName(attr); int value_tag = ippGetValueTag(attr); if (!strcasecmp(attrname, "printer-type") && value_tag == IPP_TAG_ENUM) { type = ippGetInteger(attr, 0); if (type & CUPS_PRINTER_NOT_SHARED) { /* Skip CUPS queues not marked as shared */ state = -1; type = -1; break; } } else if (!strcasecmp(attrname, "printer-state") && value_tag == IPP_TAG_ENUM) state = ippGetInteger(attr, 0); else if (!strcasecmp(attrname, "printer-uri-supported") && value_tag == IPP_TAG_URI) uri = ippGetString(attr, 0, NULL); else if (!strcasecmp(attrname, "printer-location") && value_tag == IPP_TAG_TEXT) { /* Remove quotes */ gchar **tokens = g_strsplit (ippGetString(attr, 0, NULL), "\"", -1); location = g_strjoinv ("", tokens); g_strfreev (tokens); } else if (!strcasecmp(attrname, "printer-info") && value_tag == IPP_TAG_TEXT) { /* Remove quotes */ gchar **tokens = g_strsplit (ippGetString(attr, 0, NULL), "\"", -1); info = g_strjoinv ("", tokens); g_strfreev (tokens); } else if (!strcasecmp(attrname, "printer-make-and-model") && value_tag == IPP_TAG_TEXT) { /* Remove quotes */ gchar **tokens = g_strsplit (ippGetString(attr, 0, NULL), "\"", -1); make_model = g_strjoinv ("", tokens); g_strfreev (tokens); } else if (!strcasecmp(attrname, "auth-info-required") && value_tag == IPP_TAG_KEYWORD) { if (strcasecmp (ippGetString(attr, 0, NULL), "none")) g_string_append_printf (browse_options, "auth-info-required=%s ", ippGetString(attr, 0, NULL)); } else if (!strcasecmp(attrname, "printer-uuid") && value_tag == IPP_TAG_URI) g_string_append_printf (browse_options, "uuid=%s ", ippGetString(attr, 0, NULL)); else if (!strcasecmp(attrname, "job-sheets-default") && value_tag == IPP_TAG_NAME && ippGetCount(attr) == 2) g_string_append_printf (browse_options, "job-sheets=%s,%s ", ippGetString(attr, 0, NULL), ippGetString(attr, 1, NULL)); else if (strstr(attrname, "-default")) { gchar *name = g_strdup (attrname); gchar *value = NULL; *strstr (name, "-default") = '\0'; switch (value_tag) { gchar **tokens; case IPP_TAG_KEYWORD: case IPP_TAG_STRING: case IPP_TAG_NAME: /* Escape value */ tokens = g_strsplit_set (ippGetString(attr, 0, NULL), " \"\'\\", -1); value = g_strjoinv ("\\", tokens); g_strfreev (tokens); break; default: /* other values aren't needed? */ debug_printf("skipping %s (%d)\n", name, value_tag); break; } if (value) { g_string_append_printf (browse_options, "%s=%s ", name, value); g_free (value); } g_free (name); } attr = ippNextAttribute(response); } if (type != -1 && state != -1 && uri && location && info && make_model) { gchar *browse_options_str = g_string_free (browse_options, FALSE); browse_data_t *data; browse_options = NULL; g_strchomp (browse_options_str); data = new_browse_data (type, state, uri, location, info, make_model, browse_options_str); browse_data = g_list_insert (browse_data, data, 0); g_free (browse_options_str); } if (make_model) g_free (make_model); if (info) g_free (info); if (location) g_free (location); if (browse_options) g_string_free (browse_options, TRUE); if (!attr) break; } fail: if (response) ippDelete(response); } static void update_local_printers (void) { gboolean get_printers = FALSE; http_t *conn; if (inhibit_local_printers_update) return; conn = http_connect_local (); if (conn && (!local_printers_context || local_printers_context->can_subscribe)) { if (!local_printers_context || local_printers_context->subscription_id == -1) { /* No subscription yet. First, create the subscription. */ local_printers_create_subscription (conn); get_printers = TRUE; } else /* We already have a subscription, so use it. */ /* Note: for the moment, browse_poll_get_notifications() just * tells us whether we should re-fetch the printer list, so it * is safe to use here. */ get_printers = browse_poll_get_notifications (local_printers_context, conn); } else get_printers = TRUE; if (get_printers) { get_local_printers (); if (BrowseLocalProtocols & BROWSE_CUPS) prepare_browse_data (); } } int check_jobs () { int num_jobs = 0; cups_job_t *jobs = NULL; remote_printer_t *p; http_t *conn = NULL; conn = http_connect_local (); if (conn == NULL) { debug_printf("Cannot connect to local CUPS to check whether there are still jobs.\n"); return 0; } if (cupsArrayCount(remote_printers) > 0) for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) if (!p->slave_of) { num_jobs = cupsGetJobs2(conn, &jobs, p->queue_name, 0, CUPS_WHICHJOBS_ACTIVE); if (num_jobs > 0) { debug_printf("Queue %s still has jobs!\n", p->queue_name); cupsFreeJobs(num_jobs, jobs); return 1; } } debug_printf("All our remote printers are without jobs.\n"); return 0; } gboolean autoshutdown_execute (gpointer data) { debug_printf("autoshutdown_execute() in THREAD %ld\n", pthread_self()); /* Are we still in auto shutdown mode and are we still without queues or jobs*/ if (autoshutdown && (cupsArrayCount(remote_printers) == 0 || (autoshutdown_on == NO_JOBS && check_jobs() == 0))) { debug_printf("Automatic shutdown as there are no print queues maintained by us or no jobs on them for %d sec.\n", autoshutdown_timeout); g_main_loop_quit(gmainloop); g_main_context_wakeup(NULL); } /* Stop this timeout handler, we needed it only once */ return FALSE; } int color_space_score(const char *color_space) { int score = 0; const char *p = color_space; if (!p) return -1; if (!strncasecmp(p, "s", 1)) { p += 1; score += 2; } else if (!strncasecmp(p, "adobe", 5)) { p += 5; score += 1; } else score += 3; if (!strncasecmp(p, "black", 5)) { p += 5; score += 1000; } else if (!strncasecmp(p, "gray", 4)) { p += 4; score += 2000; } else if (!strncasecmp(p, "cmyk", 4)) { p += 4; score += 4000; } else if (!strncasecmp(p, "cmy", 3)) { p += 3; score += 3000; } else if (!strncasecmp(p, "rgb", 3)) { p += 3; score += 5000; } if (!strncasecmp(p, "-", 1) || !strncasecmp(p, "_", 1)) { p += 1; } score += strtol(p, (char **)&p, 10) * 10; debug_printf("Score for color space %s: %d\n", color_space, score); return score; } #ifdef HAVE_LDAP_REBIND_PROC # if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000) /* * 'ldap_rebind_proc()' - Callback function for LDAP rebind */ static int /* O - Result code */ ldap_rebind_proc(LDAP *RebindLDAPHandle, /* I - LDAP handle */ LDAP_CONST char *refsp, /* I - ??? */ ber_tag_t request, /* I - ??? */ ber_int_t msgid, /* I - ??? */ void *params) /* I - ??? */ { int rc; /* Result code */ # if LDAP_API_VERSION > 3000 struct berval bval; /* Bind value */ # endif /* LDAP_API_VERSION > 3000 */ (void)request; (void)msgid; (void)params; /* * Bind to new LDAP server... */ debug_printf("ldap_rebind_proc: Rebind to %s\n", refsp); # if LDAP_API_VERSION > 3000 bval.bv_val = BrowseLDAPPassword; bval.bv_len = (BrowseLDAPPassword == NULL) ? 0 : strlen(BrowseLDAPPassword); rc = ldap_sasl_bind_s(RebindLDAPHandle, BrowseLDAPBindDN, LDAP_SASL_SIMPLE, &bval, NULL, NULL, NULL); # else rc = ldap_bind_s(RebindLDAPHandle, BrowseLDAPBindDN, BrowseLDAPPassword, LDAP_AUTH_SIMPLE); # endif /* LDAP_API_VERSION > 3000 */ return (rc); } # else /* defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000) */ /* * 'ldap_rebind_proc()' - Callback function for LDAP rebind */ static int /* O - Result code */ ldap_rebind_proc(LDAP *RebindLDAPHandle, /* I - LDAP handle */ char **dnp, /* I - ??? */ char **passwdp, /* I - ??? */ int *authmethodp, /* I - ??? */ int freeit, /* I - ??? */ void *arg) /* I - ??? */ { switch (freeit) { case 1: /* * Free current values... */ debug_printf("ldap_rebind_proc: Free values...\n"); if (dnp && *dnp) free(*dnp); if (passwdp && *passwdp) free(*passwdp); break; case 0: /* * Return credentials for LDAP referal... */ debug_printf("ldap_rebind_proc: Return necessary values...\n"); *dnp = strdup(BrowseLDAPBindDN); *passwdp = strdup(BrowseLDAPPassword); *authmethodp = LDAP_AUTH_SIMPLE; break; default: /* * Should never happen... */ debug_printf("LDAP rebind has been called with wrong freeit value!\n"); break; } return (LDAP_SUCCESS); } # endif /* defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000) */ #endif /* HAVE_LDAP_REBIND_PROC */ #ifdef HAVE_LDAP /* * 'ldap_new_connection()' - Start new LDAP connection */ static LDAP * /* O - LDAP handle */ ldap_new_connection(void) { int rc; /* LDAP API status */ int version = 3; /* LDAP version */ struct berval bv = {0, ""}; /* SASL bind value */ LDAP *TempBrowseLDAPHandle=NULL; /* Temporary LDAP Handle */ # if defined(HAVE_LDAP_SSL) && defined (HAVE_MOZILLA_LDAP) int ldap_ssl = 0; /* LDAP SSL indicator */ int ssl_err = 0; /* LDAP SSL error value */ # endif /* defined(HAVE_LDAP_SSL) && defined (HAVE_MOZILLA_LDAP) */ # ifdef HAVE_OPENLDAP # ifdef HAVE_LDAP_SSL /* * Set the certificate file to use for encrypted LDAP sessions... */ if (BrowseLDAPCACertFile) { debug_printf("ldap_new_connection: Setting CA certificate file \"%s\"\n", BrowseLDAPCACertFile); if ((rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, (void *)BrowseLDAPCACertFile)) != LDAP_SUCCESS) debug_printf("Unable to set CA certificate file for LDAP " "connections: %d - %s\n", rc, ldap_err2string(rc)); } # endif /* HAVE_LDAP_SSL */ /* * Initialize OPENLDAP connection... * LDAP stuff currently only supports ldapi EXTERNAL SASL binds... */ if (!BrowseLDAPServer || !strcasecmp(BrowseLDAPServer, "localhost")) rc = ldap_initialize(&TempBrowseLDAPHandle, "ldapi:///"); else rc = ldap_initialize(&TempBrowseLDAPHandle, BrowseLDAPServer); # else /* HAVE_OPENLDAP */ int ldap_port = 0; /* LDAP port */ char ldap_protocol[11], /* LDAP protocol */ ldap_host[255]; /* LDAP host */ /* * Split LDAP URI into its components... */ if (!BrowseLDAPServer) { debug_printf("BrowseLDAPServer not configured!\n"); debug_printf("Disabling LDAP browsing!\n"); /*BrowseLocalProtocols &= ~BROWSE_LDAP;*/ BrowseRemoteProtocols &= ~BROWSE_LDAP; return (NULL); } sscanf(BrowseLDAPServer, "%10[^:]://%254[^:/]:%d", ldap_protocol, ldap_host, &ldap_port); if (!strcmp(ldap_protocol, "ldap")) ldap_ssl = 0; else if (!strcmp(ldap_protocol, "ldaps")) ldap_ssl = 1; else { debug_printf("Unrecognized LDAP protocol (%s)!\n", ldap_protocol); debug_printf("Disabling LDAP browsing!\n"); /*BrowseLocalProtocols &= ~BROWSE_LDAP;*/ BrowseRemoteProtocols &= ~BROWSE_LDAP; return (NULL); } if (ldap_port == 0) { if (ldap_ssl) ldap_port = LDAPS_PORT; else ldap_port = LDAP_PORT; } debug_printf("ldap_new_connection: PROT:%s HOST:%s PORT:%d\n", ldap_protocol, ldap_host, ldap_port); /* * Initialize LDAP connection... */ if (!ldap_ssl) { if ((TempBrowseLDAPHandle = ldap_init(ldap_host, ldap_port)) == NULL) rc = LDAP_OPERATIONS_ERROR; else rc = LDAP_SUCCESS; # ifdef HAVE_LDAP_SSL } else { /* * Initialize SSL LDAP connection... */ if (BrowseLDAPCACertFile) { rc = ldapssl_client_init(BrowseLDAPCACertFile, (void *)NULL); if (rc != LDAP_SUCCESS) { debug_printf("Failed to initialize LDAP SSL client!\n"); rc = LDAP_OPERATIONS_ERROR; } else { if ((TempBrowseLDAPHandle = ldapssl_init(ldap_host, ldap_port, 1)) == NULL) rc = LDAP_OPERATIONS_ERROR; else rc = LDAP_SUCCESS; } } else { debug_printf("LDAP SSL certificate file/database not configured!\n"); rc = LDAP_OPERATIONS_ERROR; } # else /* HAVE_LDAP_SSL */ /* * Return error, because client libraries doesn't support SSL */ debug_printf("LDAP client libraries do not support SSL\n"); rc = LDAP_OPERATIONS_ERROR; # endif /* HAVE_LDAP_SSL */ } # endif /* HAVE_OPENLDAP */ /* * Check return code from LDAP initialize... */ if (rc != LDAP_SUCCESS) { debug_printf("Unable to initialize LDAP!\n"); if (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR) debug_printf("Temporarily disabling LDAP browsing...\n"); else { debug_printf("Disabling LDAP browsing!\n"); /*BrowseLocalProtocols &= ~BROWSE_LDAP;*/ BrowseRemoteProtocols &= ~BROWSE_LDAP; } ldap_disconnect(TempBrowseLDAPHandle); return (NULL); } /* * Upgrade LDAP version... */ if (ldap_set_option(TempBrowseLDAPHandle, LDAP_OPT_PROTOCOL_VERSION, (const void *)&version) != LDAP_SUCCESS) { debug_printf("Unable to set LDAP protocol version %d!\n", version); debug_printf("Disabling LDAP browsing!\n"); /*BrowseLocalProtocols &= ~BROWSE_LDAP;*/ BrowseRemoteProtocols &= ~BROWSE_LDAP; ldap_disconnect(TempBrowseLDAPHandle); return (NULL); } /* * Register LDAP rebind procedure... */ # ifdef HAVE_LDAP_REBIND_PROC # if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000) rc = ldap_set_rebind_proc(TempBrowseLDAPHandle, &ldap_rebind_proc, (void *)NULL); if (rc != LDAP_SUCCESS) debug_printf("Setting LDAP rebind function failed with status %d: %s\n", rc, ldap_err2string(rc)); # else ldap_set_rebind_proc(TempBrowseLDAPHandle, &ldap_rebind_proc, (void *)NULL); # endif /* defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000) */ # endif /* HAVE_LDAP_REBIND_PROC */ /* * Start LDAP bind... */ # if LDAP_API_VERSION > 3000 struct berval bval; bval.bv_val = BrowseLDAPPassword; bval.bv_len = (BrowseLDAPPassword == NULL) ? 0 : strlen(BrowseLDAPPassword); if (!BrowseLDAPServer || !strcasecmp(BrowseLDAPServer, "localhost")) rc = ldap_sasl_bind_s(TempBrowseLDAPHandle, NULL, "EXTERNAL", &bv, NULL, NULL, NULL); else rc = ldap_sasl_bind_s(TempBrowseLDAPHandle, BrowseLDAPBindDN, LDAP_SASL_SIMPLE, &bval, NULL, NULL, NULL); # else rc = ldap_bind_s(TempBrowseLDAPHandle, BrowseLDAPBindDN, BrowseLDAPPassword, LDAP_AUTH_SIMPLE); # endif /* LDAP_API_VERSION > 3000 */ if (rc != LDAP_SUCCESS) { debug_printf("LDAP bind failed with error %d: %s\n", rc, ldap_err2string(rc)); # if defined(HAVE_LDAP_SSL) && defined (HAVE_MOZILLA_LDAP) if (ldap_ssl && (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR)) { ssl_err = PORT_GetError(); if (ssl_err != 0) debug_printf("LDAP SSL error %d: %s\n", ssl_err, ldapssl_err2string(ssl_err)); } # endif /* defined(HAVE_LDAP_SSL) && defined (HAVE_MOZILLA_LDAP) */ ldap_disconnect(TempBrowseLDAPHandle); return (NULL); } debug_printf("LDAP connection established\n"); return (TempBrowseLDAPHandle); } /* * 'ldap_reconnect()' - Reconnect to LDAP Server */ static LDAP * /* O - New LDAP handle */ ldap_reconnect(void) { LDAP *TempBrowseLDAPHandle = NULL; /* Temp Handle to LDAP server */ /* * Get a new LDAP Handle and replace the global Handle * if the new connection was successful. */ debug_printf("Try LDAP reconnect...\n"); TempBrowseLDAPHandle = ldap_new_connection(); if (TempBrowseLDAPHandle != NULL) { if (BrowseLDAPHandle != NULL) ldap_disconnect(BrowseLDAPHandle); BrowseLDAPHandle = TempBrowseLDAPHandle; } return (BrowseLDAPHandle); } /* * 'ldap_disconnect()' - Disconnect from LDAP Server */ static void ldap_disconnect(LDAP *ld) /* I - LDAP handle */ { int rc; /* Return code */ /* * Close LDAP handle... */ # if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 rc = ldap_unbind_ext_s(ld, NULL, NULL); # else rc = ldap_unbind_s(ld); # endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ if (rc != LDAP_SUCCESS) debug_printf("Unbind from LDAP server failed with status %d: %s\n", rc, ldap_err2string(rc)); } /* * 'cupsdUpdateLDAPBrowse()' - Scan for new printers via LDAP... */ void cupsdUpdateLDAPBrowse(void) { char uri[HTTP_MAX_URI], /* Printer URI */ host[HTTP_MAX_URI], /* Hostname */ resource[HTTP_MAX_URI], /* Resource path */ local_resource[HTTP_MAX_URI], /* Resource path */ service_name[4096], location[1024], /* Printer location */ info[1024], /* Printer information */ make_model[1024], /* Printer make and model */ type_num[30], /* Printer type number */ scheme[32], /* URI's scheme */ username[64]; /* URI's username */ int port; /* URI's port number */ char *c; int hl; int rc; /* LDAP status */ int limit; /* Size limit */ LDAPMessage *res, /* LDAP search results */ *e; /* Current entry from search */ debug_printf("UpdateLDAPBrowse\n"); /* * Reconnect if LDAP Handle is invalid... */ if (! BrowseLDAPHandle) { ldap_reconnect(); return; } /* * Search for cups printers in LDAP directory... */ rc = ldap_search_rec(BrowseLDAPHandle, BrowseLDAPDN, LDAP_SCOPE_SUBTREE, BrowseLDAPFilter, (char **)ldap_attrs, 0, &res); /* * If ldap search was successfull then exit function * and temporary disable LDAP updates... */ if (rc != LDAP_SUCCESS) { if (BrowseLDAPUpdate && ((rc == LDAP_SERVER_DOWN) || (rc == LDAP_CONNECT_ERROR))) { BrowseLDAPUpdate = FALSE; debug_printf("LDAP update temporary disabled\n"); } return; } /* * If LDAP updates were disabled, we will reenable them... */ if (!BrowseLDAPUpdate) { BrowseLDAPUpdate = TRUE; debug_printf("LDAP update enabled\n"); } /* * Count LDAP entries and return if no entry exist... */ limit = ldap_count_entries(BrowseLDAPHandle, res); debug_printf("LDAP search returned %d entries\n", limit); if (limit < 1) { ldap_freeres(res); return; } /* * Loop through the available printers... */ for (e = ldap_first_entry(BrowseLDAPHandle, res); e; e = ldap_next_entry(BrowseLDAPHandle, e)) { /* * Get the required values from this entry... */ if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerDescription", info, sizeof(info)) == -1) continue; if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerLocation", location, sizeof(location)) == -1) continue; if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerMakeAndModel", make_model, sizeof(make_model)) == -1) continue; if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerType", type_num, sizeof(type_num)) == -1) continue; if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerURI", uri, sizeof(uri)) == -1) continue; /* * Process the entry... */ memset(scheme, 0, sizeof(scheme)); memset(username, 0, sizeof(username)); memset(host, 0, sizeof(host)); memset(resource, 0, sizeof(resource)); memset(local_resource, 0, sizeof(local_resource)); httpSeparateURI (HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme) - 1, username, sizeof(username) - 1, host, sizeof(host) - 1, &port, resource, sizeof(resource)- 1); if (strncasecmp (resource, "/printers/", 10) && strncasecmp (resource, "/classes/", 9)) { debug_printf("don't understand URI: %s\n", uri); return; } strncpy (local_resource, resource + 1, sizeof (local_resource) - 1); local_resource[sizeof (local_resource) - 1] = '\0'; c = strchr (local_resource, '?'); if (c) *c = '\0'; /* Build the DNS-SD service name which CUPS would give to this printer when DNS-SD-broadcasting it */ snprintf(service_name, sizeof (service_name), "%s @ %s", (strlen(info) > 0 ? info : strchr(local_resource, '/') + 1), host); /* Cut off trailing ".local" of host name */ hl = strlen(service_name); if (hl > 6 && !strcasecmp(service_name + hl - 6, ".local")) service_name[hl - 6] = '\0'; if (hl > 7 && !strcasecmp(service_name + hl - 7, ".local.")) service_name[hl - 7] = '\0'; /* DNS-SD service name has max. 63 characters */ service_name[63] = '\0'; debug_printf("LDAP: Remote host: %s; Port: %d; Remote queue name: %s; Service Name: %s\n", host, port, strchr(local_resource, '/') + 1, service_name); examine_discovered_printer_record(host, NULL, port, local_resource, service_name, location, info, "", "", "", 0, NULL); } ldap_freeres(res); } /* * 'ldap_search_rec()' - LDAP Search with reconnect */ static int /* O - Return code */ ldap_search_rec(LDAP *ld, /* I - LDAP handler */ char *base, /* I - Base dn */ int scope, /* I - LDAP search scope */ char *filter, /* I - Filter string */ char *attrs[], /* I - Requested attributes */ int attrsonly, /* I - Return only attributes? */ LDAPMessage **res) /* I - LDAP handler */ { int rc; /* Return code */ LDAP *ldr; /* LDAP handler after reconnect */ # if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 rc = ldap_search_ext_s(ld, base, scope, filter, attrs, attrsonly, NULL, NULL, NULL, LDAP_NO_LIMIT, res); # else rc = ldap_search_s(ld, base, scope, filter, attrs, attrsonly, res); # endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ /* * If we have a connection problem try again... */ if (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR) { debug_printf("LDAP search failed with status %d: %s\n", rc, ldap_err2string(rc)); debug_printf("We try the LDAP search once again after reconnecting to " "the server\n"); ldap_freeres(*res); ldr = ldap_reconnect(); # if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 rc = ldap_search_ext_s(ldr, base, scope, filter, attrs, attrsonly, NULL, NULL, NULL, LDAP_NO_LIMIT, res); # else rc = ldap_search_s(ldr, base, scope, filter, attrs, attrsonly, res); # endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ } if (rc == LDAP_NO_SUCH_OBJECT) debug_printf("ldap_search_rec: LDAP entry/object not found\n"); else if (rc != LDAP_SUCCESS) debug_printf("ldap_search_rec: LDAP search failed with status %d: %s\n", rc, ldap_err2string(rc)); if (rc != LDAP_SUCCESS) ldap_freeres(*res); return (rc); } /* * 'ldap_freeres()' - Free LDAPMessage */ static void ldap_freeres(LDAPMessage *entry) /* I - LDAP handler */ { int rc; /* Return value */ rc = ldap_msgfree(entry); if (rc == -1) debug_printf("Can't free LDAPMessage!\n"); else if (rc == 0) debug_printf("Freeing LDAPMessage was unnecessary\n"); } /* * 'ldap_getval_char()' - Get first LDAP value and convert to string */ static int /* O - Return code */ ldap_getval_firststring(LDAP *ld, /* I - LDAP handler */ LDAPMessage *entry, /* I - LDAP message or search result */ char *attr, /* I - the wanted attribute */ char *retval, /* O - String to return */ unsigned long maxsize) /* I - Max string size */ { char *dn; /* LDAP DN */ int rc = 0; /* Return code */ # if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 struct berval **bval; /* LDAP value array */ unsigned long size; /* String size */ /* * Get value from LDAPMessage... */ if ((bval = ldap_get_values_len(ld, entry, attr)) == NULL) { rc = -1; dn = ldap_get_dn(ld, entry); debug_printf("Failed to get LDAP value %s for %s!\n", attr, dn); ldap_memfree(dn); } else { /* * Check size and copy value into our string... */ size = maxsize; if (size < (bval[0]->bv_len + 1)) { rc = -1; dn = ldap_get_dn(ld, entry); debug_printf("Attribute %s is too big! (dn: %s)\n", attr, dn); ldap_memfree(dn); } else size = bval[0]->bv_len + 1; strncpy(retval, bval[0]->bv_val, size); if (size > 0) retval[size - 1] = '\0'; ldap_value_free_len(bval); } # else char **value; /* LDAP value */ /* * Get value from LDAPMessage... */ if ((value = (char **)ldap_get_values(ld, entry, attr)) == NULL) { rc = -1; dn = ldap_get_dn(ld, entry); debug_printf("Failed to get LDAP value %s for %s!\n", attr, dn); ldap_memfree(dn); } else { strncpy(retval, *value, maxsize); if (maxsize > 0) retval[maxsize - 1] = '\0'; ldap_value_free(value); } # endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ return (rc); } #endif /* HAVE_LDAP */ static int create_subscription () { ipp_t *req; ipp_t *resp; ipp_attribute_t *attr; int id = 0; http_t *conn = NULL; conn = http_connect_local (); if (conn == NULL) { debug_printf("Cannot connect to local CUPS to subscribe to notifications.\n"); return 0; } req = ippNewRequest (IPP_CREATE_PRINTER_SUBSCRIPTION); ippAddString (req, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, "/"); ippAddString (req, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, "notify-events", NULL, "all"); ippAddString (req, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, "notify-recipient-uri", NULL, "dbus://"); ippAddInteger (req, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, "notify-lease-duration", notify_lease_duration); resp = cupsDoRequest (conn, req, "/"); if (!resp || cupsLastError() != IPP_STATUS_OK) { debug_printf ("Error subscribing to CUPS notifications: %s\n", cupsLastErrorString ()); return 0; } attr = ippFindAttribute (resp, "notify-subscription-id", IPP_TAG_INTEGER); if (attr) id = ippGetInteger (attr, 0); else debug_printf ("" "ipp-create-printer-subscription response doesn't contain " "subscription id.\n"); ippDelete (resp); return id; } static gboolean renew_subscription (int id) { ipp_t *req; ipp_t *resp; http_t *conn = NULL; conn = http_connect_local (); if (conn == NULL) { debug_printf("Cannot connect to local CUPS to renew subscriptions.\n"); return FALSE; } req = ippNewRequest (IPP_RENEW_SUBSCRIPTION); ippAddInteger (req, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "notify-subscription-id", id); ippAddString (req, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, "/"); ippAddString (req, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, "notify-recipient-uri", NULL, "dbus://"); ippAddInteger (req, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, "notify-lease-duration", notify_lease_duration); resp = cupsDoRequest (conn, req, "/"); if (!resp || cupsLastError() != IPP_STATUS_OK) { debug_printf ("Error renewing CUPS subscription %d: %s\n", id, cupsLastErrorString ()); return FALSE; } ippDelete (resp); return TRUE; } static gboolean renew_subscription_timeout (gpointer userdata) { int *subscription_id = userdata; debug_printf("renew_subscription_timeout() in THREAD %ld\n", pthread_self()); if (*subscription_id <= 0 || !renew_subscription (*subscription_id)) *subscription_id = create_subscription (); return TRUE; } void cancel_subscription (int id) { ipp_t *req; ipp_t *resp; http_t *conn = NULL; conn = http_connect_local (); if (conn == NULL) { debug_printf("Cannot connect to local CUPS to cancel subscriptions.\n"); return; } if (id <= 0) return; req = ippNewRequest (IPP_CANCEL_SUBSCRIPTION); ippAddString (req, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, "/"); ippAddInteger (req, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "notify-subscription-id", id); resp = cupsDoRequest (conn, req, "/"); if (!resp || cupsLastError() != IPP_STATUS_OK) { debug_printf ("Error subscribing to CUPS notifications: %s\n", cupsLastErrorString ()); return; } ippDelete (resp); } int is_created_by_cups_browsed (const char *printer) { remote_printer_t *p; if (printer == NULL) return 0; for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) if (!p->slave_of && !strcasecmp(printer, p->queue_name)) return 1; return 0; } remote_printer_t * printer_record (const char *printer) { remote_printer_t *p; if (printer == NULL) return NULL; for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) if (!p->slave_of && !strcasecmp(printer, p->queue_name)) return p; return NULL; } void log_cluster(remote_printer_t *p) { remote_printer_t *q, *r; int i; if (p == NULL || (!debug_stderr && !debug_logfile)) return; if (p->slave_of) q = p->slave_of; else q = p; if (q->queue_name == NULL) return; debug_printf("Remote CUPS printers clustered as queue %s:\n", q->queue_name); for (r = (remote_printer_t *)cupsArrayFirst(remote_printers), i = 0; r; r = (remote_printer_t *)cupsArrayNext(remote_printers), i ++) if (r->status != STATUS_DISAPPEARED && r->status != STATUS_UNCONFIRMED && r->status != STATUS_TO_BE_RELEASED && (r == q || r->slave_of == q)) debug_printf(" %s%s%s\n", r->uri, (r == q ? "*" : ""), (i == q->last_printer ? " (last job printed)" : "")); } void log_all_printers() { remote_printer_t *p, *q; if (!debug_stderr && !debug_logfile) return; debug_printf("=== Remote printer overview ===\n"); for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) debug_printf("Printer %s (%s, %s): Local queue %s, %s, Slave of %s%s\n", p->uri, p->host, (p->ip ? p->ip : "IP not determined"), p->queue_name, (p->netprinter ? "IPP Printer" : "Remote CUPS Printer"), ((q = p->slave_of) != NULL ? (q->uri ? q->uri : "Deleted Printer") : "None"), (p->status == STATUS_UNCONFIRMED ? " (Unconfirmed)" : (p->status == STATUS_DISAPPEARED ? " (Disappeared)" : (p->status == STATUS_TO_BE_RELEASED ? " (To be released from cups-browsed)" : (p->status == STATUS_TO_BE_CREATED ? " (To be created/updated)" : ""))))); debug_printf("===============================\n"); } char* is_disabled(const char *printer, const char *reason) { ipp_t *request, *response; ipp_attribute_t *attr; const char *pname = NULL; ipp_pstate_t pstate = IPP_PRINTER_IDLE; const char *p; char *pstatemsg = NULL; static const char *pattrs[] = { "printer-name", "printer-state", "printer-state-message" }; http_t *conn = NULL; conn = http_connect_local (); if (conn == NULL) { debug_printf("Cannot connect to local CUPS to check whether the printer %s is disabled.\n", printer); return NULL; } request = ippNewRequest(CUPS_GET_PRINTERS); ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", sizeof(pattrs) / sizeof(pattrs[0]), NULL, pattrs); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); if ((response = cupsDoRequest(conn, request, "/")) != NULL) { for (attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response)) { while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER) attr = ippNextAttribute(response); if (attr == NULL) break; pname = NULL; pstate = IPP_PRINTER_IDLE; if (pstatemsg) { free(pstatemsg); pstatemsg = NULL; } while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_PRINTER) { if (!strcmp(ippGetName(attr), "printer-name") && ippGetValueTag(attr) == IPP_TAG_NAME) pname = ippGetString(attr, 0, NULL); else if (!strcmp(ippGetName(attr), "printer-state") && ippGetValueTag(attr) == IPP_TAG_ENUM) pstate = (ipp_pstate_t)ippGetInteger(attr, 0); else if (!strcmp(ippGetName(attr), "printer-state-message") && ippGetValueTag(attr) == IPP_TAG_TEXT) { if (pstatemsg != NULL) { free(pstatemsg); pstatemsg = NULL; } p = ippGetString(attr, 0, NULL); if (p != NULL) pstatemsg = strdup(p); } attr = ippNextAttribute(response); } if (pname == NULL) { if (attr == NULL) break; else continue; } if (!strcasecmp(pname, printer)) { switch (pstate) { case IPP_PRINTER_IDLE: case IPP_PRINTER_PROCESSING: ippDelete(response); if (pstatemsg != NULL) { free(pstatemsg); pstatemsg = NULL; } return NULL; case IPP_PRINTER_STOPPED: ippDelete(response); if (reason == NULL) return pstatemsg; else if (pstatemsg != NULL && (strcasestr(pstatemsg, reason) != NULL)) return pstatemsg; else { if (pstatemsg != NULL) { free(pstatemsg); pstatemsg = NULL; } return NULL; } } } } debug_printf("No information regarding enabled/disabled found about the requested printer '%s'\n", printer); ippDelete(response); if (pstatemsg != NULL) { free(pstatemsg); pstatemsg = NULL; } return NULL; } debug_printf("ERROR: Request for printer info failed: %s\n", cupsLastErrorString()); if (pstatemsg != NULL) { free(pstatemsg); pstatemsg = NULL; } return NULL; } int enable_printer (const char *printer) { ipp_t *request; char uri[HTTP_MAX_URI]; http_t *conn = NULL; conn = http_connect_local (); if (conn == NULL) { debug_printf("Cannot connect to local CUPS to enable printer %s.\n", printer); return -1; } httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", printer); request = ippNewRequest (IPP_RESUME_PRINTER); ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippDelete(cupsDoRequest (conn, request, "/admin/")); if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) { debug_printf("ERROR: Failed enabling printer '%s': %s\n", printer, cupsLastErrorString()); return -1; } debug_printf("Enabled printer '%s'\n", printer); return 0; } int disable_printer (const char *printer, const char *reason) { ipp_t *request; char uri[HTTP_MAX_URI]; http_t *conn = NULL; conn = http_connect_local (); if (conn == NULL) { debug_printf("Cannot connect to local CUPS to disable printer %s.\n", printer); return -1; } if (reason == NULL) reason = "Disabled by cups-browsed"; httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", printer); request = ippNewRequest (IPP_PAUSE_PRINTER); ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "printer-state-message", NULL, reason); ippDelete(cupsDoRequest (conn, request, "/admin/")); if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) { debug_printf("ERROR: Failed disabling printer '%s': %s\n", printer, cupsLastErrorString()); return -1; } debug_printf("Disabled printer '%s'\n", printer); return 0; } int set_cups_default_printer(const char *printer) { ipp_t *request; char uri[HTTP_MAX_URI]; http_t *conn = NULL; conn = http_connect_local (); if (conn == NULL) { debug_printf("Cannot connect to local CUPS to subscribe to set printer %s as default printer.\n", printer); return -1; } if (printer == NULL) return 0; httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", printer); request = ippNewRequest(IPP_OP_CUPS_SET_DEFAULT); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); ippDelete(cupsDoRequest(conn, request, "/admin/")); if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) { debug_printf("ERROR: Failed setting CUPS default printer to '%s': %s\n", printer, cupsLastErrorString()); return -1; } debug_printf("Successfully set CUPS default printer to '%s'\n", printer); return 0; } char* get_cups_default_printer() { ipp_t *request, *response; ipp_attribute_t *attr; const char *default_printer_name = NULL; char *name_string; http_t *conn = NULL; conn = http_connect_local (); if (conn == NULL) { debug_printf("Cannot connect to local CUPS to find out which is the default printer.\n"); return NULL; } request = ippNewRequest(CUPS_GET_DEFAULT); /* Default user */ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); /* Do it */ response = cupsDoRequest(conn, request, "/"); if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE || !response) { debug_printf("Could not determine system default printer!\n"); } else { for (attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response)) { while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER) attr = ippNextAttribute(response); if (attr) { for (; attr && ippGetGroupTag(attr) == IPP_TAG_PRINTER; attr = ippNextAttribute(response)) { if (!strcasecmp(ippGetName(attr), "printer-name") && ippGetValueTag(attr) == IPP_TAG_NAME) { default_printer_name = ippGetString(attr, 0, NULL); break; } } } if (default_printer_name) break; } } if (default_printer_name != NULL) { name_string = strdup(default_printer_name); } else { name_string = NULL; } ippDelete(response); return name_string; } int is_cups_default_printer(const char *printer) { if (printer == NULL) return 0; char *cups_default = get_cups_default_printer(); if (cups_default == NULL) return 0; if (!strcasecmp(printer, cups_default)) { free(cups_default); return 1; } free(cups_default); return 0; } int invalidate_default_printer(int local) { const char *filename = local ? local_default_printer_file : remote_default_printer_file; unlink(filename); return 0; } int record_default_printer(const char *printer, int local) { FILE *fp = NULL; const char *filename = local ? local_default_printer_file : remote_default_printer_file; if (printer == NULL || strlen(printer) == 0) return invalidate_default_printer(local); fp = fopen(filename, "w+"); if (fp == NULL) { debug_printf("ERROR: Failed creating file %s\n", filename); invalidate_default_printer(local); return -1; } fprintf(fp, "%s", printer); fclose(fp); return 0; } char* retrieve_default_printer(int local) { FILE *fp = NULL; const char *filename = local ? local_default_printer_file : remote_default_printer_file; const char *printer = NULL; char *p, buf[1024]; int n; fp = fopen(filename, "r"); if (fp == NULL) { debug_printf("Failed reading file %s\n", filename); return NULL; } p = buf; n = fscanf(fp, "%s", p); if (n == 1) { if (strlen(p) > 0) printer = p; } fclose(fp); return (printer ? strdup(printer) : NULL); } int invalidate_printer_options(const char *printer) { char filename[1024]; snprintf(filename, sizeof(filename), save_options_file, printer); unlink(filename); return 0; } char* loadPPD(http_t *http, const char *name) { /* This function replaces cupsGetPPD2(), but is much simplified (does not support classes) and works with non-standard (!= 631) ports */ char uri[HTTP_MAX_URI]; char *resource; int fd, status; char tempfile[1024] = ""; /* Download URI and resource for the PPD file */ httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "http", NULL, "localhost", 0, "/printers/%s.ppd", name); resource = strstr(uri, "/printers/"); /* Download the file */ fd = cupsTempFd(tempfile, sizeof(tempfile)); status = cupsGetFd(http, resource, fd); close(fd); /* Check for errors */ if (status == HTTP_STATUS_OK) { if (tempfile[0]) return(strdup(tempfile)); } else if (tempfile[0]) unlink(tempfile); return NULL; } int record_printer_options(const char *printer) { remote_printer_t *p; char filename[1024]; FILE *fp = NULL; char uri[HTTP_MAX_URI], *resource; ipp_t *request, *response; ipp_attribute_t *attr; const char *key; char buf[65536], *c; char *ppdname = NULL; ppd_file_t *ppd; ppd_option_t *ppd_opt; cups_option_t *option; int i; /* List of IPP attributes to get recorded */ static const char *attrs_to_record[] = { "*-default", "auth-info-required", /*"device-uri",*/ "job-quota-period", "job-k-limit", "job-page-limit", /*"port-monitor",*/ "printer-error-policy", "printer-info", "printer-is-accepting-jobs", "printer-is-shared", "printer-geo-location", "printer-location", "printer-op-policy", "printer-organization", "printer-organizational-unit", /*"printer-state", "printer-state-message", "printer-state-reasons",*/ "requesting-user-name-allowed", "requesting-user-name-denied", NULL }; const char **ptr; http_t *conn = NULL; if (printer == NULL || strlen(printer) == 0) return 0; /* Get our data about this printer */ p = printer_record(printer); if (p == NULL) { debug_printf("Not recording printer options for %s: Unknown printer!\n", printer); return 0; } if (p->status == STATUS_TO_BE_RELEASED) { debug_printf("Not recording printer options for externally modified printer %s.\n", printer); return 0; } snprintf(filename, sizeof(filename), save_options_file, printer); debug_printf("Recording printer options for %s to %s\n", printer, filename); conn = http_connect_local (); if (conn) { /* If there is a PPD file for this printer, we save the local settings for the PPD options. */ if (cups_notifier != NULL || (p && p->netprinter)) { if ((ppdname = loadPPD(conn, printer)) == NULL) { debug_printf("Unable to get PPD file for %s: %s\n", printer, cupsLastErrorString()); } else if ((ppd = ppdOpenFile(ppdname)) == NULL) { unlink(ppdname); debug_printf("Unable to open PPD file for %s.\n", printer); } else { debug_printf("Recording option settings of the PPD file for %s (%s):\n", printer, ppd->nickname); ppdMarkDefaults(ppd); for (ppd_opt = ppdFirstOption(ppd); ppd_opt; ppd_opt = ppdNextOption(ppd)) if (strcasecmp(ppd_opt->keyword, "PageRegion") != 0) { debug_printf(" %s=%s\n", ppd_opt->keyword, ppd_opt->defchoice); strncpy(buf, ppd_opt->keyword, sizeof(buf)); p->num_options = cupsAddOption(buf, ppd_opt->defchoice, p->num_options, &(p->options)); } ppdClose(ppd); unlink(ppdname); } } httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", printer); resource = uri + (strlen(uri) - strlen(printer) - 10); request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); response = cupsDoRequest(conn, request, resource); /* Write all supported printer attributes */ if (response) { debug_printf("Recording option settings from the IPP attributes for %s:\n", printer); attr = ippFirstAttribute(response); while (attr) { key = ippGetName(attr); for (ptr = attrs_to_record; *ptr; ptr++) if (strcasecmp(key, *ptr) == 0 || (*ptr[0] == '*' && strcasecmp(key + strlen(key) - strlen(*ptr) + 1, *ptr + 1) == 0)) break; if (*ptr != NULL) { if (strcasecmp(key, CUPS_BROWSED_DEST_PRINTER "-default") != 0) { ippAttributeString(attr, buf, sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; c = buf; while (*c) { if (*c == '\\') memmove(c, c + 1, strlen(c)); if (*c) c ++; } debug_printf(" %s=%s\n", key, buf); p->num_options = cupsAddOption(key, buf, p->num_options, &(p->options)); } } attr = ippNextAttribute(response); } ippDelete(response); } } else { debug_printf("Cannot connect to local CUPS to read out the IPP and PPD attributes for printer %s.\n", printer); } if (ppdname) free(ppdname); if (p->num_options > 0) { fp = fopen(filename, "w+"); if (fp == NULL) { debug_printf("ERROR: Failed creating file %s: %s\n", filename, strerror(errno)); return -1; } for (i = p->num_options, option = p->options; i > 0; i --, option ++) if (fprintf (fp, "%s=%s\n", option->name, option->value) < 0) { debug_printf("ERROR: Failed to write into file %s: %s\n", filename, strerror(errno)); fclose(fp); return -1; } fclose(fp); return 0; } else return -1; } int load_printer_options(const char *printer, int num_options, cups_option_t **options) { char filename[1024]; FILE *fp = NULL; char *opt = NULL, *val; size_t optlen = 0; if (printer == NULL || strlen(printer) == 0 || options == NULL) return 0; /* Prepare reading file with saved option settings */ snprintf(filename, sizeof(filename), save_options_file, printer); debug_printf("Loading saved printer options for %s from %s\n", printer, filename); /* Open the file with the saved option settings for this print queue */ fp = fopen(filename, "r"); if (fp == NULL) { debug_printf("Failed reading file %s, probably no options recorded yet\n", filename); } else { /* Now read the lines of the file and add each setting to our request */ errno = 0; debug_printf("Loading following option settings for printer %s:\n", printer); while (getline(&opt, &optlen, fp) != -1) { if (strlen(opt) > 1 && (val = strchr(opt, '=')) != NULL) { *val = '\0'; val ++; val[strlen(val)-1] = '\0'; debug_printf(" %s=%s\n", opt, val); num_options = cupsAddOption(opt, val, num_options, options); } } debug_printf("\n"); if (errno != 0) debug_printf("Failed reading saved options file %s: %s\n", filename, strerror(errno)); free(opt); fclose(fp); } return (num_options); } int queue_creation_handle_default(const char *printer) { /* No default printer management if we cannot get D-Bus notifications from CUPS */ if (cups_notifier == NULL) return 0; /* If this queue is recorded as the former default queue (and the current default is local), set it as default (the CUPS notification handler will record the local default printer then) */ char *recorded_default = retrieve_default_printer(0); if (recorded_default == NULL || strcasecmp(recorded_default, printer)) { if (recorded_default) free(recorded_default); return 0; } free(recorded_default); char *current_default = get_cups_default_printer(); if (current_default == NULL || !is_created_by_cups_browsed(current_default)) { if (set_cups_default_printer(printer) < 0) { debug_printf("ERROR: Could not set former default printer %s as default again.\n", printer); free(current_default); return -1; } else { debug_printf("Former default printer %s re-appeared, set as default again.\n", printer); invalidate_default_printer(0); } } free(current_default); return 0; } int queue_removal_handle_default(const char *printer) { /* No default printer management if we cannot get D-Bus notifications from CUPS */ if (cups_notifier == NULL) return 0; /* If the queue is the default printer, get back to the recorded local default printer, record this queue for getting the default set to this queue again if it re-appears. */ /* We call this also if a queue is only conserved because on cups-browsed shutdown it still has jobs */ if (!is_cups_default_printer(printer)) return 0; /* Record the fact that this printer was default */ if (record_default_printer(default_printer, 0) < 0) { /* Delete record file if recording failed */ debug_printf("ERROR: Failed recording remote default printer (%s). Removing the file with possible old recording.\n", printer); invalidate_default_printer(0); } else debug_printf("Recorded the fact that the current printer (%s) is the default printer before deleting the queue and returning to the local default printer.\n", printer); /* Switch back to a recorded local printer, if available */ char *local_default = retrieve_default_printer(1); if (local_default != NULL) { if (set_cups_default_printer(local_default) >= 0) { debug_printf("Switching back to %s as default printer.\n", local_default); free(local_default); } else { debug_printf("ERROR: Unable to switch back to %s as default printer.\n", local_default); free(local_default); return -1; } } invalidate_default_printer(1); return 0; } static char * get_local_queue_name(const char *service_name, const char *make_model, const char *resource, const char *remote_host, int *is_cups_queue, const char *exclude) { char *queue_name = NULL, *backup_queue_name = NULL, *local_queue_name = NULL, *local_queue_name_lower = NULL; local_printer_t *local_printer = NULL; cluster_t *cluster = NULL; char *member = NULL, *str = NULL; if (*is_cups_queue) { /* This is a remote CUPS printer */ /* Determine the queue name */ if (LocalQueueNamingRemoteCUPS == LOCAL_QUEUE_NAMING_MAKE_MODEL && make_model) /* Works only with DNS-SD-discovered queues as otherwise we have no make/model info */ queue_name = remove_bad_chars(make_model, 0); else if (LocalQueueNamingRemoteCUPS == LOCAL_QUEUE_NAMING_REMOTE_NAME) /* Not directly used in script generation input later, but taken from packet, so better safe than sorry. (consider second loop with backup_queue_name) */ queue_name = remove_bad_chars(strrchr(resource, '/') + 1, 0); else /* Convert DNS-SD service name into a CUPS queue name exactly as CUPS would do it, to override CUPS' own temporary queue generation mechanism */ queue_name = remove_bad_chars(service_name, 2); } else { /* This is an IPP-based network printer */ /* Determine the queue name */ if (LocalQueueNamingIPPPrinter == LOCAL_QUEUE_NAMING_MAKE_MODEL && make_model) /* Works only if we actually have make/model info in the DNS-SD record*/ queue_name = remove_bad_chars(make_model, 0); else /* Convert DNS-SD service name into a CUPS queue name exactly as CUPS would do it, to override CUPS' own temporary queue generation mechanism */ queue_name = remove_bad_chars(service_name, 2); } /* Check if there exists already a CUPS queue with the requested name Try name@host in such a case and if this is also taken, ignore the printer */ /* Get available CUPS queues */ update_local_printers (); /* We skip trying to use the queue name purely derived from the remote CUPS queue name or make and model for remote CUPS queues when automatic clustering of remote CUPS queues is turned off, to directly create queues with names containing the server name to avoid name clashes and with this remote queues skipped by cups-browsed. */ if ((!*is_cups_queue || AutoClustering == 1 || LocalQueueNamingRemoteCUPS == LOCAL_QUEUE_NAMING_DNSSD) && (!exclude || strcasecmp(queue_name, exclude))) { /* Is there a local queue with the name of the remote queue? */ local_queue_name_lower = g_ascii_strdown(queue_name, -1); local_printer = g_hash_table_lookup (local_printers, local_queue_name_lower); free(local_queue_name_lower); /* To decide on whether the queue name is already taken, only consider CUPS queues not created by us. */ if (local_printer && !local_printer->cups_browsed_controlled) { debug_printf("Queue name %s already taken.\n", queue_name); local_queue_name = NULL; } else local_queue_name = strdup(queue_name); } /* Use the originally chosen queue name plus the server name if the original name is already taken or if we had skipped using it. Do this only if we do not use DNS-SD-service-name-based naming. */ if (!local_queue_name && (!*is_cups_queue || LocalQueueNamingRemoteCUPS != LOCAL_QUEUE_NAMING_DNSSD) && (is_cups_queue || LocalQueueNamingIPPPrinter != LOCAL_QUEUE_NAMING_DNSSD)) { if ((backup_queue_name = malloc((strlen(queue_name) + strlen(remote_host) + 2) * sizeof(char))) == NULL) { debug_printf("ERROR: Unable to allocate memory.\n"); exit(1); } sprintf(backup_queue_name, "%s@%s", queue_name, remote_host); local_queue_name = backup_queue_name; debug_printf("Using fallback queue name: %s\n", local_queue_name); /* Is there a local queue with the name @? */ local_queue_name_lower = g_ascii_strdown(local_queue_name, -1); local_printer = g_hash_table_lookup (local_printers, local_queue_name_lower); free(local_queue_name_lower); if ((local_printer && !local_printer->cups_browsed_controlled) || (exclude && !strcasecmp(local_queue_name, exclude))) { /* Found also a local queue with name @ (or this name is explicitly excluded), so ignore this remote printer */ debug_printf("%s also taken, printer ignored.\n", local_queue_name); free(backup_queue_name); local_queue_name = NULL; } } free(queue_name); if (!local_queue_name) { debug_printf("No suitable local queue name found, printer ignored.\n"); return NULL; } /* Check whether our new printer matches one of the user-defined printer clusters */ for (cluster = cupsArrayFirst(clusters); cluster; cluster = cupsArrayNext(clusters)) { if (exclude && !strcasecmp(cluster->local_queue_name, exclude)) continue; local_queue_name_lower = g_ascii_strdown(cluster->local_queue_name, -1); local_printer = g_hash_table_lookup (local_printers, local_queue_name_lower); free(local_queue_name_lower); if (local_printer && !local_printer->cups_browsed_controlled) continue; for (member = cupsArrayFirst(cluster->members); member; member = cupsArrayNext(cluster->members)) { /* Match remote CUPS queue name */ if ((str = strrchr(resource, '/')) != NULL && strlen(str) > 1) { str = remove_bad_chars(str + 1, 2); if (strcasecmp(member, str) == 0) /* Match */ break; free(str); } /* Match make and model */ if (make_model) { str = remove_bad_chars(make_model, 2); if (strcasecmp(member, str) == 0) /* Match */ break; free(str); } /* Match DNS-SD service name */ if (service_name) { str = remove_bad_chars(service_name, 2); if (strcasecmp(member, str) == 0) /* Match */ break; free(str); } } if (member) break; } if (cluster) { local_queue_name = strdup(cluster->local_queue_name); *is_cups_queue = 2; free(str); } else if (AutoClustering) { /* If we do automatic clustering by matching queue names, do not add a queue to a manually defined cluster because it matches the cluster's local queue name. Manually defined clusters can only be joined by printers which match one of the cluster's member names */ for (cluster = cupsArrayFirst(clusters); cluster; cluster = cupsArrayNext(clusters)) { if (strcasecmp(local_queue_name, cluster->local_queue_name) == 0) { debug_printf("We have already a manually defined printer cluster with the name %s. Automatic clustering does not add this printer to this cluster as it does not match any of the cluster's member names. Skipping this printer.\n", local_queue_name); debug_printf("In cups-browsed.conf try \"LocalQueueNamingRemoteCUPS DNS-SD\" or give another name to your manually defined cluster (\"Cluster\" directive) to avoid name clashes.\n"); free(local_queue_name); return NULL; } } } return local_queue_name; } int join_cluster_if_needed(remote_printer_t *p, int is_cups_queue) { /* is_cups_queue: -1: Unknown, 0: IPP printer, 1: Remote CUPS queue, 2: Remote CUPS queue in user-defined cluster */ remote_printer_t *q; for (q = (remote_printer_t *)cupsArrayFirst(remote_printers); q; q = (remote_printer_t *)cupsArrayNext(remote_printers)) if (q != p && !strcasecmp(q->queue_name, p->queue_name) && /* Queue with same name on server */ !q->slave_of) /* Find the master of the queues with this name, to avoid "daisy chaining" */ break; if (q && AutoClustering == 0 && (is_cups_queue == 1 ||is_cups_queue == 0) ) { debug_printf("We have already created a queue with the name %s for another remote CUPS printer but automatic clustering of equally named printers is turned off nor did we find a manually defined cluster this printer belongs to. Skipping this printer.\n", p->queue_name); debug_printf("In cups-browsed.conf try setting \"AutoClustering On\" to cluster equally-named remote CUPS printers, \"LocalQueueNamingRemoteCUPS DNS-SD\" to avoid queue name clashes, or define clusters with the \"Cluster\" directive.\n"); return -1; } p->slave_of = (q && q->status != STATUS_DISAPPEARED && q->status != STATUS_UNCONFIRMED && q->status != STATUS_TO_BE_RELEASED) ? q : NULL; if (p->slave_of) { debug_printf("Printer %s already available through host %s, port %d.\n", p->queue_name, q->host, q->port); /* Update q */ q->status = STATUS_TO_BE_CREATED; q->timeout = time(NULL) + TIMEOUT_IMMEDIATELY; log_cluster(p); } else if (q) { q->slave_of = p; debug_printf("Unconfirmed/disappeared printer %s already available through host %s, port %d, marking that printer a slave of the newly found one.\n", p->queue_name, q->host, q->port); log_cluster(p); } return (q ? 1 : 0); } static void on_printer_state_changed (CupsNotifier *object, const gchar *text, const gchar *printer_uri, const gchar *printer, guint printer_state, const gchar *printer_state_reasons, gboolean printer_is_accepting_jobs, gpointer user_data) { char *ptr, buf[2048]; debug_printf("on_printer_state_changed() in THREAD %ld\n", pthread_self()); debug_printf("[CUPS Notification] Printer state change on printer %s: %s\n", printer, text); debug_printf("[CUPS Notification] Printer state reasons: %s\n", printer_state_reasons); if (terminating) { debug_printf("[CUPS Notification]: Ignoring because cups-browsed is terminating.\n"); return; } if (autoshutdown && autoshutdown_on == NO_JOBS) { if (check_jobs() == 0) { /* If auto shutdown is active for triggering on no jobs being left, we schedule the shutdown in autoshutdown_timeout seconds */ if (!autoshutdown_exec_id) { debug_printf ("No jobs there any more on printers made available by us, shutting down in %d sec...\n", autoshutdown_timeout); autoshutdown_exec_id = g_timeout_add_seconds (autoshutdown_timeout, autoshutdown_execute, NULL); } } else { /* If auto shutdown is active for triggering on no jobs being left, we cancel a shutdown in autoshutdown_timeout seconds as there are jobs again. */ if (autoshutdown_exec_id) { debug_printf ("New jobs there on the printers made available by us, killing auto shutdown timer.\n"); g_source_remove(autoshutdown_exec_id); autoshutdown_exec_id = 0; } } } if ((ptr = strstr(text, " is now the default printer")) != NULL) { /* Default printer has changed, we are triggered by the new default printer */ strncpy(buf, text, ptr - text); buf[ptr - text] = '\0'; debug_printf("[CUPS Notification] Default printer changed from %s to %s.\n", default_printer, buf); if (is_created_by_cups_browsed(default_printer)) { /* Previous default printer created by cups-browsed */ if (!is_created_by_cups_browsed(buf)) { /* New default printer local */ /* Removed backed-up local default printer as we do not have a remote printer as default any more */ invalidate_default_printer(1); debug_printf("Manually switched default printer from a cups-browsed-generated one to a local printer.\n"); } } else { /* Previous default printer local */ if (is_created_by_cups_browsed(buf)) { /* New default printer created by cups-browsed */ /* Back up the local default printer to be able to return to it if the remote printer disappears */ if (record_default_printer(default_printer, 1) < 0) { /* Delete record file if recording failed */ debug_printf("ERROR: Failed recording local default printer. Removing the file with possible old recording.\n"); invalidate_default_printer(1); } else debug_printf("Recorded previous default printer so that if the currently selected cups-browsed-generated one disappears, we can return to the old local one.\n"); /* Remove a recorded remote printer as after manually selecting another one as default this one is not relevant any more */ invalidate_default_printer(0); } } if (default_printer != NULL) free((void *)default_printer); default_printer = strdup(buf); } else if ((ptr = strstr(text, " is no longer the default printer")) != NULL) { /* Default printer has changed, we are triggered by the former default printer */ strncpy(buf, text, ptr - text); buf[ptr - text] = '\0'; debug_printf("[CUPS Notification] %s not default printer any more.\n", buf); } } static void on_job_state (CupsNotifier *object, const gchar *text, const gchar *printer_uri, const gchar *printer, guint printer_state, const gchar *printer_state_reasons, gboolean printer_is_accepting_jobs, guint job_id, guint job_state, const gchar *job_state_reasons, const gchar *job_name, guint job_impressions_completed, gpointer user_data) { int i, count; char buf[2048]; remote_printer_t *p, *q, *r, *s=NULL; http_t *http = NULL; ipp_t *request, *response, *printer_attributes = NULL; ipp_attribute_t *attr; const char *pname = NULL; ipp_pstate_t pstate = IPP_PRINTER_IDLE; int paccept = 0; int num_jobs, min_jobs = 99999999; char destination_uri[1024]; const char *dest_host = NULL; int dest_index = 0; int valid_dest_found = 0; char uri[HTTP_MAX_URI]; int num_options; cups_option_t *options; int num_of_printers; char* document_format; int print_quality = 0; const char *pdl = NULL; cups_array_t *pdl_list; char resolution[32]; res_t *max_res = NULL, *min_res = NULL, *res = NULL; int xres, yres; int got_printer_info; static const char *pattrs[] = { "printer-name", "printer-state", "printer-is-accepting-jobs" }; http_t *conn = NULL; debug_printf("on_job_state() in THREAD %ld\n", pthread_self()); debug_printf("[CUPS Notification] Job state changed on printer %s: %s\n", printer, text); debug_printf("[CUPS Notification] Printer state reasons: %s\n", printer_state_reasons); debug_printf("[CUPS Notification] Job ID: %d\n", job_id); debug_printf("[CUPS Notification] Job State: %s\n", job_state_reasons); debug_printf("[CUPS Notification] Job is processing: %s\n", job_state == IPP_JOB_PROCESSING ? "Yes" : "No"); if (terminating) { debug_printf("[CUPS Notification]: Ignoring because cups-browsed is terminating.\n"); return; } if (autoshutdown && autoshutdown_on == NO_JOBS) { if (check_jobs() == 0) { /* If auto shutdown is active for triggering on no jobs being left, we schedule the shutdown in autoshutdown_timeout seconds */ if (!autoshutdown_exec_id) { debug_printf ("No jobs there any more on printers made available by us, shutting down in %d sec...\n", autoshutdown_timeout); autoshutdown_exec_id = g_timeout_add_seconds (autoshutdown_timeout, autoshutdown_execute, NULL); } } else { /* If auto shutdown is active for triggering on no jobs being left, we cancel a shutdown in autoshutdown_timeout seconds as there are jobs again. */ if (autoshutdown_exec_id) { debug_printf ("New jobs there on the printers made available by us, killing auto shutdown timer.\n"); g_source_remove(autoshutdown_exec_id); autoshutdown_exec_id = 0; } } } if (job_id != 0 && job_state == IPP_JOB_PROCESSING) { /* Printer started processing a job, check if it uses the implicitclass backend and if so, we select the remote queue to which to send the job in a way so that we get load balancing between all remote queues associated with this queue. There are two methods to do that (configurable in cups-browsed.conf): Queuing of jobs on the client (LoadBalancingType = QUEUE_ON_CLIENT): Here we check all remote printers assigned to this printer and to its slaves which is currently accepting jobs and idle. If all are busy, we send a failure message and the backend will close with an error code after some seconds of delay, to make the job getting retried making us checking again here. If we find a destination, we tell the backend which remote queue this destination is, making the backend printing the job there immediately. With this all waiting jobs get queued up on the client, on the servers there will only be the jobs which are actually printing, as we do not send jobs to a server which is already printing. This is also the method which CUPS uses for classes. Advantage is a more even distribution of the job workload on the servers, and if a server fails, there are not several jobs stuck or lost. Disadvantage is that if one takes the client (laptop, mobile phone, ...) out of the local network, printing stops with the jobs waiting in the local queue. Queuing of jobs on the servers (LoadBalancingType = QUEUE_ON_SERVERS): Here we check all remote printers assigned to this printer and to its slaves which is currently accepting jobs and find the one with the lowest amount of jobs waiting and send the job to there. So on the local queue we have never jobs waiting if at least one remote printer accepts jobs. Not having jobs waiting locally has the advantage that we can take the local machine from the network and all jobs get printed. Disadvantage is that if a server with a full queue of jobs goes away, the jobs go away, too. Default is queuing the jobs on the client as this is what CUPS does with classes. */ debug_printf("[CUPS Notification] %s starts processing a job.\n", printer); conn = http_connect_local (); if (conn == NULL) { debug_printf("Cannot connect to local CUPS to set destination for job in the load-balanced cluster %s.\n", printer); return; } q = printer_record(printer); /* If we hit a slave and not the master, switch to the master */ if (q && q->slave_of) q = q->slave_of; if (q && q->queue_name) { /* We have remote CUPS queue(s) and so are using the implicitclass backend */ debug_printf("[CUPS Notification] %s is using the \"implicitclass\" CUPS backend, so let us search for a destination for this job.\n", printer); /* We keep track of the printer which we used last time and start checking with the next printer this time, to get a "round robin" type of printer usage instead of having most jobs going to the first printer in the list. Method taken from the cupsdFindAvailablePrinter() function of the scheduler/classes.c file of CUPS. */ if (q->last_printer < 0 || q->last_printer >= cupsArrayCount(remote_printers)) q->last_printer = 0; log_cluster(q); for (i = q->last_printer + 1; ; i++) { if (i >= cupsArrayCount(remote_printers)) i = 0; p = (remote_printer_t *)cupsArrayIndex(remote_printers, i); if (!strcasecmp(p->queue_name, printer) && p->status == STATUS_CONFIRMED) { num_of_printers = 0; for (r = (remote_printer_t *)cupsArrayFirst(remote_printers); r; r = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (!strcmp(r->queue_name, q->queue_name)) { if(r->status == STATUS_DISAPPEARED || r->status == STATUS_UNCONFIRMED || r->status == STATUS_TO_BE_RELEASED ) continue; num_of_printers ++; } } /* If we are in a cluster, see whether the printer supports the requested job attributes*/ if (num_of_printers > 1) { if (!supports_job_attributes_requested(printer, i, job_id, &print_quality)) { debug_printf("Printer with uri %s in cluster %s doesn't support the requested job attributes\n", p->uri, p->queue_name); if (i == q->last_printer) break; else continue; } } debug_printf("Checking state of remote printer %s on host %s, IP %s, port %d.\n", p->uri, p->host, p->ip, p->port); /* Check whether the printer is idle, processing, or disabled */ debug_printf("HTTP connection to %s:%d established.\n", p->host, p->port); response = get_printer_attributes(p->uri, pattrs, sizeof(pattrs) / sizeof(pattrs[0]), NULL, 0, 0); debug_log_out(get_printer_attributes_log); if (response != NULL) { debug_printf("IPP request to %s:%d successful.\n", p->host, p->port); pname = NULL; pstate = IPP_PRINTER_IDLE; paccept = 0; for (attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response)) { while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER) attr = ippNextAttribute(response); if (attr == NULL) break; pname = NULL; pstate = IPP_PRINTER_IDLE; paccept = 0; got_printer_info = 0; while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_PRINTER) { if (!strcmp(ippGetName(attr), "printer-name") && ippGetValueTag(attr) == IPP_TAG_NAME) { pname = ippGetString(attr, 0, NULL); } else if (!strcmp(ippGetName(attr), "printer-state") && ippGetValueTag(attr) == IPP_TAG_ENUM) pstate = (ipp_pstate_t)ippGetInteger(attr, 0); else if (!strcmp(ippGetName(attr), "printer-is-accepting-jobs") && ippGetValueTag(attr) == IPP_TAG_BOOLEAN) { paccept = ippGetBoolean(attr, 0); got_printer_info = 1; } attr = ippNextAttribute(response); } if (got_printer_info == 0) { if (attr == NULL) break; else continue; } debug_printf("IPP Response contains attributes values printer-name %s, accepting-job %d\n", (pname ? pname : "(Not reported)"), paccept); if (paccept) { debug_printf("Printer %s on host %s, port %d is accepting jobs.\n", p->uri, p->host, p->port); switch (pstate) { case IPP_PRINTER_IDLE: valid_dest_found = 1; dest_host = p->ip ? p->ip : p->host; strncpy(destination_uri, p->uri, sizeof(destination_uri) - 1); printer_attributes = p->prattrs; pdl = p->pdl; s = p; dest_index = i; debug_printf("Printer %s on host %s, port %d is idle, take this as destination and stop searching.\n", p->uri, p->host, p->port); break; case IPP_PRINTER_PROCESSING: valid_dest_found = 1; if (LoadBalancingType == QUEUE_ON_SERVERS) { num_jobs = 0; http = httpConnectEncryptShortTimeout (p->ip ? p->ip : p->host, p->port, HTTP_ENCRYPT_IF_REQUESTED); if (http) { num_jobs = get_number_of_jobs(http, p->uri, 0, CUPS_WHICHJOBS_ACTIVE); if (num_jobs >= 0 && num_jobs < min_jobs) { min_jobs = num_jobs; dest_host = p->ip ? p->ip : p->host; strncpy(destination_uri, p->uri, sizeof(destination_uri) - 1); printer_attributes = p->prattrs; pdl = p->pdl; s = p; dest_index = i; } debug_printf("Printer %s on host %s, port %d is printing and it has %d jobs.\n", p->uri, p->host, p->port, num_jobs); httpClose(http); http = NULL; } } else debug_printf("Printer %s on host %s, port %d is printing.\n", p->uri, p->host, p->port); break; case IPP_PRINTER_STOPPED: debug_printf("Printer %s on host %s, port %d is disabled, skip it.\n", p->uri, p->host, p->port); break; } } else { debug_printf("Printer %s on host %s, port %d is not accepting jobs, skip it.\n", p->uri, p->host, p->port); } break; } ippDelete(response); response = NULL; if (pstate == IPP_PRINTER_IDLE && paccept) { q->last_printer = i; break; } } else debug_printf("IPP request to %s:%d failed.\n", p->host, p->port); } if (i == q->last_printer) break; } /* Write the selected destination host into an option of our implicit class queue (cups-browsed-dest-printer="") so that the implicitclass backend will pick it up */ if ((pdl_list = cupsArrayNew3((cups_array_func_t)strcasecmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL){ debug_printf("Could Not allocate memory for cups Array \n"); return; } /* Finding the best pdl supported by the printer, we need to send the document format to the implictclass backend */ if (((attr = ippFindAttribute(printer_attributes, "document-format-supported", IPP_TAG_MIMETYPE)) != NULL) || (pdl && pdl[0] != '\0')) { const char *format = pdl; i = 0; count = ippGetCount(attr); while ((attr && i < count) || /* Go through formats in attribute */ (!attr && pdl && pdl[0] != '\0' && format[0] != '\0')) { /* Go through formats in pdl string (from DNS-SD record) */ /* Pick next format from attribute */ if (attr) format = ippGetString(attr, i, NULL); /* Add format to list of supported PDLs, skip duplicates */ if (!cupsArrayFind(pdl_list, (void *)format)) cupsArrayAdd(pdl_list, (void *)format); if (attr) /* Next format in attribute */ i ++; else { /* Find the next format in the string pdl, if there is none left, go to the terminating zero */ while (!isspace(*format) && *format != ',' && *format != '\0') format ++; while ((isspace(*format) || *format == ',') && *format != '\0') format ++; } } } /* The priority order for the PDLs is the same as in the PPD generator in cupsfilters/ppdgenerator.c */ document_format = (char *)malloc(sizeof(char) * 32); if (cupsArrayFind(pdl_list, "application/vnd.cups-pdf") || cupsArrayFind(pdl_list, "application/pdf")) strcpy(document_format, "pdf"); #ifdef CUPS_RASTER_HAVE_APPLERASTER else if (cupsArrayFind(pdl_list, "image/urf")) strcpy(document_format, "apple-raster"); #endif else if (cupsArrayFind(pdl_list, "image/pwg-raster")) strcpy(document_format, "raster"); #ifdef QPDF_HAVE_PCLM else if (cupsArrayFind(pdl_list, "application/PCLm")) strcpy(document_format, "pclm"); #endif else if (cupsArrayFind(pdl_list, "application/vnd.hp-pclxl")) strcpy(document_format, "pclxl"); else if (cupsArrayFind(pdl_list, "application/vnd.cups-postscript") || cupsArrayFind(pdl_list, "application/postscript")) strcpy(document_format, "postscript"); else if (cupsArrayFind(pdl_list, "application/vnd.hp-pcl") || cupsArrayFind(pdl_list, "application/pcl") || cupsArrayFind(pdl_list, "application/x-pcl")) strcpy(document_format, "pcl"); if (pdl_list) cupsArrayDelete(pdl_list); /* Deciding the resolution to be sent with the job */ /* Finding the minimum and maximum resolution supported by the printer */ max_res = resolutionNew(0, 0); min_res = resolutionNew(0, 0); if (s && ((attr = ippFindAttribute(s->prattrs, "printer-resolution-supported", IPP_TAG_RESOLUTION)) != NULL)) { for (i = 0, count = ippGetCount(attr); i < count; i ++) { if ((res = ippResolutionToRes(attr, i)) != NULL) { debug_printf("%d %d\n",res->x,res->y); if (i == 0) { max_res->x = res->x; max_res->y = res->y; min_res->x = res->x; min_res->y = res->y; } else { if(compare_resolutions((void *)res,(void *)max_res,NULL) > 0) { max_res->x = res->x; max_res->y = res->y; } if(compare_resolutions((void *)res,(void *)min_res,NULL) < 0) { min_res->x = res->x; min_res->y = res->y; } } free_resolution(res, NULL); res = NULL; } } } /* If we are requesting normal print quality then send default resolution, for draft send minimum resolution and for high, send the maximum resolution */ /* If none of the below dpi is selected then default dpi will be sent as 600 */ snprintf(resolution,sizeof(resolution), "600dpi"); if (s && print_quality == 3) { if (min_res != NULL){ if (min_res->x == min_res->y) snprintf(resolution,sizeof(resolution), "%ddpi", min_res->x); else snprintf(resolution,sizeof(resolution), "%dx%ddpi", min_res->x, min_res->y); } } else if (s && print_quality == 5) { if (max_res != NULL) { if (max_res->x == max_res->y) snprintf(resolution, sizeof(resolution), "%ddpi", max_res->x); else snprintf(resolution, sizeof(resolution), "%dx%ddpi", max_res->x, max_res->y); } } else if (s) { if ((attr = ippFindAttribute(s->prattrs, "printer-resolution-default", IPP_TAG_ZERO)) != NULL) { if ((res = ippResolutionToRes(attr, 0)) != NULL) { xres = res->x; yres = res->y; if (xres == yres) snprintf(resolution, sizeof(resolution), "%ddpi", xres); else snprintf(resolution, sizeof(resolution), "%dx%ddpi", xres, yres); free_resolution(res, NULL); } } } free_resolution(max_res, NULL); free_resolution(min_res, NULL); request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER); httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", printer); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); if (dest_host) { q->last_printer = dest_index; snprintf(buf, sizeof(buf), "\"%d %s %s %s\"", job_id, destination_uri, document_format, resolution); debug_printf("Destination for job %d to %s: %s\n", job_id, printer, destination_uri); } else if (valid_dest_found == 1) { snprintf(buf, sizeof(buf), "\"%d ALL_DESTS_BUSY\"", job_id); debug_printf("All destinations busy for job %d to %s\n", job_id, printer); } else { snprintf(buf, sizeof(buf), "\"%d NO_DEST_FOUND\"", job_id); debug_printf("No destination found for job %d to %s\n", job_id, printer); } num_options = 0; options = NULL; num_options = cupsAddOption(CUPS_BROWSED_DEST_PRINTER "-default", buf, num_options, &options); cupsEncodeOptions2(request, num_options, options, IPP_TAG_OPERATION); cupsEncodeOptions2(request, num_options, options, IPP_TAG_PRINTER); ippDelete(cupsDoRequest(conn, request, "/admin/")); cupsFreeOptions(num_options, options); free(document_format); if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) { debug_printf("ERROR: Unable to set \"" CUPS_BROWSED_DEST_PRINTER "-default\" option to communicate the destination server for this job (%s)!\n", cupsLastErrorString()); return; } } } } static void on_printer_deleted (CupsNotifier *object, const gchar *text, const gchar *printer_uri, const gchar *printer, guint printer_state, const gchar *printer_state_reasons, gboolean printer_is_accepting_jobs, gpointer user_data) { remote_printer_t *p; char *r; char *local_queue_name_lower = NULL; local_printer_t *local_printer = NULL; debug_printf("on_printer_deleted() in THREAD %ld\n", pthread_self()); debug_printf("[CUPS Notification] Printer deleted: %s\n", text); if (terminating) { debug_printf("[CUPS Notification]: Ignoring because cups-browsed is terminating.\n"); return; } if (is_created_by_cups_browsed(printer)) { /* Get available CUPS queues to check whether the queue did not already get re-created */ update_local_printers (); /* Look up print queue in the list */ local_queue_name_lower = g_ascii_strdown(printer, -1); local_printer = g_hash_table_lookup (local_printers, local_queue_name_lower); free(local_queue_name_lower); /* If the queue is there again, do not re-create it */ if (local_printer) { debug_printf("Printer %s already re-created.\n", printer); return; } /* a cups-browsed-generated printer got deleted, re-create it */ debug_printf("Printer %s got deleted, re-creating it.\n", printer); /* If the deleted printer was the default printer, make sure it gets the default printer again */ if (default_printer && !strcasecmp(printer, default_printer)) { if (record_default_printer(printer, 0) < 0) { /* Delete record file if recording failed */ debug_printf("ERROR: Failed recording remote default printer. Removing the file with possible old recording.\n"); invalidate_default_printer(0); } else debug_printf("Recorded %s as remote default printer so that it gets set as default after re-creating.\n", printer); /* Make sure that a recorded local default printer does not get lost during the recovery operation */ if ((r = retrieve_default_printer(1)) != NULL) { if (default_printer != NULL) free((void *)default_printer); default_printer = r; } } /* Schedule for immediate creation of the CUPS queue */ p = printer_record(printer); if (p && p->status != STATUS_DISAPPEARED && p->status != STATUS_UNCONFIRMED && p->status != STATUS_TO_BE_RELEASED) { p->status = STATUS_TO_BE_CREATED; p->timeout = time(NULL) + TIMEOUT_IMMEDIATELY; if (in_shutdown == 0) recheck_timer(); } } } static int queue_overwritten (remote_printer_t *p) { http_t *conn = NULL; ipp_t *response = NULL; /* IPP Response */ ipp_attribute_t *attr; /* Current attribute */ const char *printername, /* Print queue name */ *uri, /* Printer URI */ *device, /* Printer device URI */ *makemodel; /* Printer make and model (equals PPD NickName) */ char local_queue_uri[1024]; static const char *pattrs[] = /* Attributes we need for printers... */ { "printer-name", "printer-uri-supported", "device-uri", "printer-make-and-model" }; int overwritten = 0; if (p->overwritten) /* We already have discovered that this queue got overwritten so we do not repeat the tests and exit positively */ return 1; if (p->uri[0] == '\0') /* Also skip unconfirmed printer entries from queues of the previous session, they do not have a PPD file registered, so we cannot compare */ return 0; /* Get the device URI which our CUPS queue actually has now, a change of the URI means a modification or replacement of the print queue by something user-defined. So we schedule this queue for release from handling by cups-browsed. In a second step get the NickName of the PPD which our CUPS queue actually uses now, a change of the NickName means a replacement of the PPD of the print queue by a user-selected one. So we schedule this queue for release from handling by cups-browsed also in this case. We only need the NickName from the PPD and due to the fact that the cupsGetPPD2() function does not work when CUPS is on a non-standard port (!= 631, Bug!) and the NickName is also in the get-printer-attributes IPP response as "printer-make-and-model", we go the IPP way here and do not download the printer's PPD. */ conn = http_connect_local (); if (conn == NULL) { debug_printf("Cannot connect to local CUPS to see whether queue %s got overwritten.\n", p->queue_name); return 0; } /* URI of the local CUPS queue (not the device URI */ httpAssembleURIf(HTTP_URI_CODING_ALL, local_queue_uri, sizeof(local_queue_uri), "ipp", NULL, "localhost", 0, "/printers/%s", p->queue_name); response = get_printer_attributes2(conn, local_queue_uri, pattrs, sizeof(pattrs) / sizeof(pattrs[0]), pattrs, sizeof(pattrs) / sizeof(pattrs[0]), 1); debug_log_out(get_printer_attributes_log); if (!response || cupsLastError() > IPP_STATUS_OK_CONFLICTING) { debug_printf("lpstat: %s\n", cupsLastErrorString()); } else { printername = NULL; device = NULL; uri = NULL; makemodel = NULL; for (attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response)) { if (!strcmp(ippGetName(attr), "printer-name") && ippGetValueTag(attr) == IPP_TAG_NAME) printername = ippGetString(attr, 0, NULL); if (!strcmp(ippGetName(attr), "printer-uri-supported") && ippGetValueTag(attr) == IPP_TAG_URI) uri = ippGetString(attr, 0, NULL); if (!strcmp(ippGetName(attr), "device-uri") && ippGetValueTag(attr) == IPP_TAG_URI) device = ippGetString(attr, 0, NULL); if (!strcmp(ippGetName(attr), "printer-make-and-model") && ippGetValueTag(attr) == IPP_TAG_TEXT) makemodel = ippGetString(attr, 0, NULL); } if (printername != NULL && strcasecmp(p->queue_name, printername) == 0) { if (device == NULL) device = uri; /* Check device URI */ if (device != NULL && (p->uri == NULL || (strlen(device) < 16 || strncmp(device, "implicitclass://", 16)))) { /* The printer's device URI is different to what we have assigned, so we got notified because the queue was externally modified and so we will release this printer from the control of cups-browsed */ debug_printf("Printer %s got modified externally, discovered by a change of its device URI from %s to %s.\n", p->queue_name, (p->uri ? (p->netprinter ? p->uri : "implicitclass://...") : "(not yet determined)"), device); overwritten = 1; } /* Check NickName */ if (p->nickname == NULL || makemodel == NULL || strcasecmp(p->nickname, makemodel)) { /* The PPD file of the queue got replaced which we discovered by comparing the NickName of the PPD with the NickName which the PPD we have used has. So we were notified because the queue was externally modified and so we will release this printer from the control of cups-browsed */ debug_printf("Printer %s got modified externally, discovered by the NickName of its PPD file having changed from \"%s\" to \"%s\".\n", p->queue_name, (p->nickname ? p->nickname : "(no PPD)"), (makemodel ? makemodel : "(NickName not readable)")); overwritten = 1; } } } if (response) ippDelete(response); return overwritten; } static void on_printer_modified (CupsNotifier *object, const gchar *text, const gchar *printer_uri, const gchar *printer, guint printer_state, const gchar *printer_state_reasons, gboolean printer_is_accepting_jobs, gpointer user_data) { remote_printer_t *p; http_t *conn = NULL; ipp_t *request; /* IPP Request */ int re_create, is_cups_queue; char *new_queue_name; cups_array_t *to_be_renamed; char local_queue_uri[1024]; char *resolved_uri = NULL; debug_printf("on_printer_modified() in THREAD %ld\n", pthread_self()); debug_printf("[CUPS Notification] Printer modified: %s\n", text); if (is_created_by_cups_browsed(printer)) { p = printer_record(printer); if (p->overwritten) /* We already have discovered that this queue got overwritten and are treating the process appropriately, so return now to avoid an infinite recursion */ return; if (queue_overwritten(p)) { /* Our generated local queue pointing to a remote printer got overwritten by an externally created queue with the same name. We will release control from this queue now and try to re-create our queue under a different name, usually @. If we have slaves, we have to do this for them, too. */ p->overwritten = 1; /* First, remove the "cups-browsed=true" from the queue's options, so that cups-browsed considers this queue as created manually */ debug_printf("Removing \"cups-browsed=true\" from CUPS queue %s (%s).\n", p->queue_name, p->uri); conn = http_connect_local (); if (conn == NULL) debug_printf("Browse send failed to connect to localhost\n"); else { request = ippNewRequest(IPP_OP_CUPS_ADD_MODIFY_PRINTER); /* Printer URI: ipp://localhost/printers/ */ httpAssembleURIf(HTTP_URI_CODING_ALL, local_queue_uri, sizeof(local_queue_uri), "ipp", NULL, "localhost", 0, "/printers/%s", p->queue_name); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, local_queue_uri); /* Default user */ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); /* Option to be removed */ ippAddInteger(request, IPP_TAG_PRINTER, IPP_TAG_DELETEATTR, CUPS_BROWSED_MARK "-default", 0); /* Do it */ ippDelete(cupsDoRequest(conn, request, "/admin/")); if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) debug_printf("Unable to remove \"cups-browsed=true\" from CUPS queue!\n"); } /* Now try to rename all our printer entries with this queue name. Drop entries where renaming fails */ to_be_renamed = cupsArrayNew(NULL, NULL); /* Put the printer entries which need attention into a separate array, as we cannot run two nested loops on one CUPS array, as our printer entry array */ for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) if (strcasecmp(p->queue_name, printer) == 0) { p->overwritten = 1; cupsArrayAdd(to_be_renamed, p); } for (p = (remote_printer_t *)cupsArrayFirst(to_be_renamed); p; p = (remote_printer_t *)cupsArrayNext(to_be_renamed)) { is_cups_queue = (p->netprinter == 0 ? 1 : 0); re_create = 1; /* Is there a local queue with the same URI as the remote queue? */ if (g_hash_table_find (local_printers, local_printer_is_same_device, p)) { /* Found a local queue with the same URI as our discovered printer would get, so ignore this remote printer */ debug_printf("Printer with URI %s (or IPP/IPPS equivalent) already exists, no replacement queue to be created.\n", p->uri); re_create = 0; } else if ((new_queue_name = /* Try to find a new queue name */ get_local_queue_name(p->service_name, p->make_model, p->resource, p->host, &is_cups_queue, p->queue_name)) == NULL) { /* Not able to find a new name for the queue */ debug_printf("No new name for printer found, no replacement queue to be created.\n"); re_create = 0; } else { free(p->queue_name); p->queue_name = new_queue_name; /* Check whether the queue under its new name will be stand-alone or part of a cluster */ if (join_cluster_if_needed(p, is_cups_queue) < 0) { /* There are other cups-browsed-generated queues with the new name, not able to cluster this queue with them */ debug_printf("Not able to cluster this queue with equally-named ones.\n"); re_create = 0; } } if (resolved_uri) free(resolved_uri); if (re_create) { p->overwritten = 0; p->status = STATUS_TO_BE_CREATED; p->timeout = time(NULL) + TIMEOUT_IMMEDIATELY; debug_printf("Released CUPS queue %s from the control of cups-browsed. Printer with URI %s renamed to %s.\n", printer, p->uri, p->queue_name); } else { /* To remove this entry independent of any other entry we set the slave_of to NULL. This does not lead to an attempt to remove a CUPS queue as we have the status STATUS_TO_BE_RELEASED */ p->slave_of = NULL; p->status = STATUS_TO_BE_RELEASED; p->timeout = time(NULL) + TIMEOUT_IMMEDIATELY; debug_printf("Released CUPS queue %s from the control of cups-browsed. No local queue any more for printer with URI %s.\n", printer, p->uri); } } cupsArrayDelete(to_be_renamed); if (in_shutdown == 0) recheck_timer(); } else { if (terminating) { debug_printf("[CUPS Notification]: Not saving external option changes because cups-browsed is terminating.\n"); return; } /* The user has changed settings of a printer which we have generated, backup the changes for the case of a crash or unclean shutdown of cups-browsed. */ if (!p->no_autosave) { debug_printf("Settings of printer %s got modified, doing backup.\n", p->queue_name); p->no_autosave = 1; /* Avoid infinite recursion */ record_printer_options(p->queue_name); p->no_autosave = 0; } } } } /* This compare function makes the "lo" (looback) interface always sorted to the beginning of the array, this way one only needs to check the first element of the error to find out whether a remote printer is already available through the loopback interface (preferred interface) or not. All other interfaces are sorted alphabetically, the types let IPPS appear before IPP, and the families numerically (makes IPv4 appear before IPv6). */ int ipp_discovery_cmp(void *va, void *vb, void *data) { ipp_discovery_t *a = (ipp_discovery_t *)va; ipp_discovery_t *b = (ipp_discovery_t *)vb; int cmp; if (!a && !b) return 0; if (a && !b) return -1; if (!a && b) return 1; if (!strcasecmp(a->interface, "lo") && strcasecmp(b->interface, "lo")) return -1; if (strcasecmp(a->interface, "lo") && !strcasecmp(b->interface, "lo")) return 1; cmp = strcasecmp(a->interface, b->interface); if (cmp) return cmp; if (strcasestr(a->type, "ipps") && !strcasestr(b->type, "ipps")) return -1; if (!strcasestr(a->type, "ipps") && strcasestr(b->type, "ipps")) return 1; cmp = strcasecmp(a->type, b->type); if (cmp) return cmp; if (a->family < b->family) return -1; else if (a->family > b->family) return 1; else return 0; } void ipp_discovery_free(void *ve, void *data) { ipp_discovery_t *e = (ipp_discovery_t *)ve; if (e) { if (e->interface) free(e->interface); if (e->type) free(e->type); free(e); } } void ipp_discoveries_list(cups_array_t *a) { ipp_discovery_t *e; debug_printf("Printer discovered %d times:\n", cupsArrayCount(a)); for (e = cupsArrayFirst(a); e; e = cupsArrayNext(a)) debug_printf(" %s, %s, %s\n", e->interface, e->type, (e->family == AF_INET ? "IPv4" : (e->family == AF_INET6 ? "IPv6" : "???"))); } int ipp_discoveries_add(cups_array_t *a, const char *interface, const char *type, int family) { ipp_discovery_t *e; if (!interface || !type) return 0; if ((e = (ipp_discovery_t *)calloc(1, sizeof(ipp_discovery_t))) == NULL) { debug_printf("ERROR: Unable to allocate memory.\n"); return 0; } e->interface = strdup(interface); e->type = strdup(type); e->family = family; cupsArrayAdd(a, e); ipp_discoveries_list(a); return 1; } static remote_printer_t * create_remote_printer_entry (const char *queue_name, const char *location, const char *info, const char *uri, const char *host, const char *ip, int port, const char *resource, const char *service_name, const char *type, const char *domain, const char *interface, int family, const char *pdl, int color, int duplex, const char *make_model, int is_cups_queue) { remote_printer_t *p; remote_printer_t *q; http_t *http_printer = NULL; #ifdef HAVE_CUPS_1_6 int i; ipp_attribute_t *attr; char valuebuffer[65536]; int is_pwgraster = 0; int is_appleraster = 0; int is_pclm = 0; int is_pdf = 0; #endif /* HAVE_CUPS_1_6 */ if (!queue_name || !location || !info || !uri || !host || !resource || !service_name || !type || !domain) { debug_printf("ERROR: create_remote_printer_entry(): Input value missing!\n"); return NULL; } /* Mark this as a queue to be created locally pointing to the printer */ if ((p = (remote_printer_t *)calloc(1, sizeof(remote_printer_t))) == NULL) { debug_printf("ERROR: Unable to allocate memory.\n"); return NULL; } /* Assure that, if we have forgotten to set a field in the printer record, that it is set to zero */ memset(p, 0, sizeof(remote_printer_t)); /* Queue name */ p->queue_name = strdup(queue_name); if (!p->queue_name) goto fail; p->location = strdup(location); if (!p->location) goto fail; p->info = strdup(info); if (!p->info) goto fail; if (make_model) p->make_model = strdup(make_model); else p->make_model = NULL; if (pdl) p->pdl = strdup(pdl); else p->pdl = NULL; p->color = color; p->duplex = duplex; p->uri = strdup(uri); if (!p->uri) goto fail; p->slave_of = NULL; p->last_printer = -1; p->num_options = 0; p->options = NULL; p->host = strdup (host); if (!p->host) goto fail; p->ip = (ip != NULL ? strdup (ip) : NULL); p->port = (port != 0 ? port : 631); p->resource = strdup (resource); if (!p->resource) goto fail; p->service_name = strdup (service_name); if (!p->service_name) goto fail; /* Record DNS-SD service parameters to identify print queue entry for removal when service disappears */ p->type = strdup (type); if (!p->type) goto fail; p->domain = strdup (domain); if (!p->domain) goto fail; p->ipp_discoveries = cupsArrayNew3(ipp_discovery_cmp, NULL, NULL, 0, NULL, ipp_discovery_free); if (p->ipp_discoveries == NULL) { debug_printf("ERROR: Unable to allocate memory.\n"); return NULL; } if (domain != NULL && domain[0] != '\0' && type != NULL && type[0] != '\0') ipp_discoveries_add(p->ipp_discoveries, interface, type, family); /* Schedule for immediate creation of the CUPS queue */ p->status = STATUS_TO_BE_CREATED; p->timeout = time(NULL) + TIMEOUT_IMMEDIATELY; /* Flag which can be set to inhibit automatic saving of option settings by the on_printer_modified() notification handler function */ p->no_autosave = 0; /* Flag to be set when a local queue generated by us was overwritten by an external process. It serves to avoid that the process to treat this case is not repeated in an infinite recursion */ p->overwritten = 0; /* Flag to mark whether this printer was discovered through a legacy CUPS broadcast (1) or through DNS-SD (0) */ p->is_legacy = 0; /* Initialize field for how many timeouts cups-browsed experienced in a row during creation of this printer's queue */ p->timeouted = 0; /* Initialize nickname array for *Nickname directive from PPD * - either from CUPS server or from our PPD generator */ p->nickname = NULL; /* Remote CUPS printer or local queue remaining from previous cups-browsed session */ /* is_cups_queue: -1: Unknown, 0: IPP printer, 1: Remote CUPS queue, 2: Remote CUPS queue in user-defined cluster */ if (is_cups_queue != 0) { if (is_cups_queue > 0 && CreateRemoteCUPSPrinterQueues == 0) { debug_printf("Printer %s (%s) is a remote CUPS printer and cups-browsed is not configured to set up such printers automatically, ignoring this printer.\n", p->queue_name, p->uri); goto fail; } /* For a remote CUPS printer our local queue will be raw or get a PPD file from the remote CUPS server, so that the driver on the remote CUPS server gets used. So we will not generate a PPD file or interface script at this point. */ p->netprinter = 0; if (p->uri[0] != '\0') { p->prattrs = get_printer_attributes(p->uri, NULL, 0, NULL, 0, 1); debug_log_out(get_printer_attributes_log); if (p->prattrs == NULL) debug_printf("get-printer-attributes IPP call failed on printer %s (%s).\n", p->queue_name, p->uri); } } else { #ifndef HAVE_CUPS_1_6 /* The following code uses a lot of CUPS >= 1.6 specific stuff. For older CUPS <= 1.5.4 the following functionality is skipped which means for CUPS <= 1.5.4 only for CUPS printer broadcasts there are local queues created which should be sufficient on systems where traditional CUPS <= 1.5.4 is used. */ goto fail; #else /* HAVE_CUPS_1_6 */ /* Non-CUPS printer broadcasts are most probably from printers directly connected to the network and using the IPP protocol. We check whether we can set them up without a device-specific driver, only using page description languages which the operating system provides: PCL 5c/5e/6/XL, PostScript, PDF, PWG Raster, Apple Raster, PCLm. Especially printers designed for driverless printing (DNS-SD + IPP 2.x + at least one of PWG Raster, Apple Raster, PCLm, PDF) will work this way. Making only driverless queues we can get an easy, configuration-less way to print from mobile devices, even if there is no CUPS server with shared printers around. */ if (CreateIPPPrinterQueues == IPP_PRINTERS_NO) { debug_printf("Printer %s (%s) is an IPP network printer and cups-browsed is not configured to set up such printers automatically, ignoring this printer.\n", p->queue_name, p->uri); goto fail; } if (!pdl || pdl[0] == '\0' || (!strcasestr(pdl, "application/postscript") && !strcasestr(pdl, "application/pdf") && !strcasestr(pdl, "image/pwg-raster") && #ifdef CUPS_RASTER_HAVE_APPLERASTER !strcasestr(pdl, "image/urf") && #endif #ifdef QPDF_HAVE_PCLM !strcasestr(pdl, "application/PCLm") && #endif ((!strcasestr(pdl, "application/vnd.hp-PCL") && !strcasestr(pdl, "application/PCL") && !strcasestr(pdl, "application/x-pcl")) || ((!strncasecmp(make_model, "HP", 2) || /* HP inkjets not supported */ !strncasecmp(make_model, "Hewlett Packard", 15) || !strncasecmp(make_model, "Hewlett-Packard", 15)) && !strcasestr(make_model, "LaserJet") && !strcasestr(make_model, "Mopier"))) && !strcasestr(pdl, "application/vnd.hp-PCLXL"))) { debug_printf("Cannot create remote printer %s (URI: %s, Model: %s, Accepted data formats: %s) as its PDLs are not known, ignoring this printer.\n", p->queue_name, p->uri, make_model, pdl); debug_printf("Supported PDLs: PWG Raster, %s%sPostScript, PDF, PCL XL, PCL 5c/e (HP inkjets report themselves as PCL printers but their PCL is not supported)\n", #ifdef CUPS_RASTER_HAVE_APPLERASTER "Apple Raster, ", #else "", #endif #ifdef QPDF_HAVE_PCLM "PCLm, " #else "" #endif ); goto fail; } /* Check whether we have an equally named queue already */ for (q = (remote_printer_t *)cupsArrayFirst(remote_printers); q; q = (remote_printer_t *)cupsArrayNext(remote_printers)) if (!strcasecmp(q->queue_name, p->queue_name)) {/* Queue with same name */ debug_printf("We have already created a queue with the name %s for another printer. Skipping this printer.\n", p->queue_name); debug_printf("Try setting \"LocalQueueNamingIPPPrinter DNS-SD\" in cups-browsed.conf.\n"); goto fail; } p->slave_of = NULL; p->netprinter = 1; p->prattrs = get_printer_attributes(p->uri, NULL, 0, NULL, 0, 1); debug_log_out(get_printer_attributes_log); if (p->prattrs == NULL) { debug_printf("get-printer-attributes IPP call failed on printer %s (%s).\n", p->queue_name, p->uri); goto fail; } /* If we have opted for only printers designed for driverless use (PWG Raster + Apple Raster + PCLm + PDF) being set up automatically, we check first, whether our printer supports IPP 2.0 or newer. If not, we skip this printer */ if (CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS) { valuebuffer[0] = '\0'; debug_printf("Checking whether printer %s supports IPP 2.x or newer:\n", p->queue_name); if ((attr = ippFindAttribute(p->prattrs, "ipp-versions-supported", IPP_TAG_KEYWORD)) != NULL) { debug_printf(" Attr: %s\n", ippGetName(attr)); for (i = 0; i < ippGetCount(attr); i ++) { strncpy(valuebuffer, ippGetString(attr, i, NULL), sizeof(valuebuffer) - 1); if (strlen(ippGetString(attr, i, NULL)) > 65535) valuebuffer[65535] = '\0'; debug_printf(" Keyword: %s\n", valuebuffer); if (valuebuffer[0] > '1') break; } } if (!attr || valuebuffer[0] == '\0' || valuebuffer[0] <= '1') { debug_printf(" --> cups-browsed is configured to auto-setup only printers which are designed for driverless printing. These printers require IPP 2.x or newer, but this printer only supports IPP 1.x or older. Skipping.\n"); goto fail; } else debug_printf(" --> Printer supports IPP 2.x or newer.\n"); } /* If we have opted for only PWG Raster printers or for only printers designed for driverless use (PWG Raster + Apple Raster + PCLm + PDF) being set up automatically, we check whether the printer has a non-empty string in its "pwg-raster-document-resolution-supported" IPP attribute to see whether we have a PWG Raster printer. */ if (CreateIPPPrinterQueues == IPP_PRINTERS_PWGRASTER || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS) { valuebuffer[0] = '\0'; debug_printf("Checking whether printer %s understands PWG Raster:\n", p->queue_name); if ((attr = ippFindAttribute(p->prattrs, "pwg-raster-document-resolution-supported", IPP_TAG_RESOLUTION)) != NULL) { debug_printf(" Attr: %s\n", ippGetName(attr)); ippAttributeString(attr, valuebuffer, sizeof(valuebuffer)); debug_printf(" Value: %s\n", valuebuffer); if (valuebuffer[0] == '\0') { for (i = 0; i < ippGetCount(attr); i ++) { strncpy(valuebuffer, ippGetString(attr, i, NULL), sizeof(valuebuffer) - 1); if (strlen(ippGetString(attr, i, NULL)) > 65535) valuebuffer[65535] = '\0'; debug_printf(" Keyword: %s\n", valuebuffer); if (valuebuffer[0] != '\0') break; } } } if (attr && valuebuffer[0] != '\0') is_pwgraster = 1; debug_printf(" --> Printer %s PWG Raster.\n", is_pwgraster ? "supports" : "does not support"); } #ifdef CUPS_RASTER_HAVE_APPLERASTER /* If we have opted for only Apple Raster printers or for only printers designed for driverless use (PWG Raster + Apple Raster + PCLm + PDF) being set up automatically, we check whether the printer has a non-empty string in its "urf-supported" IPP attribute to see whether we have an Apple Raster printer. */ if (CreateIPPPrinterQueues == IPP_PRINTERS_APPLERASTER || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS) { valuebuffer[0] = '\0'; debug_printf("Checking whether printer %s understands Apple Raster:\n", p->queue_name); if ((attr = ippFindAttribute(p->prattrs, "urf-supported", IPP_TAG_KEYWORD)) != NULL) { debug_printf(" Attr: %s\n", ippGetName(attr)); ippAttributeString(attr, valuebuffer, sizeof(valuebuffer)); debug_printf(" Value: %s\n", valuebuffer); if (valuebuffer[0] == '\0') { for (i = 0; i < ippGetCount(attr); i ++) { strncpy(valuebuffer, ippGetString(attr, i, NULL), sizeof(valuebuffer) - 1); if (strlen(ippGetString(attr, i, NULL)) > 65535) valuebuffer[65535] = '\0'; debug_printf(" Keyword: %s\n", valuebuffer); if (valuebuffer[0] != '\0') break; } } } if (attr && valuebuffer[0] != '\0') is_appleraster = 1; debug_printf(" --> Printer %s Apple Raster.\n", is_appleraster ? "supports" : "does not support"); } #endif #ifdef QPDF_HAVE_PCLM /* If we have opted for only PCLm printers or for only printers designed for driverless use (PWG Raster + Apple Raster + PCLm + PDF) being set up automatically, we check whether the printer has a non-empty string in its "pclm-compression-method-preferred" IPP attribute to see whether we have a PCLm printer. */ if (CreateIPPPrinterQueues == IPP_PRINTERS_PCLM || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS) { valuebuffer[0] = '\0'; debug_printf("Checking whether printer %s understands PCLm:\n", p->queue_name); if ((attr = ippFindAttribute(p->prattrs, "pclm-compression-method-preferred", IPP_TAG_KEYWORD)) != NULL) { debug_printf(" Attr: %s\n", ippGetName(attr)); ippAttributeString(attr, valuebuffer, sizeof(valuebuffer)); debug_printf(" Value: %s\n", p->queue_name, valuebuffer); if (valuebuffer[0] == '\0') { for (i = 0; i < ippGetCount(attr); i ++) { strncpy(valuebuffer, ippGetString(attr, i, NULL), sizeof(valuebuffer) - 1); if (strlen(ippGetString(attr, i, NULL)) > 65535) valuebuffer[65535] = '\0'; debug_printf(" Keyword: %s\n", valuebuffer); if (valuebuffer[0] != '\0') break; } } } if (attr && valuebuffer[0] != '\0') is_pclm = 1; debug_printf(" --> Printer %s PCLm.\n", is_pclm ? "supports" : "does not support"); } #endif /* If we have opted for only PDF printers or for only printers designed for driverless use (PWG Raster + Apple Raster + PCLm + PDF) being set up automatically, we check whether the printer has "application/pdf" under its PDLs. */ if (CreateIPPPrinterQueues == IPP_PRINTERS_PDF || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS) { debug_printf("Checking whether printer %s understands PDF: PDLs: %s\n", p->queue_name, pdl); if(strcasestr(pdl, "application/pdf")) is_pdf = 1; debug_printf(" --> Printer %s PDF.\n", is_pdf ? "supports" : "does not support"); } /* If the printer is not the driverless printer we opted for, we skip this printer. */ if ((CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS && is_pwgraster == 0 && is_appleraster == 0 && is_pclm == 0 && is_pdf == 0) || (CreateIPPPrinterQueues == IPP_PRINTERS_PWGRASTER && is_pwgraster == 0) || (CreateIPPPrinterQueues == IPP_PRINTERS_APPLERASTER && is_appleraster == 0) || (CreateIPPPrinterQueues == IPP_PRINTERS_PCLM && is_pclm == 0) || (CreateIPPPrinterQueues == IPP_PRINTERS_PDF && is_pdf == 0)) { debug_printf("Printer %s (%s%s%s%s%s%s%s%s%s%s%s%s%s) does not support the driverless printing protocol cups-browsed is configured to accept for setting up such printers automatically, ignoring this printer.\n", p->queue_name, p->uri, (CreateIPPPrinterQueues == IPP_PRINTERS_PWGRASTER || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS ? ", " : ""), (CreateIPPPrinterQueues == IPP_PRINTERS_PWGRASTER || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS ? (is_pwgraster ? "" : "not ") : ""), (CreateIPPPrinterQueues == IPP_PRINTERS_PWGRASTER || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS ? "PWG Raster" : ""), (CreateIPPPrinterQueues == IPP_PRINTERS_APPLERASTER || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS ? ", " : ""), (CreateIPPPrinterQueues == IPP_PRINTERS_APPLERASTER || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS ? (is_appleraster ? "" : "not ") : ""), (CreateIPPPrinterQueues == IPP_PRINTERS_APPLERASTER || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS ? "Apple Raster" : ""), (CreateIPPPrinterQueues == IPP_PRINTERS_PCLM || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS ? ", " : ""), (CreateIPPPrinterQueues == IPP_PRINTERS_PCLM || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS ? (is_pclm ? "" : "not ") : ""), (CreateIPPPrinterQueues == IPP_PRINTERS_PCLM || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS ? "PCLm" : ""), (CreateIPPPrinterQueues == IPP_PRINTERS_PDF || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS ? ", " : ""), (CreateIPPPrinterQueues == IPP_PRINTERS_PDF || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS ? (is_pdf ? "" : "not ") : ""), (CreateIPPPrinterQueues == IPP_PRINTERS_PDF || CreateIPPPrinterQueues == IPP_PRINTERS_DRIVERLESS ? "PDF" : "")); goto fail; } #endif /* HAVE_CUPS_1_6 */ } /* Check whether we have an equally named queue already from another server and join a cluster if needed */ if (join_cluster_if_needed(p, is_cups_queue) < 0) goto fail; /* Add the new remote printer entry */ log_all_printers(); cupsArrayAdd(remote_printers, p); log_all_printers(); /* If auto shutdown is active we have perhaps scheduled a timer to shut down due to not having queues any more to maintain, kill the timer now */ if (autoshutdown && autoshutdown_exec_id && autoshutdown_on == NO_QUEUES && cupsArrayCount(remote_printers) > 0) { debug_printf ("New printers there to make available, killing auto shutdown timer.\n"); g_source_remove(autoshutdown_exec_id); autoshutdown_exec_id = 0; } if (http_printer) httpClose(http_printer); return p; fail: debug_printf("ERROR: Unable to create print queue, ignoring printer.\n"); if (p->prattrs) ippDelete(p->prattrs); if (http_printer) httpClose(http_printer); if (p->type) free (p->type); if (p->service_name) free (p->service_name); if (p->host) free (p->host); if (p->resource) free (p->resource); if (p->domain) free (p->domain); cupsArrayDelete(p->ipp_discoveries); if (p->ip) free (p->ip); cupsFreeOptions(p->num_options, p->options); if (p->uri) free (p->uri); if (p->pdl) free (p->pdl); if (p->make_model) free (p->make_model); if (p->location) free (p->location); if (p->info) free (p->info); if (p->queue_name) free (p->queue_name); if (p->nickname) free (p->nickname); free (p); return NULL; } void remove_printer_entry(remote_printer_t *p) { remote_printer_t *q = NULL, *r; if (p == NULL) { debug_printf ("ERROR: remove_printer_entry(): Supplied printer entry is NULL"); return; } if (!p->slave_of) { /* Check whether this queue has a slave from another server and find it */ for (q = (remote_printer_t *)cupsArrayFirst(remote_printers); q; q = (remote_printer_t *)cupsArrayNext(remote_printers)) if (q != p && q->slave_of == p && q->status != STATUS_DISAPPEARED && q->status != STATUS_UNCONFIRMED && q->status != STATUS_TO_BE_RELEASED) break; } if (q) { /* Make q the master of the cluster and p a slave of q. This way removal of p does not delete the cluster's CUPS queue and update of q makes sure the cluster's queue gets back into working state */ for (r = (remote_printer_t *)cupsArrayFirst(remote_printers); r; r = (remote_printer_t *)cupsArrayNext(remote_printers)) if (r != q && r->slave_of == p && r->status != STATUS_DISAPPEARED && r->status != STATUS_UNCONFIRMED && r->status != STATUS_TO_BE_RELEASED) r->slave_of = q; q->slave_of = NULL; p->slave_of = q; q->num_options = p->num_options; q->options = p->options; p->num_options = 0; p->options = NULL; /* Schedule this printer for updating the CUPS queue */ q->status = STATUS_TO_BE_CREATED; q->timeout = time(NULL) + TIMEOUT_IMMEDIATELY; debug_printf("Printer %s (%s) diasappeared, replacing by backup on host %s, port %d with URI %s.\n", p->queue_name, p->uri, q->host, q->port, q->uri); } else debug_printf("Printer %s (Host: %s, Port: %d, URI: %s) disappeared and no slave available (or it is a slave of another printer), removing entry.\n", p->queue_name, p->host, p->port, p->uri); /* Schedule entry and its CUPS queue for removal */ if (p->status != STATUS_TO_BE_RELEASED) p->status = STATUS_DISAPPEARED; p->timeout = time(NULL) + TIMEOUT_REMOVE; } gboolean update_cups_queues(gpointer unused) { remote_printer_t *p, *q, *r, *s, *master; http_t *http; char uri[HTTP_MAX_URI], device_uri[HTTP_MAX_URI], buf[1024], line[1024]; int num_options; cups_option_t *options; int num_jobs; cups_job_t *jobs; ipp_t *request; time_t current_time; int i, ap_remote_queue_id_line_inserted, want_raw, num_cluster_printers = 0; char *disabled_str; char *ppdfile, *ifscript; int fd = 0; /* Script file descriptor */ char tempfile[1024]; /* Temporary file */ char buffer[8192]; /* Buffer for creating script */ int bytes; const char *cups_serverbin; /* CUPS_SERVERBIN environment variable */ #ifdef HAVE_CUPS_1_6 ipp_attribute_t *attr; int count, left, right, bottom, top; const char *default_page_size = NULL, *best_color_space = NULL, *color_space; #endif const char *loadedppd = NULL; ppd_file_t *ppd = NULL; ppd_choice_t *choice; cups_file_t *in, *out; char keyword[1024], *keyptr; const char *customval; const char *val = NULL; cups_dest_t *dest = NULL; int is_shared; cups_array_t *conflicts = NULL; ipp_t *printer_attributes = NULL; cups_array_t *sizes=NULL; ipp_t *printer_ipp_response; char *make_model = NULL; const char *pdl=NULL; int color; int duplex; char *default_pagesize = NULL; const char *default_color = NULL; int cups_queues_updated = 0; /* Create dummy entry to point slaves at when their master is about to get removed now (if we point them to NULL, we would try to remove the already removed CUPS queue again when it comes to the removal of the slave. */ if (deleted_master == NULL) { if ((deleted_master = (remote_printer_t *)calloc(1, sizeof(remote_printer_t))) == NULL) { debug_printf("ERROR: Unable to allocate memory.\n"); if (in_shutdown == 0) recheck_timer (); return FALSE; } memset(deleted_master, 0, sizeof(remote_printer_t)); deleted_master->uri = ""; } /* Now redirect the slave_of pointers of the masters which get deleted now to this dummy entry */ for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) if ((p->status == STATUS_DISAPPEARED || p->status == STATUS_TO_BE_RELEASED) && (q = p->slave_of) != NULL && q->queue_name && (q->status == STATUS_DISAPPEARED || q->status == STATUS_TO_BE_RELEASED)) p->slave_of = deleted_master; debug_printf("Processing printer list ...\n"); log_all_printers(); for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { /* We need to get the current time as precise as possible for retries and reset the timeout flag */ current_time = time(NULL); timeout_reached = 0; /* terminating means we have received a signal and should shut down. in_shutdown means we have exited the main loop. update_cups_queues() is called after having exited the main loop in order to remove any queues we have set up */ if (terminating && !in_shutdown) { debug_printf("Stopping processing printer list because cups-browsed is terminating.\n"); break; } /* We do not necessarily update all local CUPS queues which are scheduled for creation, update, or removal in a single call of the update_cups_queues() function, as then we could be stuck in this function for a long time and other tasks of cups-browsed, especially directing print jobs to destination printers before the implicitclass backend times out, will not get done in time. We schedule a new call of update_cups_queues() after a short delay to continue with the next local CUPS queues. */ if (!in_shutdown && update_cups_queues_max_per_call > 0 && cups_queues_updated >= update_cups_queues_max_per_call) { debug_printf("Stopping processing printer list here because the update_cups_queues() function has reached its per-call limit of %d queue updates. Continuing in further calls.\n", update_cups_queues_max_per_call); break; } switch (p->status) { /* Print queue generated by us in a previous session */ case STATUS_UNCONFIRMED: /* Only act if the timeout has passed */ if (p->timeout > current_time) break; /* Queue not reported again by DNS-SD, remove it */ debug_printf("No remote printer named %s available, removing entry from previous session.\n", p->queue_name); remove_printer_entry(p); /* DNS-SD has reported this printer as disappeared or we have replaced this printer by another one */ case STATUS_DISAPPEARED: case STATUS_TO_BE_RELEASED: /* Only act if the timeout has passed */ if (p->timeout > current_time) break; debug_printf("Removing entry %s (%s)%s.\n", p->queue_name, p->uri, (p->slave_of || p->status == STATUS_TO_BE_RELEASED ? "" : " and its CUPS queue")); /* Slaves do not have a CUPS queue */ if ((q = p->slave_of) == NULL) { if ((http = http_connect_local ()) == NULL) { debug_printf("Unable to connect to CUPS!\n"); if (in_shutdown == 0) { current_time = time(NULL); p->timeout = current_time + TIMEOUT_RETRY; } break; } /* Do not auto-save option settings due to the print queue removal process or release process */ p->no_autosave = 1; /* Record the option settings to retrieve them when the remote queue re-appears later or when cups-browsed gets started again */ record_printer_options(p->queue_name); if (p->status != STATUS_TO_BE_RELEASED && !queue_overwritten(p)) { /* Remove the CUPS queue */ /* Check whether there are still jobs and do not remove the queue then */ num_jobs = 0; jobs = NULL; num_jobs = cupsGetJobs2(http, &jobs, p->queue_name, 0, CUPS_WHICHJOBS_ACTIVE); if (num_jobs > 0) { /* There are still jobs */ debug_printf("Queue has still jobs or CUPS error!\n"); cupsFreeJobs(num_jobs, jobs); /* Disable the queue */ #ifdef HAVE_AVAHI if (avahi_present || p->domain == NULL || p->domain[0] == '\0') /* If avahi has got shut down, do not disable queues which are, created based on DNS-SD broadcasts as the server has most probably not gone away */ #endif /* HAVE_AVAHI */ disable_printer(p->queue_name, "Printer disappeared or cups-browsed shutdown"); /* Schedule the removal of the queue for later */ if (in_shutdown == 0) { current_time = time(NULL); p->timeout = current_time + TIMEOUT_RETRY; p->no_autosave = 0; break; } else /* Make sure queue's list entry gets freed */ goto keep_queue; } /* If this queue was the default printer, note that fact so that it gets the default printer again when it re-appears, also switch back to the last local default printer */ queue_removal_handle_default(p->queue_name); /* If we do not have a subscription to CUPS' D-Bus notifications and so no default printer management, we simply do not remove this CUPS queue if it is the default printer, to not cause a change of the default printer or the loss of the information that this printer is the default printer. */ if (cups_notifier == NULL && is_cups_default_printer(p->queue_name)) { /* Schedule the removal of the queue for later */ if (in_shutdown == 0) { current_time = time(NULL); p->timeout = current_time + TIMEOUT_RETRY; p->no_autosave = 0; break; } else /* Make sure queue's list entry gets freed */ goto keep_queue; } /* No jobs, remove the CUPS queue */ debug_printf("Removing local CUPS queue %s (%s).\n", p->queue_name, p->uri); request = ippNewRequest(CUPS_DELETE_PRINTER); /* Printer URI: ipp://localhost/printers/ */ httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", p->queue_name); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); /* Default user */ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); /* Do it */ ippDelete(cupsDoRequest(http, request, "/admin/")); cups_queues_updated ++; if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE && cupsLastError() != IPP_STATUS_ERROR_NOT_FOUND) { debug_printf("Unable to remove CUPS queue! (%s)\n", cupsLastErrorString()); if (in_shutdown == 0) { current_time = time(NULL); p->timeout = current_time + TIMEOUT_RETRY; p->no_autosave = 0; break; } } } } keep_queue: /* CUPS queue removed or released from cups-browsed, remove the list entry */ /* Note that we do not need to break out of the loop passing through all elements of a CUPS array when we remove an element via the cupsArrayRemove() function, as the function decreases the array- internal index by one and so the cupsArrayNext() call gives us the element right after the deleted element. So no skipping of an element and especially no reading beyond the end of the array. */ cupsArrayRemove(remote_printers, p); if (p->queue_name) free (p->queue_name); if (p->location) free (p->location); if (p->info) free (p->info); if (p->make_model) free (p->make_model); if (p->pdl) free (p->pdl); if (p->uri) free (p->uri); cupsFreeOptions(p->num_options, p->options); if (p->host) free (p->host); if (p->ip) free (p->ip); if (p->resource) free (p->resource); if (p->service_name) free (p->service_name); if (p->type) free (p->type); if (p->domain) free (p->domain); cupsArrayDelete(p->ipp_discoveries); if (p->prattrs) ippDelete (p->prattrs); if (p->nickname) free (p->nickname); free(p); p = NULL; /* If auto shutdown is active and all printers we have set up got removed again, schedule the shutdown in autoshutdown_timeout seconds Note that in this case we also do not have jobs any more so if we auto shutdown on running out of jobs, trigger it here, too. */ if (in_shutdown == 0 && autoshutdown && !autoshutdown_exec_id && (cupsArrayCount(remote_printers) == 0 || (autoshutdown_on == NO_JOBS && check_jobs() == 0))) { debug_printf ("No printers there any more to make available or no jobs, shutting down in %d sec...\n", autoshutdown_timeout); autoshutdown_exec_id = g_timeout_add_seconds (autoshutdown_timeout, autoshutdown_execute, NULL); } break; /* DNS-SD has reported a new remote printer, create a CUPS queue for it, or upgrade an existing queue, or update a queue to use a backup host when it has disappeared on the currently used host */ /* (...or, we've just received a CUPS Browsing packet for this queue) */ case STATUS_TO_BE_CREATED: /* Do not create a queue for slaves */ if (p->slave_of) { master = p->slave_of; if (master->queue_name) { p->status = STATUS_CONFIRMED; master->status = STATUS_TO_BE_CREATED; master->timeout = time(NULL) + TIMEOUT_IMMEDIATELY; if (p->is_legacy) { p->timeout = time(NULL) + BrowseTimeout; debug_printf("starting BrowseTimeout timer for %s (%ds)\n", p->queue_name, BrowseTimeout); } else p->timeout = (time_t) -1; } else { debug_printf("Master for slave %s is invalid (deleted?)\n", p->queue_name); p->status = STATUS_DISAPPEARED; p->timeout = time(NULL) + TIMEOUT_IMMEDIATELY; } break; } /* Only act if the timeout has passed */ if (p->timeout > current_time) break; /* cups-browsed tried to add this print queue unsuccessfully for too many times due to timeouts - Skip print queue creation for this one */ if (p->timeouted >= HttpMaxRetries) { debug_printf("Max number of retries (%d) for creating print queue %s reached, skipping it.\n", HttpMaxRetries, p->queue_name); continue; } debug_printf("Creating/Updating CUPS queue %s\n", p->queue_name); /* Make sure to have a connection to the local CUPS daemon */ if ((http = http_connect_local ()) == NULL) { debug_printf("Unable to connect to CUPS!\n"); current_time = time(NULL); p->timeout = current_time + TIMEOUT_RETRY; break; } httpSetTimeout(http, HttpLocalTimeout, http_timeout_cb, NULL); /* Do not auto-save option settings due to the print queue creation process */ p->no_autosave = 1; /* Printer URI: ipp://localhost/printers/ */ httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", p->queue_name); ifscript = NULL; ppdfile = NULL; #ifdef HAVE_CUPS_1_6 /* Check whether there is a temporary CUPS queue which we would overwrite */ dest = NULL; if (OnlyUnsupportedByCUPS == 0) dest = cupsGetNamedDest(http, p->queue_name, NULL); if (dest) { /* CUPS has found a queue with this name. Either CUPS generates a temporary queue here or we have already made this queue permanent. In any case, load the PPD from this queue to conserve the PPD which CUPS has originally generated. */ if (p->netprinter == 1 && IPPPrinterQueueType == PPD_YES && UseCUPSGeneratedPPDs) { if (LocalQueueNamingIPPPrinter != LOCAL_QUEUE_NAMING_DNSSD) { debug_printf("Local queue %s: We can replace temporary CUPS queues and keep their PPD file only when we name our queues like them, to avoid duplicate queues to the same printer.\n", p->queue_name); debug_printf("Not loading PPD from temporary CUPS queue for this printer.\n"); debug_printf("Try setting \"LocalQueueNamingIPPPrinter DNS-SD\" in cups-browsed.conf.\n"); } else { /* This call makes CUPS actually create the queue so that we can grab the PPD. We discard the result of the call. */ debug_printf("Establishing dummy connection to make CUPS create the temporary queue.\n"); cups_dinfo_t *dinfo = cupsCopyDestInfo(http, dest); if (dinfo == NULL) debug_printf("Unable to connect to destination.\n"); else { debug_printf("Temporary queue created, grabbing the PPD.\n"); cupsFreeDestInfo(dinfo); loadedppd = NULL; if ((loadedppd = loadPPD(http, p->queue_name)) == NULL) debug_printf("Unable to load PPD from local temporary queue %s!\n", p->queue_name); else { ppdfile = strdup(loadedppd); debug_printf("Loaded PPD file %s from local temporary queue %s.\n", ppdfile, p->queue_name); } } } } /* If we have already a temporary CUPS queue our local queue we are creating would overwrite the temporary queue, and so the resulting queue will still be considered temporary by CUPS and removed after one minute of inactivity. To avoid this we need to convert the queue into a permanent one and CUPS does this only by sharing the queue (setting its boolean printer-is-shared option. We unset the bit right after that to not actually share the queue (if we want to share the queue we take care about this later). Note that we cannot reliably determine whether we have a temporary queue via the printer-is-temporary attribute, therefore we consider only shared queues as for sure permanent and not shared queues as possibly temporary. To assure we have a permanent queue in the end we set and remove the shared bit on any queue which is not shared. If the temporary queue is pointing to a remote CUPS printer we cannot modify its printer-is-shared option as CUPS prevents this. In this case we remove the temporary queue so that we create a fresh one which will always be permanent. If the temporary queue has still jobs we will not remove it to not loose the jobs and wait with creating our new queue until the jobs are done. */ val = cupsGetOption ("printer-is-shared", dest->num_options, dest->options); is_shared = val && (!strcasecmp (val, "yes") || !strcasecmp (val, "on") || !strcasecmp (val, "true")); cupsFreeDests(1, dest); if (!is_shared) { debug_printf("Our new queue overwrites the possibly temporary CUPS queue %s, so we need to assure the queue gets permanent.\n", p->queue_name); /* We need to modify the printer-is-shared bit twice if we need to make a temporary queue permanent but not share this queue */ for (i = 0; i <= 1; i ++) { if (i == 0) debug_printf("Setting printer-is-shared bit to make this queue permanent.\n"); else debug_printf("Unsetting printer-is-shared bit.\n"); request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); num_options = 0; options = NULL; num_options = cupsAddOption("printer-is-shared", (i == 0 ? "true" : "false"), num_options, &options); num_options = cupsAddOption(CUPS_BROWSED_MARK "-default", "true", num_options, &options); cupsEncodeOptions2(request, num_options, options, IPP_TAG_OPERATION); cupsEncodeOptions2(request, num_options, options, IPP_TAG_PRINTER); /* * Do IPP request for printer-is-shared option only when we have * network printer or if we have remote CUPS queue, do IPP request * only if we have CUPS older than 2.2. * When you have remote queue, clean up and break from the loop. */ if (p->netprinter != 0 || !HAVE_CUPS_2_2 || AllowResharingRemoteCUPSPrinters) ippDelete(cupsDoRequest(http, request, "/admin/")); else { ippDelete(request); cupsFreeOptions(num_options, options); break; } cupsFreeOptions(num_options, options); if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) { debug_printf("Unable change printer-is-shared bit to %s (%s)!\n", (i == 0 ? "true" : "false"), cupsLastErrorString()); break; } } /* Error on modifying printer-is-shared bit, removing possibly temporary queue */ if (i <= 1) { debug_printf("Removing the possibly temporary CUPS queue.\n"); /* Check whether there are still jobs and do not remove the queue then */ num_jobs = 0; jobs = NULL; num_jobs = cupsGetJobs2(http, &jobs, p->queue_name, 0, CUPS_WHICHJOBS_ACTIVE); if (num_jobs > 0) { /* there are still jobs */ debug_printf("Temporary queue has still jobs or CUPS error, retrying later.\n"); cupsFreeJobs(num_jobs, jobs); /* Schedule the removal of the queue for later */ if (in_shutdown == 0) { current_time = time(NULL); p->timeout = current_time + TIMEOUT_RETRY; p->no_autosave = 0; } break; } /* No jobs, remove the CUPS queue */ request = ippNewRequest(CUPS_DELETE_PRINTER); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); ippDelete(cupsDoRequest(http, request, "/admin/")); if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE && cupsLastError() != IPP_STATUS_ERROR_NOT_FOUND) { debug_printf("Unable to remove temporary CUPS queue (%s), retrying later\n", cupsLastErrorString()); if (in_shutdown == 0) { current_time = time(NULL); p->timeout = current_time + TIMEOUT_RETRY; p->no_autosave = 0; break; } } } } else debug_printf("Creating/Updating permanent CUPS queue %s.\n", p ->queue_name); } else debug_printf("Creating permanent CUPS queue %s.\n", p->queue_name); /* If we did not already obtain a PPD file from the temporary CUPS queue or if we want to use a System V interface script for our IPP network printer, we proceed here */ if (p->netprinter == 1) { if (p->prattrs == NULL) { p->prattrs = get_printer_attributes(p->uri, NULL, 0, NULL, 0, 1); debug_log_out(get_printer_attributes_log); } if (p->prattrs == NULL) { debug_printf("get-printer-attributes IPP call failed on printer %s (%s).\n", p->queue_name, p->uri); p->status = STATUS_DISAPPEARED; current_time = time(NULL); p->timeout = current_time + TIMEOUT_IMMEDIATELY; goto cannot_create; } if (IPPPrinterQueueType == PPD_YES) { num_cluster_printers = 0; for (s = (remote_printer_t *)cupsArrayFirst(remote_printers); s; s = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (!strcmp(s->queue_name, p->queue_name)) { if (s->status == STATUS_DISAPPEARED || s->status == STATUS_UNCONFIRMED || s->status == STATUS_TO_BE_RELEASED ) continue; num_cluster_printers ++; } } if (num_cluster_printers == 1) { printer_attributes = p->prattrs; conflicts = NULL; default_pagesize = NULL; default_color = NULL; make_model = p->make_model; pdl = p->pdl; color = p->color; duplex = p->duplex; sizes = NULL; } else { make_model = (char*)malloc(sizeof(char) * 256); printer_attributes = get_cluster_attributes(p->queue_name); if ((attr = ippFindAttribute(printer_attributes, "printer-make-and-model", IPP_TAG_TEXT)) != NULL) strncpy(make_model, ippGetString(attr, 0, NULL), sizeof(make_model) - 1); color = 0; duplex = 0; for (r = (remote_printer_t *)cupsArrayFirst(remote_printers); r; r = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (!strcmp(p->queue_name, r->queue_name)) { if (r->color == 1) color = 1; if (r->duplex == 1) duplex = 1; } } default_pagesize = (char *)malloc(sizeof(char)*32); debug_printf("Generated Merged Attributes for local queue %s\n", p->queue_name); conflicts = generate_cluster_conflicts(p->queue_name, printer_attributes); debug_printf("Generated Constraints for queue %s\n", p->queue_name); sizes = get_cluster_sizes(p->queue_name); get_cluster_default_attributes(&printer_attributes, p->queue_name, default_pagesize, &default_color); debug_printf("Generated Default Attributes for local queue %s\n", p->queue_name); } if (ppdfile == NULL) { /* If we do not want CUPS-generated PPDs or we cannot obtain a CUPS-generated PPD, for example if CUPS does not create a temporary queue for this printer, we generate a PPD by ourselves */ printer_ipp_response = (num_cluster_printers == 1) ? p->prattrs : printer_attributes; if (!ppdCreateFromIPP2(buffer, sizeof(buffer), printer_ipp_response, make_model, pdl, color, duplex, conflicts, sizes, default_pagesize, default_color)) { if (errno != 0) debug_printf("Unable to create PPD file: %s\n", strerror(errno)); else debug_printf("Unable to create PPD file: %s\n", ppdgenerator_msg); p->status = STATUS_DISAPPEARED; current_time = time(NULL); p->timeout = current_time + TIMEOUT_IMMEDIATELY; goto cannot_create; } else { debug_printf("PPD generation successful: %s\n", ppdgenerator_msg); debug_printf("Created temporary PPD file: %s\n", buffer); ppdfile = strdup(buffer); } } if (num_cluster_printers != 1) { if (default_pagesize != NULL) { free(default_pagesize); default_pagesize = NULL; } if (make_model != NULL) { free(make_model); make_model = NULL; } if (conflicts != NULL) { cupsArrayDelete(conflicts); conflicts = NULL; } if (printer_attributes != NULL) { ippDelete(printer_attributes); printer_attributes = NULL; } if (sizes != NULL) { cupsArrayDelete(sizes); sizes = NULL; } } } else if (IPPPrinterQueueType == PPD_NO) { ppdfile = NULL; /* Find default page size of the printer */ attr = ippFindAttribute(p->prattrs, "media-default", IPP_TAG_ZERO); if (attr) { default_page_size = ippGetString(attr, 0, NULL); debug_printf("Default page size: %s\n", default_page_size); p->num_options = cupsAddOption("media-default", default_page_size, p->num_options, &(p->options)); } else { attr = ippFindAttribute(p->prattrs, "media-ready", IPP_TAG_ZERO); if (attr) { default_page_size = ippGetString(attr, 0, NULL); debug_printf("Default page size: %s\n", default_page_size); p->num_options = cupsAddOption("media-default", default_page_size, p->num_options, &(p->options)); } else debug_printf("No default page size found!\n"); } /* Find maximum unprintable margins of the printer */ if ((attr = ippFindAttribute(p->prattrs, "media-bottom-margin-supported", IPP_TAG_INTEGER)) != NULL) { for (i = 1, bottom = ippGetInteger(attr, 0), count = ippGetCount(attr); i < count; i ++) if (ippGetInteger(attr, i) > bottom) bottom = ippGetInteger(attr, i); } else bottom = 1270; snprintf(buffer, sizeof(buffer), "%d", bottom); p->num_options = cupsAddOption("media-bottom-margin-default", buffer, p->num_options, &(p->options)); if ((attr = ippFindAttribute(p->prattrs, "media-left-margin-supported", IPP_TAG_INTEGER)) != NULL) { for (i = 1, left = ippGetInteger(attr, 0), count = ippGetCount(attr); i < count; i ++) if (ippGetInteger(attr, i) > left) left = ippGetInteger(attr, i); } else left = 635; snprintf(buffer, sizeof(buffer), "%d", left); p->num_options = cupsAddOption("media-left-margin-default", buffer, p->num_options, &(p->options)); if ((attr = ippFindAttribute(p->prattrs, "media-right-margin-supported", IPP_TAG_INTEGER)) != NULL) { for (i = 1, right = ippGetInteger(attr, 0), count = ippGetCount(attr); i < count; i ++) if (ippGetInteger(attr, i) > right) right = ippGetInteger(attr, i); } else right = 635; snprintf(buffer, sizeof(buffer), "%d", right); p->num_options = cupsAddOption("media-right-margin-default", buffer, p->num_options, &(p->options)); if ((attr = ippFindAttribute(p->prattrs, "media-top-margin-supported", IPP_TAG_INTEGER)) != NULL) { for (i = 1, top = ippGetInteger(attr, 0), count = ippGetCount(attr); i < count; i ++) if (ippGetInteger(attr, i) > top) top = ippGetInteger(attr, i); } else top = 1270; snprintf(buffer, sizeof(buffer), "%d", top); p->num_options = cupsAddOption("media-top-margin-default", buffer, p->num_options, &(p->options)); debug_printf("Margins: Left: %d, Right: %d, Top: %d, Bottom: %d\n", left, right, top, bottom); /* Find best color space of the printer */ attr = ippFindAttribute(p->prattrs, "pwg-raster-document-type-supported", IPP_TAG_ZERO); if (attr) { for (i = 0; i < ippGetCount(attr); i ++) { color_space = ippGetString(attr, i, NULL); debug_printf("Supported color space: %s\n", color_space); if (color_space_score(color_space) > color_space_score(best_color_space)) best_color_space = color_space; } debug_printf("Best color space: %s\n", best_color_space); p->num_options = cupsAddOption("print-color-mode-default", best_color_space, p->num_options, &(p->options)); } else { debug_printf("No info about supported color spaces found!\n"); p->num_options = cupsAddOption("print-color-mode-default", p->color == 1 ? "rgb" : "black", p->num_options, &(p->options)); } if (p->duplex) p->num_options = cupsAddOption("sides-default", "two-sided-long-edge", p->num_options, &(p->options)); p->num_options = cupsAddOption("output-format-default", p->pdl, p->num_options, &(p->options)); p->num_options = cupsAddOption("make-and-model-default", remove_bad_chars(p->make_model, 0), p->num_options, &(p->options)); if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL) cups_serverbin = CUPS_SERVERBIN; if ((fd = cupsTempFd(tempfile, sizeof(tempfile))) < 0) { debug_printf("Unable to create interface script file\n"); p->status = STATUS_DISAPPEARED; current_time = time(NULL); p->timeout = current_time + TIMEOUT_IMMEDIATELY; goto cannot_create; } debug_printf("Creating temp script file \"%s\"\n", tempfile); snprintf(buffer, sizeof(buffer), "#!/bin/sh\n" "# System V interface script for printer %s generated by cups-browsed\n" "\n" "if [ $# -lt 5 -o $# -gt 6 ]; then\n" " echo \"ERROR: $0 job-id user title copies options [file]\" >&2\n" " exit 1\n" "fi\n" "\n" "# Read from given file\n" "if [ -n \"$6\" ]; then\n" " exec \"$0\" \"$1\" \"$2\" \"$3\" \"$4\" \"$5\" < \"$6\"\n" "fi\n" "\n" "%s/filter/sys5ippprinter \"$1\" \"$2\" \"$3\" \"$4\" \"$5\"\n", p->queue_name, cups_serverbin); bytes = write(fd, buffer, strlen(buffer)); if (bytes != strlen(buffer)) { debug_printf("Unable to write interface script into the file\n"); p->status = STATUS_DISAPPEARED; current_time = time(NULL); p->timeout = current_time + TIMEOUT_IMMEDIATELY; goto cannot_create; } close(fd); ifscript = strdup(tempfile); } } #endif /* HAVE_CUPS_1_6 */ /* Do we have default option settings in cups-browsed.conf? */ if (DefaultOptions) { debug_printf("Applying default option settings to printer %s: %s\n", p->queue_name, DefaultOptions); p->num_options = cupsParseOptions(DefaultOptions, p->num_options, &p->options); } /* Loading saved option settings from last session */ p->num_options = load_printer_options(p->queue_name, p->num_options, &p->options); /* Determine whether we have an IPP network printer. If not we have remote CUPS queue(s) and so we use an implicit class for load balancing. In this case we will assign an implicitclass://... device URI, which makes cups-browsed find the best destination for each job. */ loadedppd = NULL; if (cups_notifier != NULL && p->netprinter == 0) { /* We are not an IPP network printer, so we use the device URI implicitclass:/// We use the httpAssembleURI() function here, to percent-encode the queue name in the URI, so that any allowed character in a queue name, especially the '@' when we add the server name to a remote queue's name, goes safely into the URI. The implicitclass backend uses httpSeparateURI() to decode the queue name. We never use the implicitclass backend if we do not have D-Bus notification from CUPS as we cannot assign a destination printer to an incoming job then. */ httpAssembleURI(HTTP_URI_CODING_ALL, device_uri, sizeof(device_uri), "implicitclass", NULL, p->queue_name, 0, NULL); debug_printf("Print queue %s is for remote CUPS queue(s) and we get notifications from CUPS, using implicit class device URI %s\n", p->queue_name, device_uri); if (!ppdfile && !ifscript) { /* Having another backend than the CUPS "ipp" backend the options from the PPD of the queue on the server are not automatically used on the client any more, so we have to explicitly load the PPD from one of the servers, apply it to our local queue, and replace its "*cupsFilter(2): ..." lines by one line making the print data get passed through to the server without filtering on the client (where not necessarily the right filters/drivers are installed) so that it gets filtered on the server. In addition, we prefix the PPD's NickName, so that automatic PPD updating by the distribution's package installation/update infrastructure is suppressed. */ /* Generating the ppd file for the remote cups queue */ if (p->prattrs == NULL) { p->prattrs = get_printer_attributes(p->uri, NULL, 0, NULL, 0, 1); debug_log_out(get_printer_attributes_log); } if (p->prattrs == NULL) { debug_printf("get-printer-attributes IPP call failed on printer %s (%s).\n", p->queue_name, p->uri); goto cannot_create; } num_cluster_printers = 0; for (s = (remote_printer_t *)cupsArrayFirst(remote_printers); s; s = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (!strcmp(s->queue_name, p->queue_name)) { if (s->status == STATUS_DISAPPEARED || s->status == STATUS_UNCONFIRMED || s->status == STATUS_TO_BE_RELEASED ) continue; num_cluster_printers++; } } if (num_cluster_printers == 1) { printer_attributes = p->prattrs; conflicts = NULL; default_pagesize = NULL; default_color = NULL; make_model = p->make_model; pdl = p->pdl; color = p->color; duplex = p->duplex; sizes = NULL; } else { make_model = (char*)malloc(sizeof(char)*256); printer_attributes = get_cluster_attributes(p->queue_name); if((attr = ippFindAttribute(printer_attributes, "printer-make-and-model", IPP_TAG_TEXT)) != NULL) strncpy(make_model, ippGetString(attr, 0, NULL), sizeof(make_model) - 1); color = 0; duplex = 0; for (r = (remote_printer_t *)cupsArrayFirst(remote_printers); r; r = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (!strcmp(p->queue_name, r->queue_name)) { if (r->color == 1) color = 1; if (r->duplex == 1) duplex = 1; } } default_pagesize = (char *)malloc(sizeof(char)*32); debug_printf("Generated Merged Attributes for local queue %s\n", p->queue_name); conflicts = generate_cluster_conflicts(p->queue_name, printer_attributes); debug_printf("Generated Constraints for queue %s\n",p->queue_name); sizes = get_cluster_sizes(p->queue_name); get_cluster_default_attributes(&printer_attributes, p->queue_name, default_pagesize,&default_color); debug_printf("Generated Default Attributes for local queue %s\n", p->queue_name); } if (ppdfile == NULL) { /* If we do not want CUPS-generated PPDs or we cannot obtain a CUPS-generated PPD, for example if CUPS does not create a temporary queue for this printer, we generate a PPD by ourselves */ printer_ipp_response = (num_cluster_printers == 1) ? p->prattrs : printer_attributes; if (!ppdCreateFromIPP2(buffer, sizeof(buffer), printer_ipp_response, make_model, pdl, color, duplex, conflicts, sizes, default_pagesize, default_color)) { if (errno != 0) debug_printf("Unable to create PPD file: %s\n", strerror(errno)); else debug_printf("Unable to create PPD file: %s\n", ppdgenerator_msg); p->status = STATUS_DISAPPEARED; current_time = time(NULL); p->timeout = current_time + TIMEOUT_IMMEDIATELY; goto cannot_create; } else { debug_printf("PPD generation successful: %s\n", ppdgenerator_msg); debug_printf("Created temporary PPD file: %s\n", buffer); ppdfile = strdup(buffer); } } } if (num_cluster_printers != 1) { if (default_pagesize != NULL) { free(default_pagesize); default_pagesize = NULL; } if (make_model != NULL) { free(make_model); make_model = NULL; } if (conflicts != NULL) { cupsArrayDelete(conflicts); conflicts = NULL; } if (printer_attributes != NULL) { ippDelete(printer_attributes); printer_attributes = NULL; } if (sizes != NULL) { cupsArrayDelete(sizes); sizes = NULL; } } } else { /* Device URI: using implicitclass backend for IPP network printer */ httpAssembleURI(HTTP_URI_CODING_ALL, device_uri, sizeof(device_uri), "implicitclass", NULL, p->queue_name, 0, NULL); if (strlen(device_uri) > HTTP_MAX_URI-1) device_uri[HTTP_MAX_URI-1] = '\0'; debug_printf("Print queue %s is for an IPP network printer, using implicitclass backend for the printer: %s\n", p->queue_name, device_uri); } /* PPD readily available */ if (ppdfile) { debug_printf("Using PPD %s for queue %s.\n", ppdfile, p->queue_name); loadedppd = ppdfile; } if (loadedppd) { if ((ppd = ppdOpenFile(loadedppd)) == NULL) { int linenum; /* Line number of error */ ppd_status_t status = ppdLastError(&linenum); debug_printf("Unable to open PPD \"%s\": %s on line %d.", loadedppd, ppdErrorString(status), linenum); current_time = time(NULL); p->timeout = current_time + TIMEOUT_RETRY; p->no_autosave = 0; unlink(loadedppd); break; } ppdMarkDefaults(ppd); cupsMarkOptions(ppd, p->num_options, p->options); if ((out = cupsTempFile2(buf, sizeof(buf))) == NULL) { debug_printf("Unable to create temporary file!\n"); current_time = time(NULL); p->timeout = current_time + TIMEOUT_RETRY; p->no_autosave = 0; ppdClose(ppd); ppd = NULL; unlink(loadedppd); break; } if ((in = cupsFileOpen(loadedppd, "r")) == NULL) { debug_printf("Unable to open the downloaded PPD file!\n"); current_time = time(NULL); p->timeout = current_time + TIMEOUT_RETRY; p->no_autosave = 0; cupsFileClose(out); ppdClose(ppd); ppd = NULL; unlink(loadedppd); break; } debug_printf("Editing PPD file %s for printer %s, setting the option defaults of the previous cups-browsed session%s, saving the resulting PPD in %s.\n", loadedppd, p->queue_name, " and doing client-side filtering of the job" , buf); ap_remote_queue_id_line_inserted = 0; while (cupsFileGets(in, line, sizeof(line))) { if (!strncmp(line, "*Default", 8)) { strncpy(keyword, line + 8, sizeof(keyword) - 1); if ((strlen(line) + 8) > 1023) keyword[1023] = '\0'; for (keyptr = keyword; *keyptr; keyptr ++) if (*keyptr == ':' || isspace(*keyptr & 255)) break; *keyptr++ = '\0'; while (isspace(*keyptr & 255)) keyptr ++; if (!strcmp(keyword, "PageRegion") || !strcmp(keyword, "PageSize") || !strcmp(keyword, "PaperDimension") || !strcmp(keyword, "ImageableArea")) { if ((choice = ppdFindMarkedChoice(ppd, "PageSize")) == NULL) choice = ppdFindMarkedChoice(ppd, "PageRegion"); } else choice = ppdFindMarkedChoice(ppd, keyword); if (choice && strcmp(choice->choice, keyptr)) { if (strcmp(choice->choice, "Custom")) cupsFilePrintf(out, "*Default%s: %s\n", keyword, choice->choice); else if ((customval = cupsGetOption(keyword, p->num_options, p->options)) != NULL) cupsFilePrintf(out, "*Default%s: %s\n", keyword, customval); else cupsFilePrintf(out, "%s\n", line); } else cupsFilePrintf(out, "%s\n", line); } else if (strncmp(line, "*End", 4)) { /* Write an "APRemoteQueueID" line to make this queue marked as remote printer by CUPS */ if (p->netprinter == 0 && strncmp(line, "*%", 2) && strncmp(line, "*PPD-Adobe:", 11) && ap_remote_queue_id_line_inserted == 0 && !AllowResharingRemoteCUPSPrinters) { ap_remote_queue_id_line_inserted = 1; cupsFilePrintf(out, "*APRemoteQueueID: \"\"\n"); } /* Simply write out the line as we read it */ cupsFilePrintf(out, "%s\n", line); } /* Save the NickName of the PPD to check whether external manipulations of the print queue have replaced the PPD. Check whether nickname is defined too */ if (!strncmp(line, "*NickName:", 10) && p->nickname == NULL) { char *ptr = NULL; char *end_ptr = NULL; int nickname_len = 0; ptr = strchr(line, '"'); if (ptr == NULL) { debug_printf("Malformed *Nickname directive in PPD - no double quote in line.\n"); continue; } ptr ++; end_ptr = strchr(ptr, '"'); if (end_ptr == NULL) { debug_printf("Malformed *Nickname directive in PPD - no ending double quote\n"); continue; } /* both pointers are null terminated, because cupsFileGets() puts * a null terminator into returned buffer with one line * here as 'line' array) and those two pointers points on two places * in the 'line' array. */ nickname_len = strlen(ptr) - strlen(end_ptr); if (nickname_len == 0) { debug_printf("Malformed *Nickname directive in PPD - empty nickname.\n"); continue; } /* alloc one more space for null terminator, calloc() will initialize * it to null automatically, so then we only copy a string with 'nickname_len' * length to get a proper null terminated p->nickname. */ p->nickname = (char*)calloc(nickname_len + 1, sizeof(char)); if (p->nickname != NULL) strncpy(p->nickname, ptr, nickname_len); } } cupsFilePrintf(out,"*cupsFilter2: \"application/vnd.cups-pdf application/pdf 0 -\"\n"); cupsFileClose(in); cupsFileClose(out); ppdClose(ppd); ppd = NULL; unlink(loadedppd); loadedppd = NULL; if (ppdfile) { free(ppdfile); ppdfile = NULL; } ppdfile = strdup(buf); } /* Create a new CUPS queue or modify the existing queue */ request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); /* Default user */ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); /* Queue should be enabled ... */ ippAddInteger(request, IPP_TAG_PRINTER, IPP_TAG_ENUM, "printer-state", IPP_PRINTER_IDLE); /* ... and accepting jobs */ ippAddBoolean(request, IPP_TAG_PRINTER, "printer-is-accepting-jobs", 1); num_options = 0; options = NULL; /* Device URI: ipp(s)://:631/printers/ OR implicitclass:/// */ num_options = cupsAddOption("device-uri", device_uri, num_options, &options); /* Option cups-browsed=true, marking that we have created this queue */ num_options = cupsAddOption(CUPS_BROWSED_MARK "-default", "true", num_options, &options); /* Default option settings from printer entry */ for (i = 0; i < p->num_options; i ++) if (strcasecmp(p->options[i].name, "printer-is-shared")) num_options = cupsAddOption(p->options[i].name, p->options[i].value, num_options, &options); /* Encode option list into IPP attributes */ cupsEncodeOptions2(request, num_options, options, IPP_TAG_OPERATION); cupsEncodeOptions2(request, num_options, options, IPP_TAG_PRINTER); /* Do it */ if (ppdfile) { debug_printf("Non-raw queue %s with PPD file: %s\n", p->queue_name, ppdfile); ippDelete(cupsDoFileRequest(http, request, "/admin/", ppdfile)); want_raw = 0; unlink(ppdfile); free(ppdfile); ppdfile = NULL; } else if (ifscript) { debug_printf("Non-raw queue %s with interface script: %s\n", p->queue_name, ifscript); ippDelete(cupsDoFileRequest(http, request, "/admin/", ifscript)); want_raw = 0; unlink(ifscript); free(ifscript); ifscript = NULL; } else { if (p->netprinter == 0) { debug_printf("Raw queue %s\n", p->queue_name); want_raw = 1; } else { debug_printf("Queue %s keeping its current PPD file/interface script\n", p->queue_name); want_raw = 0; } ippDelete(cupsDoRequest(http, request, "/admin/")); } cupsFreeOptions(num_options, options); cups_queues_updated ++; if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) { debug_printf("Unable to create/modify CUPS queue (%s)!\n", cupsLastErrorString()); current_time = time(NULL); p->timeout = current_time + TIMEOUT_RETRY; p->no_autosave = 0; break; } /* Do not share a queue which serves only to point to a remote CUPS printer We do this in a seperate IPP request as on newer CUPS versions we get an error when changing the printer-is-shared bit on a queue pointing to a remote CUPS printer, this way we assure all other settings be applied amd when setting the printer-is-shared to false amd this errors, we can safely ignore the error as on queues pointing to remote CUPS printers the bit is set to false by default (these printers are never shared) If our printer is an IPP network printer and not a CUPS queue, we keep track of whether the user has changed the printer-is-shared bit and recover this setting. The default setting for a new queue is configurable via the NewIPPPrinterQueuesShared directive in cups-browsed.conf */ request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); num_options = 0; options = NULL; if (p->netprinter == 1 && (val = cupsGetOption("printer-is-shared", p->num_options, p->options)) != NULL) { num_options = cupsAddOption("printer-is-shared", val, num_options, &options); debug_printf("Setting printer-is-shared bit to %s.\n", val); } else if (p->netprinter == 1 && NewIPPPrinterQueuesShared) { num_options = cupsAddOption("printer-is-shared", "true", num_options, &options); debug_printf("Setting printer-is-shared bit.\n"); } else if (NewBrowsePollQueuesShared && (val = cupsGetOption("printer-to-be-shared", p->num_options, p->options)) != NULL) { num_options = cupsAddOption("printer-is-shared", "true", num_options, &options); debug_printf("Setting printer-is-shared bit.\n"); } else { num_options = cupsAddOption("printer-is-shared", "false", num_options, &options); debug_printf("Unsetting printer-is-shared bit.\n"); } cupsEncodeOptions2(request, num_options, options, IPP_TAG_OPERATION); cupsEncodeOptions2(request, num_options, options, IPP_TAG_PRINTER); /* * Do IPP request for printer-is-shared option only when we have * network printer or if we have remote CUPS queue, do IPP request * only if we have CUPS older than 2.2. */ if (p->netprinter != 0 || !HAVE_CUPS_2_2 || AllowResharingRemoteCUPSPrinters) ippDelete(cupsDoRequest(http, request, "/admin/")); else ippDelete(request); cupsFreeOptions(num_options, options); if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) debug_printf("Unable to modify the printer-is-shared bit (%s)!\n", cupsLastErrorString()); /* If we are about to create a raw queue or turn a non-raw queue into a raw one, we apply the "ppd-name=raw" option to remove any existing PPD file assigned to the queue. Also here we do a separate IPP request as it errors in some cases. */ if (want_raw) { debug_printf("Removing local PPD file for printer %s\n", p->queue_name); request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); num_options = 0; options = NULL; num_options = cupsAddOption("ppd-name", "raw", num_options, &options); cupsEncodeOptions2(request, num_options, options, IPP_TAG_OPERATION); cupsEncodeOptions2(request, num_options, options, IPP_TAG_PRINTER); ippDelete(cupsDoRequest(http, request, "/admin/")); cupsFreeOptions(num_options, options); if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) debug_printf("Unable to remove PPD file from the print queue (%s)!\n", cupsLastErrorString()); } /* If this queue was the default printer in its previous life, make it the default printer again. */ queue_creation_handle_default(p->queue_name); /* If cups-browsed or a failed backend has disabled this queue, re-enable it. */ if ((disabled_str = is_disabled(p->queue_name, "cups-browsed")) != NULL) { enable_printer(p->queue_name); free(disabled_str); } else if ((disabled_str = is_disabled(p->queue_name, "Printer stopped due to backend errors")) != NULL) { enable_printer(p->queue_name); free(disabled_str); } p->status = STATUS_CONFIRMED; if (p->is_legacy) { p->timeout = time(NULL) + BrowseTimeout; debug_printf("starting BrowseTimeout timer for %s (%ds)\n", p->queue_name, BrowseTimeout); } else p->timeout = (time_t) -1; /* Check if an HTTP timeout happened during the print queue creation If it does - increment p->timeouted and set status to TO_BE_CREATED because the creation can fall through the process, have state changed to STATUS_CONFIRMED and experience the timeout */ /* If no timeout has happened, clear p->timeouted */ if (timeout_reached == 1) { debug_printf("Timeout happened during creation of the queue %s.\n", p->queue_name); p->timeouted ++; debug_printf("The queue %s already timeouted %d times in a row.\n", p->queue_name, p->timeouted); p->status = STATUS_TO_BE_CREATED; p->timeout = current_time + TIMEOUT_RETRY; } else if (p->timeouted != 0) { debug_printf("Creating the queue %s went smoothly after %d timeouts.\n", p->queue_name, p->timeouted); p->timeouted = 0; } p->no_autosave = 0; break; case STATUS_CONFIRMED: /* Only act if the timeout has passed */ if (p->timeout > current_time) break; if (p->is_legacy) { /* Remove a queue based on a legacy CUPS broadcast when the broadcast timeout expires without a new broadcast of this queue from the server */ remove_printer_entry(p); } else p->timeout = (time_t) -1; break; } } /* If we have printer entries which we did not treat yet because of update_cups_queues_max_per_call we push their timeouts by the value of pause_between_cups_queue_updates into the future, so that they only get worked on then. Also printer entries which are scheduled in a time less than the value of pause_between_cups_queue_updates will be pushed, so that update_cups_queues will run the next time only after this interval */ if (p && !in_shutdown) for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) if (p->timeout <= current_time + pause_between_cups_queue_updates) p->timeout = current_time + pause_between_cups_queue_updates; cannot_create: if (printer_attributes != NULL && num_cluster_printers != 1) ippDelete(printer_attributes); if (default_pagesize != NULL && num_cluster_printers != 1) free(default_pagesize); if (conflicts != NULL && num_cluster_printers != 1) cupsArrayDelete(conflicts); if (make_model != NULL && num_cluster_printers != 1) free(make_model); if (sizes != NULL && num_cluster_printers != 1) cupsArrayDelete(sizes); if (p && !in_shutdown) remove_printer_entry(p); log_all_printers(); if (in_shutdown == 0) recheck_timer (); /* Don't run this callback again */ return FALSE; } static void recheck_timer (void) { remote_printer_t *p; time_t timeout = (time_t) -1; time_t now = time(NULL); if (!gmainloop) return; for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) if (p->timeout == (time_t) -1) continue; else if (now > p->timeout) { timeout = 0; break; } else if (timeout == (time_t) -1 || p->timeout - now < timeout) timeout = p->timeout - now; if (queues_timer_id) g_source_remove (queues_timer_id); if (timeout != (time_t) -1) { debug_printf("checking queues in %ds\n", timeout); queues_timer_id = g_timeout_add_seconds (timeout, update_cups_queues, NULL); } else { debug_printf("listening\n"); queues_timer_id = 0; } } static gboolean matched_filters (const char *queue_name, const char *host, uint16_t port, const char *service_name, const char *domain, void *txt) { browse_filter_t *filter; const char *property = NULL; char buf[10]; #ifdef HAVE_AVAHI AvahiStringList *entry = NULL; char *key = NULL, *value = NULL; #endif /* HAVE_AVAHI */ debug_printf("Matching printer \"%s\" with properties Host = \"%s\", Port = %d, Service Name = \"%s\", Domain = \"%s\" with the BrowseFilter lines in cups-browsed.conf\n", queue_name, host, port, service_name, domain); /* Go through all BrowseFilter lines and stop if one line does not match, rejecting this printer */ for (filter = cupsArrayFirst (browsefilter); filter; filter = cupsArrayNext (browsefilter)) { debug_printf("Matching with line \"BrowseFilter %s%s%s %s\"", (filter->sense == FILTER_NOT_MATCH ? "NOT " : ""), (filter->regexp && !filter->cregexp ? "EXACT " : ""), filter->field, (filter->regexp ? filter->regexp : "")); #ifdef HAVE_AVAHI /* Go through the TXT record to see whether this rule applies to a field in there */ if (txt) { entry = avahi_string_list_find((AvahiStringList *)txt, filter->field); if (entry) { avahi_string_list_get_pair(entry, &key, &value, NULL); if (key) { debug_printf(", TXT record entry: %s = %s", key, (value ? value : "")); if (filter->regexp) { /* match regexp */ if (!value) value = strdup(""); if ((filter->cregexp && regexec(filter->cregexp, value, 0, NULL, 0) == 0) || (!filter->cregexp && !strcasecmp(filter->regexp, value))) { if (filter->sense == FILTER_NOT_MATCH) { avahi_free(key); avahi_free(value); goto filter_failed; } } else { if (filter->sense == FILTER_MATCH) { avahi_free(key); avahi_free(value); goto filter_failed; } } } else { /* match boolean value */ if (filter->sense == FILTER_MATCH) { if (!value || strcasecmp(value, "T")) { avahi_free(key); avahi_free(value); goto filter_failed; } } else { if (value && !strcasecmp(value, "T")) { avahi_free(key); avahi_free(value); goto filter_failed; } } } } avahi_free(key); avahi_free(value); goto filter_matched; } } #endif /* HAVE_AVAHI */ /* Does one of the properties outside the TXT record match? */ property = buf; buf[0] = '\0'; if (!strcasecmp(filter->field, "Name") || !strcasecmp(filter->field, "Printer") || !strcasecmp(filter->field, "PrinterName") || !strcasecmp(filter->field, "Queue") || !strcasecmp(filter->field, "QueueName")) { if (queue_name) property = queue_name; } else if (!strcasecmp(filter->field, "Host") || !strcasecmp(filter->field, "HostName") || !strcasecmp(filter->field, "RemoteHost") || !strcasecmp(filter->field, "RemoteHostName") || !strcasecmp(filter->field, "Server") || !strcasecmp(filter->field, "ServerName")) { if (host) property = host; } else if (!strcasecmp(filter->field, "Port")) { if (port) snprintf(buf, sizeof(buf), "%d", port); } else if (!strcasecmp(filter->field, "Service") || !strcasecmp(filter->field, "ServiceName")) { if (service_name) property = service_name; } else if (!strcasecmp(filter->field, "Domain")) { if (domain) property = domain; } else property = NULL; if (property) { if (!filter->regexp) filter->regexp = ""; if ((filter->cregexp && regexec(filter->cregexp, property, 0, NULL, 0) == 0) || (!filter->cregexp && !strcasecmp(filter->regexp, property))) { if (filter->sense == FILTER_NOT_MATCH) goto filter_failed; } else { if (filter->sense == FILTER_MATCH) goto filter_failed; } goto filter_matched; } debug_printf(": Field not found --> SKIPPED\n"); continue; filter_matched: debug_printf(" --> MATCHED\n"); } /* All BrowseFilter lines matching, accept this printer */ debug_printf("All BrowseFilter lines matched or skipped, accepting printer %s\n", queue_name); return TRUE; filter_failed: debug_printf(" --> FAILED\n"); debug_printf("One BrowseFilter line did not match, ignoring printer %s\n", queue_name); return FALSE; } static gboolean update_netifs (gpointer data) { struct ifaddrs *ifaddr, *ifa; netif_t *iface, *iface2; int i, add_to_netifs, addr_size, dupe, if_found, addr_found; char *host, buf[HTTP_MAX_HOST], *p, list[65536], *l; debug_printf("update_netifs() in THREAD %ld\n", pthread_self()); update_netifs_sourceid = 0; if (getifaddrs (&ifaddr) == -1) { debug_printf("unable to get interface addresses: %s\n", strerror (errno)); return FALSE; } while ((iface = cupsArrayFirst (netifs)) != NULL) { cupsArrayRemove (netifs, iface); free (iface->address); free (iface); } while ((host = cupsArrayFirst (local_hostnames)) != NULL) { cupsArrayRemove (local_hostnames, host); free (host); } memset(list, 0, sizeof(list)); snprintf(list, sizeof(list) - 1, "Network interfaces: "); l = list + strlen(list); for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { if_found = 0; addr_found = 0; netif_t *iface; add_to_netifs = 1; if (ifa->ifa_addr == NULL) continue; if (ifa->ifa_broadaddr == NULL) add_to_netifs = 0; if (ifa->ifa_flags & IFF_LOOPBACK) add_to_netifs = 0; if (!(ifa->ifa_flags & IFF_BROADCAST)) add_to_netifs = 0; if (ifa->ifa_addr->sa_family == AF_INET) addr_size = sizeof (struct sockaddr_in); else if (ifa->ifa_addr->sa_family == AF_INET6) addr_size = sizeof (struct sockaddr_in6); else addr_size = 0; if (addr_size) { if (strlen(list) + strlen(ifa->ifa_name) + 1 <= sizeof(list)) { snprintf(l, sizeof(list) - strlen(list) - 1, "%s", ifa->ifa_name); l = list + strlen(list); if_found = 1; } for (i = 0; i <= 1; i ++) if (getnameinfo (ifa->ifa_addr, addr_size, buf, HTTP_MAX_HOST, NULL, 0, i == 0 ? NI_NUMERICHOST : NI_NAMEREQD) == 0) if (buf[0]) { /* Cut off "%..." from IPv6 IP addresses */ if (ifa->ifa_addr->sa_family == AF_INET6 && i == 0 && (p = strchr(buf, '%')) != NULL) *p = '\0'; /* discard if we already have this name or address */ dupe = 0; for (host = (char *)cupsArrayFirst (local_hostnames); host != NULL; host = (char *)cupsArrayNext (local_hostnames)) if (strcasecmp(buf, host) == 0) { dupe = 1; break; } if (dupe == 0) { cupsArrayAdd (local_hostnames, strdup(buf)); if (addr_found == 1 && strlen(list) + 3 <= sizeof(list)) { snprintf(l, sizeof(list) - strlen(list) - 1, ", "); l = list + strlen(list); } if (addr_found == 0 && strlen(list) + 3 <= sizeof(list)) { snprintf(l, sizeof(list) - strlen(list) - 1, " ("); l = list + strlen(list); addr_found = 1; } if (strlen(list) + strlen(buf) + 1 <= sizeof(list)) { snprintf(l, sizeof(list) - strlen(list) - 1, "%s", buf); l = list + strlen(list); } } } } if (add_to_netifs == 0) goto done; iface = malloc (sizeof (netif_t)); if (iface == NULL) { debug_printf ("malloc failure\n"); exit (1); } iface->address = malloc (HTTP_MAX_HOST); if (iface->address == NULL) { free (iface); debug_printf ("malloc failure\n"); exit (1); } iface->address[0] = '\0'; switch (ifa->ifa_addr->sa_family) { case AF_INET: /* copy broadcast addr/fill in port first to faciliate dupe compares */ memcpy (&iface->broadcast, ifa->ifa_broadaddr, sizeof (struct sockaddr_in)); iface->broadcast.ipv4.sin_port = htons (BrowsePort); /* discard if we already have an interface sharing the broadcast address */ dupe = 0; for (iface2 = (netif_t *)cupsArrayFirst (netifs); iface2 != NULL; iface2 = (netif_t *)cupsArrayNext (netifs)) { if (memcmp(&iface2->broadcast, &iface->broadcast, sizeof(struct sockaddr_in)) == 0) { dupe = 1; break; } } if (dupe) break; getnameinfo (ifa->ifa_addr, sizeof (struct sockaddr_in), iface->address, HTTP_MAX_HOST, NULL, 0, NI_NUMERICHOST); break; case AF_INET6: if (IN6_IS_ADDR_LINKLOCAL (&((struct sockaddr_in6 *)(ifa->ifa_addr)) ->sin6_addr)) break; /* see above for order */ memcpy (&iface->broadcast, ifa->ifa_broadaddr, sizeof (struct sockaddr_in6)); iface->broadcast.ipv6.sin6_port = htons (BrowsePort); /* discard alias addresses (identical broadcast) */ dupe = 0; for (iface2 = (netif_t *)cupsArrayFirst (netifs); iface2 != NULL; iface2 = (netif_t *)cupsArrayNext (netifs)) { if (memcmp(&iface2->broadcast, ifa->ifa_broadaddr, sizeof(struct sockaddr_in6)) == 0) { dupe = 1; break; } } if (dupe) break; getnameinfo (ifa->ifa_addr, sizeof (struct sockaddr_in6), iface->address, HTTP_MAX_HOST, NULL, 0, NI_NUMERICHOST); break; } if (iface->address[0]) { cupsArrayAdd (netifs, iface); if (if_found == 1) { if (addr_found == 1 && strlen(list) + 3 <= sizeof(list)) { snprintf(l, sizeof(list) - strlen(list) - 1, ", "); l = list + strlen(list); } if (addr_found == 0 && strlen(list) + 3 <= sizeof(list)) { snprintf(l, sizeof(list) - strlen(list) - 1, " ("); l = list + strlen(list); addr_found = 1; } if (strlen(list) + strlen(iface->address) + 2 <= sizeof(list)) { snprintf(l, sizeof(list) - strlen(list) - 1, "%s*", iface->address); l = list + strlen(list); } } } else { free (iface->address); free (iface); } done: if (if_found == 1) { if (addr_found == 1 && strlen(list) + 2 <= sizeof(list)) { snprintf(l, sizeof(list) - strlen(list) - 1, ")"); l = list + strlen(list); } if (strlen(list) + 3 <= sizeof(list)) { snprintf(l, sizeof(list) - strlen(list) - 1, ", "); l = list + strlen(list); } } } if ((l = strrchr(list, ')')) != NULL) { if (strlen(list) + 2 <= sizeof(list)) *(l + 1) = '\0'; } else { if (strlen(list) + 5 <= sizeof(list)) snprintf(list + strlen(list), sizeof(list) - strlen(list) - 1, "None"); } debug_printf("%s\n", list); freeifaddrs (ifaddr); /* If run as a timeout, don't run it again. */ return FALSE; } int is_local_hostname(const char *host_name) { char *host; if (host_name == NULL) return 0; for (host = (char *)cupsArrayFirst (local_hostnames); host != NULL; host = (char *)cupsArrayNext (local_hostnames)) if (strncasecmp(host_name, host, strlen(host)) == 0 && (strlen(host_name) == strlen(host) || (strlen(host_name) > strlen(host) && (strcasecmp(host_name + strlen(host), ".local") == 0 || strcasecmp(host_name + strlen(host), ".local.") == 0)))) return 1; return 0; } static remote_printer_t * examine_discovered_printer_record(const char *host, const char *ip, uint16_t port, char *resource, const char *service_name, const char *location, const char *info, const char *type, const char *domain, const char *interface, int family, void *txt) { char uri[HTTP_MAX_URI]; char *remote_host = NULL, *pdl = NULL, *make_model = NULL; int color = 1, duplex = 1; #ifdef HAVE_AVAHI char *fields[] = { "product", "usb_MDL", "ty", NULL }, **f; AvahiStringList *entry = NULL; char *key = NULL, *value = NULL; char *note_value = NULL; char service_host_name[1024]; #endif /* HAVE_AVAHI */ remote_printer_t *p = NULL, key_rec; char *local_queue_name = NULL; int is_cups_queue; int raw_queue = 0; char *ptr; if (!host || !resource || !service_name || !location || !info || !type || !domain) { debug_printf("ERROR: examine_discovered_printer_record(): Input value missing!\n"); return NULL; } is_cups_queue = 0; memset(uri, 0, sizeof(uri)); /* Find the remote host name. Used in constructing backup queue name, so need to sanitize. strdup() is called inside remove_bad_chars() and result is free()-able. */ remote_host = remove_bad_chars(host, 1); /* If we only want to create queues for printers for which CUPS does not already auto-create queues, we check here whether we can skip this printer */ if (OnlyUnsupportedByCUPS) { if (g_hash_table_find (cups_supported_remote_printers, local_printer_service_name_matches, (gpointer *)service_name)) { /* Found a DNS-SD-discovered CUPS-supported printer whose service name matches our discovered printer */ debug_printf("Printer with DNS-SD service name \"%s\" does not need to be covered by us as it is already supported by CUPS, skipping.\n", service_name); goto fail; } } #ifdef HAVE_AVAHI if (txt) { /* Find make and model by the TXT record */ for (f = fields; *f; f ++) { entry = avahi_string_list_find((AvahiStringList *)txt, *f); if (entry) { avahi_string_list_get_pair(entry, &key, &value, NULL); if (key && value && !strcasecmp(key, *f) && strlen(value) >= 3) { if (!strcasecmp(key, "product")) { make_model = strdup(value + 1); make_model[strlen(make_model) - 1] = '\0'; } else make_model = strdup(value); avahi_free(key); avahi_free(value); break; } avahi_free(key); avahi_free(value); } } /* Check by the printer-type TXT field whether the discovered printer is a CUPS queue */ entry = avahi_string_list_find((AvahiStringList *)txt, "printer-type"); if (entry) { avahi_string_list_get_pair(entry, &key, &value, NULL); if (key && value && strlen(value) > 1 && !strcasecmp(key, "printer-type") && value[0] == '0' && value[1] == 'x') { is_cups_queue = 1; } avahi_free(key); avahi_free(value); } } #else /* Check by the resource whether the discovered printer is a CUPS queue */ if (!strncasecmp(resource, "printers/", 9) || !strncasecmp(resource, "classes/", 8)) /* This is a remote CUPS queue or class */ is_cups_queue = 1; #endif /* HAVE_AVAHI */ /* If we do not have a TXT record the printer was not discovered via DNS-SD but via CUPS legacy or LDAP, so it is a remote CUPS queue and not an IPP network printer. */ if (txt == NULL) is_cups_queue = 1; if (is_cups_queue) debug_printf("Found CUPS queue/class: %s on host %s.\n", strrchr(resource, '/') + 1, remote_host); #ifdef HAVE_AVAHI if (is_cups_queue) { /* If the remote queue has a PPD file, the "product" field of the TXT record is populated. If it has no PPD file the remote queue is a raw queue and so we do not know enough about the printer behind it for auto-creating a local queue pointing to it. */ if (txt) { entry = avahi_string_list_find((AvahiStringList *)txt, "product"); if (entry) { avahi_string_list_get_pair(entry, &key, &value, NULL); if (!key || !value || strcasecmp(key, "product") || value[0] != '(' || value[strlen(value) - 1] != ')') { raw_queue = 1; } avahi_free(key); avahi_free(value); } else raw_queue = 1; } else if (domain && domain[0] != '\0') raw_queue = 1; if (raw_queue && CreateRemoteRawPrinterQueues == 0) { /* The remote CUPS queue is raw, ignore it */ debug_printf("Remote DNS-SD-advertised CUPS queue %s on host %s is raw, ignored.\n", strrchr(resource, '/') + 1, remote_host); free (remote_host); if (make_model) free (make_model); return NULL; } } else { if (txt) { /* Find out which PDLs the printer understands */ entry = avahi_string_list_find((AvahiStringList *)txt, "pdl"); if (entry) { avahi_string_list_get_pair(entry, &key, &value, NULL); if (key && value && !strcasecmp(key, "pdl") && strlen(value) >= 3) { pdl = remove_bad_chars(value, 1); } avahi_free(key); avahi_free(value); } /* Find out if we have a color printer */ entry = avahi_string_list_find((AvahiStringList *)txt, "Color"); if (entry) { avahi_string_list_get_pair(entry, &key, &value, NULL); if (key && value && !strcasecmp(key, "Color")) { if (!strcasecmp(value, "T")) color = 1; if (!strcasecmp(value, "F")) color = 0; } avahi_free(key); avahi_free(value); } /* Find out if we have a duplex printer */ entry = avahi_string_list_find((AvahiStringList *)txt, "Duplex"); if (entry) { avahi_string_list_get_pair(entry, &key, &value, NULL); if (key && value && !strcasecmp(key, "Duplex")) { if (!strcasecmp(value, "T")) duplex = 1; if (!strcasecmp(value, "F")) duplex = 0; } avahi_free(key); avahi_free(value); } } } /* Extract location from DNS-SD TXT record's "note" field */ if (location[0] == '\0') { if (txt) { entry = avahi_string_list_find((AvahiStringList *)txt, "note"); if (entry) { avahi_string_list_get_pair(entry, &key, ¬e_value, NULL); if (key && note_value && !strcasecmp(key, "note")) { debug_printf("examine_discovered_printer_record: TXT.note: |%s|\n", note_value); /* !! */ location = note_value; } avahi_free(key); /* don't avahi_free(note_value) here! */ } } } /* A NULL location is only passed in from resolve_callback(), which is HAVE_AVAHI */ #endif /* HAVE_AVAHI */ /* Determine the device URI of the remote printer */ #ifdef HAVE_AVAHI if (txt && DNSSDBasedDeviceURIs) { /* Printer is DNS-SD-discovered, so we can give a DNS-SD-service-name-based device URI to it (only if DNSSDBasedDeviceURIs config option is set) */ snprintf(service_host_name, sizeof(service_host_name), "%s.%s.%s", service_name, type, domain); httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri) - 1, (strcasestr(type, "_ipps") ? "ipps" : "ipp"), NULL, service_host_name, 0, "/%s", (is_cups_queue ? "cups" : "")); } else #endif /* HAVE_AVAHI */ /* Printer is discovered via legacy CUPS or LDAP, so we have to give a IP-based/host-name-based URI to it ( or for DNS-SD-discovered printers if DNSSDBasedDeviceURIs config option is not set) */ httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri) - 1, (strcasestr(type, "_ipps") ? "ipps" : "ipp"), NULL, (ip != NULL ? ip : host), port, "/%s", resource); /* Determine the queue name */ local_queue_name = get_local_queue_name(service_name, make_model, resource, remote_host, &is_cups_queue, NULL); if (local_queue_name == NULL) goto fail; if (!matched_filters (local_queue_name, remote_host, port, service_name, domain, txt)) { debug_printf("Printer %s does not match BrowseFilter lines in cups-browsed.conf, printer ignored.\n", local_queue_name); goto fail; } /* Update network interface info if we were discovered by LDAP or legacy CUPS, needed for the is_local_hostname() function calls. During DNS-SD discovery the update is already done by the Avahi event handler function. */ if (type == NULL || type[0] == '\0') update_netifs(NULL); /* Check if we have already created a queue for the discovered printer */ for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) if (!strcasecmp(p->queue_name, local_queue_name) && (p->host[0] == '\0' || p->status == STATUS_UNCONFIRMED || p->status == STATUS_DISAPPEARED || ((!strcasecmp(p->host, remote_host) || (is_local_hostname(p->host) && is_local_hostname(remote_host))) && (p->port == port || (p->port == 631 && port == 443) || (p->port == 443 && port == 631)) && (txt || (strlen(p->uri) - strlen(resource) > 0 && !strcasecmp(p->uri + strlen(p->uri) - strlen(resource), resource)))))) break; /* Is there a local queue with the same URI as the remote queue? */ if (!p) { memset(&key_rec, 0, sizeof(key_rec)); key_rec.uri = uri; key_rec.host = remote_host; key_rec.port = port; key_rec.resource = resource; key_rec.service_name = (char *)service_name; key_rec.type = (char *)type; key_rec.domain = (char *)domain; if (g_hash_table_find (local_printers, local_printer_is_same_device, &key_rec)) { /* Found a local queue with the same URI as our discovered printer would get, so ignore this remote printer */ debug_printf("Printer with URI %s (or IPP/IPPS equivalent) already exists, printer ignored.\n", uri); goto fail; } /* We need to create a local queue pointing to the discovered printer */ p = create_remote_printer_entry (local_queue_name, location, info, uri, remote_host, ip, port, resource, service_name ? service_name : "", type, domain, interface, family, pdl, color, duplex, make_model, is_cups_queue); } else { debug_printf("Entry for %s (URI: %s) already exists.\n", p->queue_name, p->uri); /* We have already created a local queue, check whether the discovered service allows us to upgrade the queue to IPPS or whether the URI part after ipp(s):// has changed, or whether the discovered queue is discovered via DNS-SD having more info in contrary to the existing being discovered by legacy CUPS or LDAP */ int downgrade = 0, upgrade = 0; /* Get first element of array of interfaces on which this printer got already discovered, as this one is "lo" when it already got discovered through the loopback interface (preferred interface) */ ipp_discovery_t *ippdis = cupsArrayFirst(p->ipp_discoveries); /* Force upgrade if the found entry is marked unconfirmed or disappeared */ if (p->status == STATUS_UNCONFIRMED || p->status == STATUS_DISAPPEARED) { upgrade = 1; debug_printf("Replacing printer entry %s (Host: %s, Port: %d) as it was marked %s. New URI: %s\n", p->queue_name, remote_host, port, (p->status == STATUS_UNCONFIRMED ? "unconfirmed" : "disappeared"), uri); /* Check if there is a downgrade */ /* IPPS -> IPP */ } else if ((ptr = strcasestr(type, "_ipp")) != NULL && *(ptr + 4) != 's' && !strncasecmp(p->uri, "ipps:", 5)) { downgrade = 1; debug_printf("Printer %s: New discovered service from host %s, port %d, URI %s is only IPP, we have already IPPS, skipping\n", p->queue_name, remote_host, port, uri); /* "lo" -> Any non-"lo" interface */ } else if (strcasecmp(interface, "lo") && ippdis && !strcasecmp(ippdis->interface, "lo")) { downgrade = 1; debug_printf("Printer %s: New discovered service from host %s, port %d, URI %s is from a non-loopback interface, we have already one from the loopback interface, skipping\n", p->queue_name, remote_host, port, uri); /* DNS-SD -> CUPS Legacy/LDAP */ } else if (p->domain != NULL && p->domain[0] != '\0' && (domain == NULL || domain[0] == '\0') && p->type != NULL && p->type[0] != '\0' && (type == NULL || type[0] == '\0')) { downgrade = 1; debug_printf("Printer %s: New discovered service from host %s, port %d, URI %s is only discovered via legacy CUPS or LDAP, we have already a DNS-SD-discovered one, skipping\n", p->queue_name, remote_host, port, uri); } if (downgrade == 0) { /* Check if there is an upgrade */ /* IPP -> IPPS */ if (strcasestr(type, "_ipps") && !strncasecmp(p->uri, "ipp:", 4)) { upgrade = 1; debug_printf("Upgrading printer %s (Host: %s, Port: %d) to IPPS. New URI: %s\n", p->queue_name, remote_host, port, uri); /* Any non-"lo" interface -> "lo" */ } else if (!strcasecmp(interface, "lo")) { upgrade = 1; debug_printf("Upgrading printer %s (Host: %s, Port: %d) to use loopback interface \"lo\". New URI: %s\n", p->queue_name, remote_host, port, uri); /* CUPS Legacy/LDAP -> DNS-SD */ } else if ((p->domain == NULL || p->domain[0] == '\0') && domain != NULL && domain[0] != '\0' && (p->type == NULL || p->type[0] == '\0') && type != NULL && type[0] != '\0') { upgrade = 1; debug_printf("Discovered printer %s (Host: %s, Port: %d, URI: %s) by DNS-SD now.\n", p->queue_name, remote_host, port, uri); } } /* Switch local queue over to this newly discovered service */ if (upgrade == 1) { /* Remove tiemout of legacy CUPS broadcasting */ if (domain != NULL && domain[0] != '\0' && type != NULL && type[0] != '\0' && p->is_legacy) { p->is_legacy = 0; if (p->status == STATUS_CONFIRMED) p->timeout = (time_t) -1; } free(p->queue_name); free(p->location); free(p->info); free(p->make_model); free(p->pdl); free(p->uri); free(p->host); free(p->ip); free(p->resource); free(p->service_name); free(p->type); free(p->domain); p->queue_name = strdup(local_queue_name); p->location = strdup(location); p->info = strdup(info); p->make_model = (make_model != NULL ? strdup(make_model) : NULL); p->pdl = (pdl != NULL ? strdup(pdl) : NULL); p->color = color; p->duplex = duplex; p->uri = strdup(uri); p->status = STATUS_TO_BE_CREATED; p->timeout = time(NULL) + TIMEOUT_IMMEDIATELY; p->host = strdup(remote_host); p->ip = (ip != NULL ? strdup(ip) : NULL); p->port = port; p->resource = strdup(resource); p->service_name = strdup(service_name); p->type = strdup(type); p->domain = strdup(domain); debug_printf("Switched over to newly discovered entry for this printer.\n"); } else debug_printf("Staying with previously discovered entry for this printer.\n"); /* Mark queue entry as confirmed if the entry is unconfirmed */ if (p->status == STATUS_UNCONFIRMED || p->status == STATUS_DISAPPEARED) { debug_printf("Marking entry for %s (URI: %s) as confirmed.\n", p->queue_name, p->uri); p->status = STATUS_CONFIRMED; if (p->is_legacy) { p->timeout = time(NULL) + BrowseTimeout; debug_printf("starting BrowseTimeout timer for %s (%ds)\n", p->queue_name, BrowseTimeout); } else p->timeout = (time_t) -1; /* If this queue was the default printer in its previous life, make it the default printer again. */ queue_creation_handle_default(p->queue_name); /* If this queue is disabled, re-enable it. */ enable_printer(p->queue_name); /* Record the options, to record any changes which happened while cups-browsed was not running */ record_printer_options(p->queue_name); } /* Gather extra info from our new discovery */ if (p->uri[0] == '\0') { free (p->uri); p->uri = strdup(uri); } if (p->location[0] == '\0') { free (p->location); p->location = strdup(location); } if (p->info[0] == '\0') { free (p->info); p->info = strdup(info); } if (p->make_model == NULL || p->make_model[0] == '\0') { if (p->make_model) free (p->make_model); p->make_model = (make_model != NULL ? strdup(make_model) : NULL); } if (p->pdl == NULL || p->pdl[0] == '\0') { if (p->pdl) free (p->pdl); p->pdl = (pdl != NULL ? strdup(pdl) : NULL); } p->color = color; p->duplex = duplex; if (p->host[0] == '\0') { free (p->host); p->host = strdup(remote_host); } if (p->ip == NULL || p->ip[0] == '\0') { if (p->ip) free (p->ip); p->ip = (ip != NULL ? strdup(ip) : NULL); } if (p->port == 0) p->port = port; if (p->service_name[0] == '\0' && service_name) { free (p->service_name); p->service_name = strdup(service_name); } if (p->resource[0] == '\0') { free (p->resource); p->resource = strdup(resource); } if (p->type[0] == '\0' && type) { free (p->type); p->type = strdup(type); } if (p->domain[0] == '\0' && domain) { free (p->domain); p->domain = strdup(domain); } if (domain != NULL && domain[0] != '\0' && type != NULL && type[0] != '\0') ipp_discoveries_add(p->ipp_discoveries, interface, type, family); p->netprinter = is_cups_queue ? 0 : 1; } fail: free (remote_host); free (pdl); free (make_model); free (local_queue_name); #ifdef HAVE_AVAHI if (note_value) avahi_free(note_value); #endif /* HAVE_AVAHI */ if (p) debug_printf("DNS-SD IDs: Service name: \"%s\", " "Service type: \"%s\", Domain: \"%s\"\n", p->service_name, p->type, p->domain); return p; } static gboolean allowed (struct sockaddr *srcaddr) { allow_t *allow; int i; gboolean server_allowed; allow_sense_t sense; if (browse_order == ORDER_DENY_ALLOW) /* BrowseOrder Deny,Allow: Allow server, then apply BrowseDeny lines, after that BrowseAllow lines */ server_allowed = TRUE; else /* BrowseOrder Allow,Deny: Deny server, then apply BrowseAllow lines, after that BrowseDeny lines */ server_allowed = FALSE; for (i = 0; i <= 1; i ++) { if (browse_order == ORDER_DENY_ALLOW) /* Treat BrowseDeny lines first, then BrowseAllow lines */ sense = (i == 0 ? ALLOW_DENY : ALLOW_ALLOW); else /* Treat BrowseAllow lines first, then BrowseDeny lines */ sense = (i == 0 ? ALLOW_ALLOW : ALLOW_DENY); if (server_allowed == (sense == ALLOW_ALLOW ? TRUE : FALSE)) continue; if (browseallow_all && sense == ALLOW_ALLOW) { server_allowed = TRUE; continue; } if (browsedeny_all && sense == ALLOW_DENY) { server_allowed = FALSE; continue; } for (allow = cupsArrayFirst (browseallow); allow; allow = cupsArrayNext (browseallow)) { if (allow->sense != sense) continue; switch (allow->type) { case ALLOW_INVALID: break; case ALLOW_IP: switch (srcaddr->sa_family) { case AF_INET: if (((struct sockaddr_in *) srcaddr)->sin_addr.s_addr == allow->addr.ipv4.sin_addr.s_addr) { server_allowed = (sense == ALLOW_ALLOW ? TRUE : FALSE); goto match; } break; case AF_INET6: if (!memcmp (&((struct sockaddr_in6 *) srcaddr)->sin6_addr, &allow->addr.ipv6.sin6_addr, sizeof (allow->addr.ipv6.sin6_addr))) { server_allowed = (sense == ALLOW_ALLOW ? TRUE : FALSE); goto match; } break; } break; case ALLOW_NET: switch (srcaddr->sa_family) { struct sockaddr_in6 *src6addr; case AF_INET: if ((((struct sockaddr_in *) srcaddr)->sin_addr.s_addr & allow->mask.ipv4.sin_addr.s_addr) == allow->addr.ipv4.sin_addr.s_addr) { server_allowed = (sense == ALLOW_ALLOW ? TRUE : FALSE); goto match; } break; case AF_INET6: src6addr = (struct sockaddr_in6 *) srcaddr; if (((src6addr->sin6_addr.s6_addr[0] & allow->mask.ipv6.sin6_addr.s6_addr[0]) == allow->addr.ipv6.sin6_addr.s6_addr[0]) && ((src6addr->sin6_addr.s6_addr[1] & allow->mask.ipv6.sin6_addr.s6_addr[1]) == allow->addr.ipv6.sin6_addr.s6_addr[1]) && ((src6addr->sin6_addr.s6_addr[2] & allow->mask.ipv6.sin6_addr.s6_addr[2]) == allow->addr.ipv6.sin6_addr.s6_addr[2]) && ((src6addr->sin6_addr.s6_addr[3] & allow->mask.ipv6.sin6_addr.s6_addr[3]) == allow->addr.ipv6.sin6_addr.s6_addr[3])) { server_allowed = (sense == ALLOW_ALLOW ? TRUE : FALSE); goto match; } break; } } } match: continue; } return server_allowed; } #ifdef HAVE_AVAHI static void resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, AVAHI_GCC_UNUSED void* userdata) { char ifname[IF_NAMESIZE]; AvahiStringList *uuid_entry, *printer_type_entry; char *uuid_key, *uuid_value; debug_printf("resolve_callback() in THREAD %ld\n", pthread_self()); if (r == NULL || name == NULL || type == NULL || domain == NULL) return; /* Get the interface name */ if (!if_indextoname(interface, ifname)) { debug_printf("Unable to find interface name for interface %d: %s\n", interface, strerror(errno)); strncpy(ifname, "Unknown", sizeof(ifname) - 1); } /* Ignore local queues of the cupsd we are serving for, identifying them via UUID */ update_netifs(NULL); if ((flags & AVAHI_LOOKUP_RESULT_LOCAL) || !strcasecmp(ifname, "lo") || is_local_hostname(host_name)) { update_local_printers (); uuid_value = NULL; if (txt && (uuid_entry = avahi_string_list_find(txt, "UUID"))) avahi_string_list_get_pair(uuid_entry, &uuid_key, &uuid_value, NULL); if (uuid_value && g_hash_table_find (local_printers, local_printer_has_uuid, uuid_value)) { debug_printf("Avahi Resolver: Service '%s' of type '%s' in domain '%s' with host name '%s' and port %d on interface '%s' (%s) with UUID %s is from local CUPS, ignored (Avahi lookup result or host name of local machine).\n", name, type, domain, host_name, port, ifname, (address ? (address->proto == AVAHI_PROTO_INET ? "IPv4" : address->proto == AVAHI_PROTO_INET6 ? "IPv6" : "IPv4/IPv6 Unknown") : "IPv4/IPv6 Unknown"), uuid_value); goto ignore; } if (txt && (printer_type_entry = avahi_string_list_find(txt, "printer-type")) && strcasestr(type, "_ipps")) { debug_printf("Avahi Resolver: Service '%s' of type '%s' in domain '%s' with host name '%s' and port %d on interface '%s' (%s) with UUID %s is from another CUPS instance on the local system and uses IPPS, the local CUPS has problems to print on this printer, so we ignore it (Avahi lookup result or host name of local machine).\n", name, type, domain, host_name, port, ifname, (address ? (address->proto == AVAHI_PROTO_INET ? "IPv4" : address->proto == AVAHI_PROTO_INET6 ? "IPv6" : "IPv4/IPv6 Unknown") : "IPv4/IPv6 Unknown"), (uuid_value ? uuid_value : "(unknown)")); goto ignore; } } /* Called whenever a service has been resolved successfully or timed out */ switch (event) { /* Resolver error */ case AVAHI_RESOLVER_FAILURE: debug_printf("Avahi-Resolver: Failed to resolve service '%s' of type '%s' in domain '%s' with host name '%s' and port %d on interface '%s' (%s): %s\n", name, type, domain, host_name, port, ifname, (address ? (address->proto == AVAHI_PROTO_INET ? "IPv4" : address->proto == AVAHI_PROTO_INET6 ? "IPv6" : "IPv4/IPv6 Unknown") : "IPv4/IPv6 Unknown"), avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); break; /* New remote printer found */ case AVAHI_RESOLVER_FOUND: { AvahiStringList *rp_entry, *adminurl_entry; char *rp_key, *rp_value, *adminurl_key, *adminurl_value; debug_printf("Avahi Resolver: Service '%s' of type '%s' in domain '%s' with host name '%s' and port %d on interface '%s' (%s).\n", name, type, domain, host_name, port, ifname, (address ? (address->proto == AVAHI_PROTO_INET ? "IPv4" : address->proto == AVAHI_PROTO_INET6 ? "IPv6" : "IPv4/IPv6 Unknown") : "IPv4/IPv6 Unknown")); /* Ignore if terminated (by SIGTERM) */ if (terminating) { debug_printf("Avahi Resolver: Ignoring because cups-browsed is terminating.\n"); break; } if (txt && (rp_entry = avahi_string_list_find(txt, "rp"))) avahi_string_list_get_pair(rp_entry, &rp_key, &rp_value, NULL); else { rp_key = strdup("rp"); rp_value = strdup(""); } if (txt && (adminurl_entry = avahi_string_list_find(txt, "adminurl"))) avahi_string_list_get_pair(adminurl_entry, &adminurl_key, &adminurl_value, NULL); else { adminurl_key = strdup("adminurl"); if (host_name && (adminurl_value = malloc(strlen(host_name) + 8)) != NULL) sprintf(adminurl_value, "http://%s", host_name); else adminurl_value = strdup(""); } /* If we create queues only for local IPP printers (like IPP-over-USB with ippusbxd) check whether the entry is local and skip if not. We also check for remote CUPS (with "printer-type" TXT field) as this option is only for IPP network printers */ if (CreateIPPPrinterQueues == IPP_PRINTERS_LOCAL_ONLY && strcasecmp(ifname, "lo") && (!txt || avahi_string_list_find(txt, "printer-type") == NULL)) { debug_printf("Avahi Resolver: Service '%s' of type '%s' in domain '%s' skipped, not a local service.\n", name, type, domain); goto clean_up; } if (txt && rp_key && rp_value && adminurl_key && adminurl_value && !strcasecmp(rp_key, "rp") && !strcasecmp(adminurl_key, "adminurl")) { char *p, instance[64]; /* Extract instance from DNSSD service name (to serve as info field) */ p = strstr(name, " @ "); if (p) { int n; n = p - name; if (n >= sizeof(instance)) n = sizeof(instance) - 1; strncpy(instance, name, sizeof(instance) - 1); instance[n] = '\0'; debug_printf("Avahi-Resolver: Instance: %s\n", instance); /* !! */ } else { instance[0] = '\0'; } /* Determine the remote printer's IP */ if (IPBasedDeviceURIs != IP_BASED_URIS_NO || (!browseallow_all && cupsArrayCount(browseallow) > 0)) { struct sockaddr saddr; struct sockaddr *addr = &saddr; char *addrstr; int addrlen; int addrfound = 0; if ((addrstr = calloc(256, sizeof(char))) == NULL) { debug_printf("Avahi Resolver: Service '%s' of type '%s' in domain '%s' skipped, could not allocate memory to determine IP address.\n", name, type, domain); goto clean_up; } if (address && address->proto == AVAHI_PROTO_INET && IPBasedDeviceURIs != IP_BASED_URIS_IPV6_ONLY) { avahi_address_snprint(addrstr, 256, address); addr->sa_family = AF_INET; if (inet_aton(addrstr, &((struct sockaddr_in *) addr)->sin_addr) && allowed(addr)) addrfound = 1; } else if (address && address->proto == AVAHI_PROTO_INET6 && interface != AVAHI_IF_UNSPEC && IPBasedDeviceURIs != IP_BASED_URIS_IPV4_ONLY) { strncpy(addrstr, "[v1.", sizeof(addrstr) - 1); avahi_address_snprint(addrstr + 4, 256 - 6, address); addrlen = strlen(addrstr + 4); addr->sa_family = AF_INET6; if (inet_pton(AF_INET6, addrstr + 4, &((struct sockaddr_in6 *) addr)->sin6_addr) && allowed(addr)) { if (!strncasecmp(addrstr + 4, "fe", 2) && (addrstr[6] == '8' || addrstr[6] == '9' || addrstr[6] == 'A' || addrstr[6] == 'B' || addrstr[6] == 'a' || addrstr[6] == 'B')) /* Link-local address, needs specification of interface */ snprintf(addrstr + addrlen + 4, 256 - addrlen - 4, "%%%s]", ifname); else { addrstr[addrlen + 4] = ']'; addrstr[addrlen + 5] = '\0'; } addrfound = 1; } } else debug_printf("Avahi Resolver: Service '%s' of type '%s' in domain '%s': No IP address information available.\n", name, type, domain); if (addrfound == 1) { /* Check remote printer type and create appropriate local queue to point to it */ if (IPBasedDeviceURIs != IP_BASED_URIS_NO || !host_name) { debug_printf("Avahi Resolver: Service '%s' of type '%s' in domain '%s' with IP address %s.\n", name, type, domain, addrstr); examine_discovered_printer_record((strcasecmp(ifname, "lo") ? host_name : "localhost"), addrstr, port, rp_value, name, "", instance, type, domain, ifname, addr->sa_family, txt); } else examine_discovered_printer_record((strcasecmp(ifname, "lo") ? host_name : "localhost"), NULL, port, rp_value, name, "", instance, type, domain, ifname, addr->sa_family, txt); } else debug_printf("Avahi Resolver: Service '%s' of type '%s' in domain '%s' skipped, could not determine IP address.\n", name, type, domain); free(addrstr); } else { /* Check remote printer type and create appropriate local queue to point to it */ if (host_name) examine_discovered_printer_record((strcasecmp(ifname, "lo") ? host_name : "localhost"), NULL, port, rp_value, name, "", instance, type, domain, ifname, (address->proto == AVAHI_PROTO_INET ? AF_INET : (address->proto == AVAHI_PROTO_INET6 ? AF_INET6 : 0)), txt); else debug_printf("Avahi Resolver: Service '%s' of type '%s' in domain '%s' skipped, host name not supplied.\n", name, type, domain); } } clean_up: /* Clean up */ if (rp_entry) { avahi_free(rp_key); avahi_free(rp_value); } else { free(rp_key); free(rp_value); } if (adminurl_entry) { avahi_free(adminurl_key); avahi_free(adminurl_value); } else { free(adminurl_key); free(adminurl_value); } break; } } ignore: avahi_service_resolver_free(r); if (in_shutdown == 0) recheck_timer (); } static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* userdata) { AvahiClient *c = userdata; char ifname[IF_NAMESIZE]; debug_printf("browse_callback() in THREAD %ld\n", pthread_self()); if (b == NULL) return; /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ switch (event) { /* Avahi browser error */ case AVAHI_BROWSER_FAILURE: debug_printf("Avahi Browser: ERROR: %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); g_main_loop_quit(gmainloop); g_main_context_wakeup(NULL); return; /* New service (remote printer) */ case AVAHI_BROWSER_NEW: if (c == NULL || name == NULL || type == NULL || domain == NULL) return; /* Get the interface name */ if (!if_indextoname(interface, ifname)) { debug_printf("Unable to find interface name for interface %d: %s\n", interface, strerror(errno)); strncpy(ifname, "Unknown", sizeof(ifname) - 1); } debug_printf("Avahi Browser: NEW: service '%s' of type '%s' in domain '%s' on interface '%s' (%s)\n", name, type, domain, ifname, protocol != AVAHI_PROTO_UNSPEC ? avahi_proto_to_string(protocol) : "Unknown"); /* Ignore if terminated (by SIGTERM) */ if (terminating) { debug_printf("Avahi Browser: Ignoring because cups-browsed is terminating.\n"); break; } /* We ignore the returned resolver object. In the callback function we free it. If the server is terminated before the callback function is called the server will free the resolver for us. */ if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_callback, c))) debug_printf("Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c))); break; /* A service (remote printer) has disappeared */ case AVAHI_BROWSER_REMOVE: { remote_printer_t *p; if (name == NULL || type == NULL || domain == NULL) return; /* Get the interface name */ if (!if_indextoname(interface, ifname)) { debug_printf("Unable to find interface name for interface %d: %s\n", interface, strerror(errno)); strncpy(ifname, "Unknown", sizeof(ifname) - 1); } debug_printf("Avahi Browser: REMOVE: service '%s' of type '%s' in domain '%s' on interface '%s' (%s)\n", name, type, domain, ifname, protocol != AVAHI_PROTO_UNSPEC ? avahi_proto_to_string(protocol) : "Unknown"); /* Ignore if terminated (by SIGTERM) */ if (terminating) { debug_printf("Avahi Browser: Ignoring because cups-browsed is terminating.\n"); break; } /* Check whether we have listed this printer */ for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) if (p->status != STATUS_DISAPPEARED && p->status != STATUS_TO_BE_RELEASED && !strcasecmp(p->service_name, name) && !strcasecmp(p->domain, domain)) break; if (p) { int family = (protocol == AVAHI_PROTO_INET ? AF_INET : (protocol == AVAHI_PROTO_INET6 ? AF_INET6 : 0)); if (p->ipp_discoveries) { ipp_discovery_t *ippdis; for (ippdis = cupsArrayFirst(p->ipp_discoveries); ippdis; ippdis = cupsArrayNext(p->ipp_discoveries)) if (!strcasecmp(ippdis->interface, ifname) && !strcasecmp(ippdis->type, type) && ippdis->family == family) { debug_printf("Discovered instance for printer with Service name \"%s\", Domain \"%s\" unregistered: Interface \"%s\", Service type: \"%s\", Protocol: \"%s\"\n", p->service_name, p->domain, ippdis->interface, ippdis->type, (ippdis->family == AF_INET ? "IPv4" : (ippdis->family == AF_INET6 ? "IPv6" : "Unknown"))); cupsArrayRemove(p->ipp_discoveries, (void *)ippdis); ipp_discoveries_list(p->ipp_discoveries); break; } /* Remove the entry if no discovered instances are left */ if (cupsArrayCount(p->ipp_discoveries) == 0) { debug_printf("Removing printer with Service name \"%s\", Domain \"%s\", all discovered instances disappeared.\n", p->service_name, p->domain); remove_printer_entry(p); } } if (in_shutdown == 0) recheck_timer (); } break; } /* All cached Avahi events are treated now */ case AVAHI_BROWSER_ALL_FOR_NOW: case AVAHI_BROWSER_CACHE_EXHAUSTED: debug_printf("Avahi Browser: %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); break; } } void avahi_browser_shutdown() { remote_printer_t *p; avahi_present = 0; /* Remove all queues which we have set up based on DNS-SD discovery*/ if (cupsArrayCount(remote_printers) > 0) { for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (p->type && p->type[0]) { if (KeepGeneratedQueuesOnShutdown) { if (p->status != STATUS_TO_BE_RELEASED && p->status != STATUS_DISAPPEARED) { p->status = STATUS_UNCONFIRMED; p->timeout = time(NULL) + TIMEOUT_CONFIRM; } } else { if (p->status != STATUS_TO_BE_RELEASED) p->status = STATUS_DISAPPEARED; p->timeout = time(NULL) + TIMEOUT_IMMEDIATELY; } } } if (in_shutdown == 0) recheck_timer(); else update_cups_queues(NULL); } /* Free the data structures for DNS-SD browsing */ if (sb1) { avahi_service_browser_free(sb1); sb1 = NULL; } if (sb2) { avahi_service_browser_free(sb2); sb2 = NULL; } /* Switch on auto shutdown mode */ if (autoshutdown_avahi && in_shutdown == 0) { autoshutdown = 1; debug_printf("Avahi server disappeared, switching to auto shutdown mode ...\n"); /* If there are no printers or no jobs schedule the shutdown in autoshutdown_timeout seconds */ if (!autoshutdown_exec_id && (cupsArrayCount(remote_printers) == 0 || (autoshutdown_on == NO_JOBS && check_jobs() == 0))) { debug_printf ("We entered auto shutdown mode and no printers are there to make available or no jobs on them, shutting down in %d sec...\n", autoshutdown_timeout); autoshutdown_exec_id = g_timeout_add_seconds (autoshutdown_timeout, autoshutdown_execute, NULL); } } } void avahi_shutdown() { avahi_browser_shutdown(); if (client) { avahi_client_free(client); client = NULL; } if (glib_poll) { avahi_glib_poll_free(glib_poll); glib_poll = NULL; } } static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) { int error; if (c == NULL) return; /* Called whenever the client or server state changes */ switch (state) { /* avahi-daemon available */ case AVAHI_CLIENT_S_REGISTERING: case AVAHI_CLIENT_S_RUNNING: case AVAHI_CLIENT_S_COLLISION: debug_printf("Avahi server connection got available, setting up service browsers.\n"); /* Create the service browsers */ if (!sb1) if (!(sb1 = avahi_service_browser_new(c, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_ipp._tcp", NULL, 0, browse_callback, c))) { debug_printf("ERROR: Failed to create service browser for IPP: %s\n", avahi_strerror(avahi_client_errno(c))); } if (!sb2) if (!(sb2 = avahi_service_browser_new(c, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_ipps._tcp", NULL, 0, browse_callback, c))) { debug_printf("ERROR: Failed to create service browser for IPPS: %s\n", avahi_strerror(avahi_client_errno(c))); } avahi_present = 1; /* switch off auto shutdown mode */ if (autoshutdown_avahi) { autoshutdown = 0; debug_printf("Avahi server available, switching to permanent mode ...\n"); /* If there is still an active auto shutdown timer, kill it */ if (autoshutdown_exec_id) { debug_printf ("We have left auto shutdown mode, killing auto shutdown timer.\n"); g_source_remove(autoshutdown_exec_id); autoshutdown_exec_id = 0; } } break; /* Avahi client error */ case AVAHI_CLIENT_FAILURE: if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { debug_printf("Avahi server disappeared, shutting down service browsers, removing DNS-SD-discovered print queues.\n"); avahi_browser_shutdown(); /* Renewing client */ avahi_client_free(client); client = avahi_client_new(avahi_glib_poll_get(glib_poll), AVAHI_CLIENT_NO_FAIL, client_callback, NULL, &error); if (!client) { debug_printf("ERROR: Failed to create client: %s\n", avahi_strerror(error)); BrowseRemoteProtocols &= ~BROWSE_DNSSD; avahi_shutdown(); } } else { debug_printf("ERROR: Avahi server connection failure: %s\n", avahi_strerror(avahi_client_errno(c))); g_main_loop_quit(gmainloop); g_main_context_wakeup(NULL); } break; default: break; } } void avahi_init() { int error; if (BrowseRemoteProtocols & BROWSE_DNSSD) { /* Allocate main loop object */ if (!glib_poll) if (!(glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT))) { debug_printf("ERROR: Failed to create glib poll object.\n"); goto avahi_init_fail; } /* Allocate a new client */ if (!client) client = avahi_client_new(avahi_glib_poll_get(glib_poll), AVAHI_CLIENT_NO_FAIL, client_callback, NULL, &error); /* Check wether creating the client object succeeded */ if (!client) { debug_printf("ERROR: Failed to create client: %s\n", avahi_strerror(error)); goto avahi_init_fail; } return; avahi_init_fail: BrowseRemoteProtocols &= ~BROWSE_DNSSD; avahi_shutdown(); } } #endif /* HAVE_AVAHI */ /* * A CUPS printer has been discovered via CUPS Browsing * or with BrowsePoll */ void found_cups_printer (const char *remote_host, const char *uri, const char *location, const char *info) { char scheme[32]; char username[64]; char host[HTTP_MAX_HOST]; char resource[HTTP_MAX_URI]; int port; netif_t *iface; char local_resource[HTTP_MAX_URI]; char service_name[HTTP_MAX_URI]; char *c; int hl; remote_printer_t *printer; memset(scheme, 0, sizeof(scheme)); memset(username, 0, sizeof(username)); memset(host, 0, sizeof(host)); memset(resource, 0, sizeof(resource)); memset(local_resource, 0, sizeof(local_resource)); httpSeparateURI (HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme) - 1, username, sizeof(username) - 1, host, sizeof(host) - 1, &port, resource, sizeof(resource)- 1); /* Check this isn't one of our own broadcasts */ for (iface = cupsArrayFirst (netifs); iface; iface = cupsArrayNext (netifs)) if (!strcasecmp (host, iface->address)) break; if (iface) { debug_printf("ignoring own broadcast on %s\n", iface->address); return; } if (strncasecmp (resource, "/printers/", 10) && strncasecmp (resource, "/classes/", 9)) { debug_printf("Don't understand URI: %s\n", uri); return; } strncpy (local_resource, resource + 1, sizeof (local_resource) - 1); local_resource[sizeof (local_resource) - 1] = '\0'; c = strchr (local_resource, '?'); if (c) *c = '\0'; /* Build the DNS-SD service name which CUPS would give to this printer when DNS-SD-broadcasting it */ snprintf(service_name, sizeof (service_name), "%s @ %s", (info ? info : strchr(local_resource, '/') + 1), host); /* Cut off trailing ".local" of host name */ hl = strlen(service_name); if (hl > 6 && !strcasecmp(service_name + hl - 6, ".local")) service_name[hl - 6] = '\0'; if (hl > 7 && !strcasecmp(service_name + hl - 7, ".local.")) service_name[hl - 7] = '\0'; /* DNS-SD service name has max. 63 characters */ service_name[63] = '\0'; debug_printf("CUPS browsing: Remote host: %s; Port: %d; Remote queue name: %s; Service Name: %s\n", host, port, strchr(local_resource, '/') + 1, service_name); printer = examine_discovered_printer_record(host, NULL, port, local_resource, service_name, location ? location : "", info ? info : "", "", "", "", 0, NULL); if (printer && (printer->domain == NULL || printer->domain[0] == '\0' || printer->type == NULL || printer->type[0] == '\0')) { printer->is_legacy = 1; if (printer->status != STATUS_TO_BE_CREATED) { printer->timeout = time(NULL) + BrowseTimeout; debug_printf("starting BrowseTimeout timer for %s (%ds)\n", printer->queue_name, BrowseTimeout); } } if (printer && NewBrowsePollQueuesShared && (HAVE_CUPS_1_6 || (!HAVE_CUPS_1_6 && !printer->is_legacy))) printer->num_options = cupsAddOption("printer-to-be-shared", "true", printer->num_options, &(printer->options)); } gboolean process_browse_data (GIOChannel *source, GIOCondition condition, gpointer data) { char packet[2048]; http_addr_t srcaddr; socklen_t srclen; ssize_t got; unsigned int type; unsigned int state; char remote_host[256]; char uri[1024]; char location[1024]; char info[1024]; char *c = NULL, *end = NULL; debug_printf("process_browse_data() in THREAD %ld\n", pthread_self()); memset(packet, 0, sizeof(packet)); memset(remote_host, 0, sizeof(remote_host)); memset(uri, 0, sizeof(uri)); memset(info, 0, sizeof(info)); srclen = sizeof (srcaddr); got = recvfrom (browsesocket, packet, sizeof (packet) - 1, 0, &srcaddr.addr, &srclen); if (got == -1) { debug_printf ("cupsd-browsed: error receiving browse packet: %s\n", strerror (errno)); /* Remove this I/O source */ return FALSE; } packet[got] = '\0'; httpAddrString (&srcaddr, remote_host, sizeof (remote_host) - 1); /* Check this packet is allowed */ if (!allowed ((struct sockaddr *) &srcaddr)) { debug_printf("browse packet from %s disallowed\n", remote_host); return TRUE; } debug_printf("browse packet received from %s\n", remote_host); if (sscanf (packet, "%x%x%1023s", &type, &state, uri) < 3) { debug_printf("incorrect browse packet format\n"); return TRUE; } info[0] = '\0'; /* do not read OOB */ end = packet + sizeof(packet); c = strchr (packet, '\"'); if (c >= end) return TRUE; if (c) { /* Extract location field */ { int i; c++; for (i = 0; i < sizeof (location) - 1 && *c != '\"' && c < end; i++, c++) location[i] = *c; location[i] = '\0'; debug_printf("process_browse_data: location: |%s|\n", location); /* !! */ } for (; c < end && *c != '\"'; c++) ; if (c >= end) return TRUE; if (*c == '\"') { for (c++; c < end && isspace(*c); c++) ; } if (c >= end) return TRUE; /* Is there an info field? */ if (*c == '\"') { int i; c++; for (i = 0; i < sizeof (info) - 1 && *c != '\"' && c < end; i++, c++) info[i] = *c; info[i] = '\0'; debug_printf("process_browse_data: info: |%s|\n", info); /* !! */ } } if (c >= end) return TRUE; if (!(type & CUPS_PRINTER_DELETE)) found_cups_printer (remote_host, uri, location, info); if (in_shutdown == 0) recheck_timer (); /* Don't remove this I/O source */ return TRUE; } static void broadcast_browse_packets (gpointer data, gpointer user_data) { browse_data_t *bdata = data; netif_t *browse; char packet[2048]; char uri[HTTP_MAX_URI]; char scheme[32]; char username[64]; char host[HTTP_MAX_HOST]; int port; char resource[HTTP_MAX_URI]; debug_printf("broadcast_browse_packets() in THREAD %ld\n", pthread_self()); for (browse = (netif_t *)cupsArrayFirst (netifs); browse != NULL; browse = (netif_t *)cupsArrayNext (netifs)) { /* Replace 'localhost' with our IP address on this interface */ httpSeparateURI(HTTP_URI_CODING_ALL, bdata->uri, scheme, sizeof(scheme), username, sizeof(username), host, sizeof(host), &port, resource, sizeof(resource)); httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof (uri), scheme, username, browse->address, port, resource); if (snprintf (packet, sizeof (packet), "%x " /* type */ "%x " /* state */ "%s " /* uri */ "\"%s\" " /* location */ "\"%s\" " /* info */ "\"%s\" " /* make-and-model */ "lease-duration=%d" /* BrowseTimeout */ "%s%s" /* other browse options */ "\n", bdata->type, bdata->state, uri, bdata->location, bdata->info, bdata->make_model, BrowseTimeout, bdata->browse_options ? " " : "", bdata->browse_options ? bdata->browse_options : "") >= sizeof (packet)) { debug_printf ("oversize packet not sent\n"); continue; } debug_printf("packet to send:\n%s", packet); int err = sendto (browsesocket, packet, strlen (packet), 0, &browse->broadcast.addr, httpAddrLength (&browse->broadcast)); if (err == -1) debug_printf("cupsd-browsed: sendto returned %d: %s\n", err, strerror (errno)); } } gboolean send_browse_data (gpointer data) { debug_printf("send_browse_data() in THREAD %ld\n", pthread_self()); update_netifs (NULL); res_init (); update_local_printers (); g_list_foreach (browse_data, broadcast_browse_packets, NULL); g_timeout_add_seconds (BrowseInterval, send_browse_data, NULL); /* Stop this timeout handler, we called a new one */ return FALSE; } static browsepoll_printer_t * new_browsepoll_printer (const char *uri_supported, const char *location, const char *info) { browsepoll_printer_t *printer = g_malloc (sizeof (browsepoll_printer_t)); printer->uri_supported = g_strdup (uri_supported); printer->location = g_strdup (location); printer->info = g_strdup (info); return printer; } static void browsepoll_printer_free (gpointer data) { browsepoll_printer_t *printer = data; debug_printf("browsepoll_printer_free() in THREAD %ld\n", pthread_self()); free (printer->uri_supported); free (printer->location); free (printer->info); free (printer); } static void browse_poll_get_printers (browsepoll_t *context, http_t *conn) { static const char * const rattrs[] = { "printer-uri-supported", "printer-location", "printer-info"}; ipp_t *request, *response = NULL; ipp_attribute_t *attr; GList *printers = NULL; debug_printf ("cups-browsed [BrowsePoll %s:%d]: CUPS-Get-Printers\n", context->server, context->port); request = ippNewRequest(CUPS_GET_PRINTERS); if (context->major > 0) { debug_printf("cups-browsed [BrowsePoll %s:%d]: setting IPP version %d.%d\n", context->server, context->port, context->major, context->minor); ippSetVersion (request, context->major, context->minor); } ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", sizeof (rattrs) / sizeof (rattrs[0]), NULL, rattrs); /* Ask the server to exclude printers that are remote or not shared, or implicit classes. */ ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type-mask", CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT | CUPS_PRINTER_NOT_SHARED); ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type", 0); ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser ()); response = cupsDoRequest(conn, request, "/"); if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) { debug_printf("cups-browsed [BrowsePoll %s:%d]: failed: %s\n", context->server, context->port, cupsLastErrorString ()); goto fail; } for (attr = ippFirstAttribute(response); attr; attr = ippNextAttribute(response)) { browsepoll_printer_t *printer; const char *uri, *location, *info; while (attr && ippGetGroupTag(attr) != IPP_TAG_PRINTER) attr = ippNextAttribute(response); if (!attr) break; uri = NULL; info = NULL; location = NULL; while (attr && ippGetGroupTag(attr) == IPP_TAG_PRINTER) { if (!strcasecmp (ippGetName(attr), "printer-uri-supported") && ippGetValueTag(attr) == IPP_TAG_URI) uri = ippGetString(attr, 0, NULL); else if (!strcasecmp (ippGetName(attr), "printer-location") && ippGetValueTag(attr) == IPP_TAG_TEXT) location = ippGetString(attr, 0, NULL); else if (!strcasecmp (ippGetName(attr), "printer-info") && ippGetValueTag(attr) == IPP_TAG_TEXT) info = ippGetString(attr, 0, NULL); attr = ippNextAttribute(response); } if (uri) { found_cups_printer (context->server, uri, location, info); printer = new_browsepoll_printer (uri, location, info); printers = g_list_insert (printers, printer, 0); } if (!attr) break; } g_list_free_full (context->printers, browsepoll_printer_free); context->printers = printers; fail: if (response) ippDelete(response); } static void browse_poll_create_subscription (browsepoll_t *context, http_t *conn) { static const char * const events[] = { "printer-added", "printer-changed", "printer-config-changed", "printer-modified", "printer-deleted", "printer-state-changed" }; ipp_t *request, *response = NULL; ipp_attribute_t *attr; debug_printf ("cups-browsed [BrowsePoll %s:%d]: IPP-Create-Subscription\n", context->server, context->port); request = ippNewRequest(IPP_CREATE_PRINTER_SUBSCRIPTION); if (context->major > 0) { debug_printf("cups-browsed [BrowsePoll %s:%d]: setting IPP version %d.%d\n", context->server, context->port, context->major, context->minor); ippSetVersion (request, context->major, context->minor); } ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, "/"); ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, "notify-pull-method", NULL, "ippget"); ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_CHARSET, "notify-charset", NULL, "utf-8"); ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser ()); ippAddStrings (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, "notify-events", sizeof (events) / sizeof (events[0]), NULL, events); ippAddInteger (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, "notify-time-interval", BrowseInterval); response = cupsDoRequest (conn, request, "/"); if (!response || ippGetStatusCode (response) > IPP_STATUS_OK_EVENTS_COMPLETE) { debug_printf("cupsd-browsed [BrowsePoll %s:%d]: failed: %s\n", context->server, context->port, cupsLastErrorString ()); context->subscription_id = -1; context->can_subscribe = FALSE; goto fail; } for (attr = ippFirstAttribute(response); attr; attr = ippNextAttribute(response)) { if (ippGetGroupTag (attr) == IPP_TAG_SUBSCRIPTION) { if (ippGetValueTag (attr) == IPP_TAG_INTEGER && !strcasecmp (ippGetName (attr), "notify-subscription-id")) { context->subscription_id = ippGetInteger (attr, 0); debug_printf("cups-browsed [BrowsePoll %s:%d]: subscription ID=%d\n", context->server, context->port, context->subscription_id); break; } } } if (!attr) { debug_printf("cups-browsed [BrowsePoll %s:%d]: no ID returned\n", context->server, context->port); context->subscription_id = -1; context->can_subscribe = FALSE; } fail: if (response) ippDelete(response); } static void browse_poll_cancel_subscription (browsepoll_t *context) { ipp_t *request, *response = NULL; http_t *conn = httpConnectEncryptShortTimeout (context->server, context->port, HTTP_ENCRYPT_IF_REQUESTED); if (conn == NULL) { debug_printf("cups-browsed [BrowsePoll %s:%d]: connection failure " "attempting to cancel\n", context->server, context->port); return; } httpSetTimeout(conn, HttpRemoteTimeout, http_timeout_cb, NULL); debug_printf ("cups-browsed [BrowsePoll %s:%d]: IPP-Cancel-Subscription\n", context->server, context->port); request = ippNewRequest(IPP_CANCEL_SUBSCRIPTION); if (context->major > 0) { debug_printf("cups-browsed [BrowsePoll %s:%d]: setting IPP version %d.%d\n", context->server, context->port, context->major, context->minor); ippSetVersion (request, context->major, context->minor); } ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, "/"); ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser ()); ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "notify-subscription-id", context->subscription_id); response = cupsDoRequest (conn, request, "/"); if (!response || ippGetStatusCode (response) > IPP_STATUS_OK_EVENTS_COMPLETE) debug_printf("cupsd-browsed [BrowsePoll %s:%d]: failed: %s\n", context->server, context->port, cupsLastErrorString ()); if (response) ippDelete(response); if (conn) httpClose (conn); } static gboolean browse_poll_get_notifications (browsepoll_t *context, http_t *conn) { ipp_t *request, *response = NULL; ipp_status_t status; gboolean get_printers = FALSE; debug_printf ("cups-browsed [BrowsePoll %s:%d]: IPP-Get-Notifications\n", context->server, context->port); request = ippNewRequest(IPP_GET_NOTIFICATIONS); if (context->major > 0) { debug_printf("cups-browsed [BrowsePoll %s:%d]: setting IPP version %d.%d\n", context->server, context->port, context->major, context->minor); ippSetVersion (request, context->major, context->minor); } ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, "/"); ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser ()); ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "notify-subscription-ids", context->subscription_id); ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "notify-sequence-numbers", context->sequence_number + 1); response = cupsDoRequest (conn, request, "/"); if (!response) status = cupsLastError (); else status = ippGetStatusCode (response); if (status == IPP_STATUS_ERROR_NOT_FOUND) { /* Subscription lease has expired. */ debug_printf ("cups-browsed [BrowsePoll %s:%d]: Lease expired\n", context->server, context->port); browse_poll_create_subscription (context, conn); get_printers = TRUE; } else if (status > IPP_STATUS_OK_EVENTS_COMPLETE) { debug_printf("cups-browsed [BrowsePoll %s:%d]: failed: %s\n", context->server, context->port, cupsLastErrorString ()); context->can_subscribe = FALSE; browse_poll_cancel_subscription (context); context->subscription_id = -1; context->sequence_number = 0; get_printers = TRUE; } if (!get_printers) { ipp_attribute_t *attr; gboolean seen_event = FALSE; int last_seq = context->sequence_number; if (response == NULL) return FALSE; for (attr = ippFirstAttribute(response); attr; attr = ippNextAttribute(response)) if (ippGetGroupTag (attr) == IPP_TAG_EVENT_NOTIFICATION) { /* There is a printer-* event here. */ seen_event = TRUE; if (!strcmp (ippGetName (attr), "notify-sequence-number") && ippGetValueTag (attr) == IPP_TAG_INTEGER) last_seq = ippGetInteger (attr, 0); } if (seen_event) { debug_printf("cups-browsed [BrowsePoll %s:%d]: printer-* event\n", context->server, context->port); context->sequence_number = last_seq; get_printers = TRUE; } else debug_printf("cups-browsed [BrowsePoll %s:%d]: no events\n", context->server, context->port); } if (response) ippDelete (response); return get_printers; } static void browsepoll_printer_keepalive (gpointer data, gpointer user_data) { browsepoll_printer_t *printer = data; const char *server = user_data; debug_printf("browsepoll_printer_keepalive() in THREAD %ld\n", pthread_self()); found_cups_printer (server, printer->uri_supported, printer->location, printer->info); } gboolean browse_poll (gpointer data) { browsepoll_t *context = data; http_t *conn = NULL; gboolean get_printers = FALSE; debug_printf("browse_poll() in THREAD %ld\n", pthread_self()); debug_printf ("browse polling %s:%d\n", context->server, context->port); res_init (); conn = httpConnectEncryptShortTimeout (context->server, context->port, HTTP_ENCRYPT_IF_REQUESTED); if (conn == NULL) { debug_printf("cups-browsed [BrowsePoll %s:%d]: failed to connect\n", context->server, context->port); goto fail; } httpSetTimeout(conn, HttpRemoteTimeout, http_timeout_cb, NULL); if (context->can_subscribe) { if (context->subscription_id == -1) { /* The first time this callback is run we need to create the IPP * subscription to watch to printer-* events. */ browse_poll_create_subscription (context, conn); get_printers = TRUE; } else /* On subsequent runs, check for notifications using our * subscription. */ get_printers = browse_poll_get_notifications (context, conn); } else get_printers = TRUE; update_local_printers (); inhibit_local_printers_update = TRUE; if (get_printers) browse_poll_get_printers (context, conn); else g_list_foreach (context->printers, browsepoll_printer_keepalive, context->server); inhibit_local_printers_update = FALSE; if (in_shutdown == 0) recheck_timer (); fail: if (conn) httpClose (conn); /* Call a new timeout handler so that we run again */ g_timeout_add_seconds (BrowseInterval, browse_poll, data); /* Stop this timeout handler, we called a new one */ return FALSE; } #ifdef HAVE_LDAP gboolean browse_ldap_poll (gpointer data) { char *tmpFilter; /* Query filter */ int filterLen; debug_printf("browse_ldap_poll() in THREAD %ld\n", pthread_self()); /* do real stuff here */ if (!BrowseLDAPDN) { debug_printf("Need to set BrowseLDAPDN to use LDAP browsing!\n"); BrowseLocalProtocols &= ~BROWSE_LDAP; BrowseRemoteProtocols &= ~BROWSE_LDAP; return FALSE; } else { if (!BrowseLDAPInitialised) { BrowseLDAPInitialised = TRUE; /* * Query filter string */ if (BrowseLDAPFilter) filterLen = snprintf(NULL, 0, "(&%s%s)", LDAP_BROWSE_FILTER, BrowseLDAPFilter); else filterLen = strlen(LDAP_BROWSE_FILTER); tmpFilter = (char *)malloc(filterLen + 1); if (!tmpFilter) { debug_printf("Could not allocate memory for LDAP browse query filter!\n"); BrowseLocalProtocols &= ~BROWSE_LDAP; BrowseRemoteProtocols &= ~BROWSE_LDAP; return FALSE; } if (BrowseLDAPFilter) { snprintf(tmpFilter, filterLen + 1, "(&%s%s)", LDAP_BROWSE_FILTER, BrowseLDAPFilter); free(BrowseLDAPFilter); BrowseLDAPFilter = NULL; } else strcpy(tmpFilter, LDAP_BROWSE_FILTER); BrowseLDAPFilter = tmpFilter; /* * Open LDAP handle... */ BrowseLDAPHandle = ldap_new_connection(); } cupsdUpdateLDAPBrowse(); if (in_shutdown == 0) recheck_timer(); } /* Call a new timeout handler so that we run again */ g_timeout_add_seconds (BrowseInterval, browse_ldap_poll, data); /* Stop this timeout handler, we called a new one */ return FALSE; } #endif /* HAVE_LDAP */ static void sigterm_handler(int sig) { (void)sig; /* remove compiler warnings... */ if (terminating) { debug_printf("Caught signal %d while already terminating.\n", sig); return; } terminating = 1; /* ignore any further callbacks and break loops */ /* Flag that we should stop and return... */ g_main_loop_quit(gmainloop); g_main_context_wakeup(NULL); debug_printf("Caught signal %d, shutting down ...\n", sig); } static void sigusr1_handler(int sig) { (void)sig; /* remove compiler warnings... */ /* Turn off auto shutdown mode... */ autoshutdown = 0; debug_printf("Caught signal %d, switching to permanent mode ...\n", sig); /* If there is still an active auto shutdown timer, kill it */ if (autoshutdown_exec_id) { debug_printf ("We have left auto shutdown mode, killing auto shutdown timer.\n"); g_source_remove(autoshutdown_exec_id); autoshutdown_exec_id = 0; } } static void sigusr2_handler(int sig) { (void)sig; /* remove compiler warnings... */ /* Turn on auto shutdown mode... */ autoshutdown = 1; debug_printf("Caught signal %d, switching to auto shutdown mode ...\n", sig); /* If there are no printers or no jobs schedule the shutdown in autoshutdown_timeout seconds */ if (!autoshutdown_exec_id && (cupsArrayCount(remote_printers) == 0 || (autoshutdown_on == NO_JOBS && check_jobs() == 0))) { debug_printf ("We entered auto shutdown mode and no printers are there to make available or no jobs on them, shutting down in %d sec...\n", autoshutdown_timeout); autoshutdown_exec_id = g_timeout_add_seconds (autoshutdown_timeout, autoshutdown_execute, NULL); } } static int read_browseallow_value (const char *value, allow_sense_t sense) { char *p; struct in_addr addr; allow_t *allow; if (value && !strcasecmp (value, "all")) { if (sense == ALLOW_ALLOW) { browseallow_all = TRUE; return 0; } else if (sense == ALLOW_DENY) { browsedeny_all = TRUE; return 0; } else return 1; } allow = calloc (1, sizeof (allow_t)); allow->sense = sense; if (value == NULL) goto fail; p = strchr (value, '/'); if (p) { char *s = strdup (value); s[p - value] = '\0'; if (!inet_aton (s, &addr)) { free (s); goto fail; } free (s); allow->type = ALLOW_NET; allow->addr.ipv4.sin_addr.s_addr = addr.s_addr; p++; if (strchr (p, '.')) { if (inet_aton (p, &addr)) allow->mask.ipv4.sin_addr.s_addr = addr.s_addr; else goto fail; } else { char *endptr; unsigned long bits = strtoul (p, &endptr, 10); if (p == endptr) goto fail; if (bits > 32) goto fail; allow->mask.ipv4.sin_addr.s_addr = htonl (((0xffffffff << (32 - bits)) & 0xffffffff)); } } else if (inet_aton (value, &addr)) { allow->type = ALLOW_IP; allow->addr.ipv4.sin_addr.s_addr = addr.s_addr; } else goto fail; cupsArrayAdd (browseallow, allow); return 0; fail: allow->type = ALLOW_INVALID; cupsArrayAdd (browseallow, allow); return 1; } void read_configuration (const char *filename) { cups_file_t *fp; int i, linenum = 0; char line[HTTP_MAX_BUFFER]; char *value = NULL, *ptr, *ptr2, *start; const char *delim = " \t,"; int browse_allow_line_found = 0; int browse_deny_line_found = 0; int browse_order_line_found = 0; int browse_line_found = 0; browse_filter_t *filter = NULL; int browse_filter_options, exact_match, err; char errbuf[1024]; cluster_t *cluster = NULL; if (!filename) filename = CUPS_SERVERROOT "/cups-browsed.conf"; if ((fp = cupsFileOpen(filename, "r")) == NULL) { debug_printf("unable to open configuration file; " "using defaults\n"); return; } i = 0; linenum = -1; /* First, we read the option settings supplied on the command line via "-o ..." in the order given on the command line, then we read the lines of the configuration file. This means that if there are contradicting settings on the command line and in the configuration file, the setting in the configuration file is used. */ while ((i < cupsArrayCount(command_line_config) && (value = cupsArrayIndex(command_line_config, i++)) && strncpy(line, value, sizeof(line) - 1) && ((strlen(value) > HTTP_MAX_BUFFER-1) ? line[HTTP_MAX_BUFFER-1] = '\0': 1)) || cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) { if (linenum < 0) { /* We are still reading options from the command line ("-o ..."), separate key (line) and value (value) */ value = line; while (*value && !isspace(*value) && !(*value == '=')) value ++; if (*value) { *value = '\0'; value ++; while (*value && (isspace(*value) || (*value == '='))) value ++; } } debug_printf("Reading config%s: %s %s\n", (linenum < 0 ? " (from command line)" : ""), line, value); if (!strcasecmp(line, "DebugLogging") && value) { char *p, *saveptr; p = strtok_r (value, delim, &saveptr); while (p) { if (!strcasecmp(p, "file")) { if (debug_logfile == 0) { debug_logfile = 1; start_debug_logging(); } } else if (!strcasecmp(p, "stderr")) debug_stderr = 1; else if (strcasecmp(p, "none")) debug_printf("Unknown debug logging mode '%s'\n", p); p = strtok_r (NULL, delim, &saveptr); } } else if (!strcasecmp(line, "CacheDir") && value) { if (value[0] != '\0') strncpy(cachedir, value, sizeof(cachedir) - 1); } else if (!strcasecmp(line, "LogDir") && value) { if (value[0] != '\0') strncpy(logdir, value, sizeof(logdir) - 1); } else if ((!strcasecmp(line, "BrowseProtocols") || !strcasecmp(line, "BrowseLocalProtocols") || !strcasecmp(line, "BrowseRemoteProtocols")) && value) { int protocols = 0; char *p, *saveptr; p = strtok_r (value, delim, &saveptr); while (p) { if (!strcasecmp(p, "dnssd")) protocols |= BROWSE_DNSSD; else if (!strcasecmp(p, "cups")) protocols |= BROWSE_CUPS; else if (!strcasecmp(p, "ldap")) protocols |= BROWSE_LDAP; else if (strcasecmp(p, "none")) debug_printf("Unknown protocol '%s'\n", p); p = strtok_r (NULL, delim, &saveptr); } if (!strcasecmp(line, "BrowseLocalProtocols")) BrowseLocalProtocols = protocols; else if (!strcasecmp(line, "BrowseRemoteProtocols")) BrowseRemoteProtocols = protocols; else BrowseLocalProtocols = BrowseRemoteProtocols = protocols; } else if (!strcasecmp(line, "BrowsePoll") && value) { browsepoll_t **old = BrowsePoll; BrowsePoll = realloc (BrowsePoll, (NumBrowsePoll + 1) * sizeof (browsepoll_t *)); if (!BrowsePoll) { debug_printf("unable to realloc: ignoring BrowsePoll line\n"); BrowsePoll = old; } else { char *colon, *slash; browsepoll_t *b = g_malloc0 (sizeof (browsepoll_t)); debug_printf("Adding BrowsePoll server: %s\n", value); b->server = strdup (value); b->port = BrowsePort; b->can_subscribe = TRUE; /* first assume subscriptions work */ b->subscription_id = -1; slash = strchr (b->server, '/'); if (slash) { *slash++ = '\0'; if (!strcasecmp (slash, "version=1.0")) { b->major = 1; b->minor = 0; } else if (!strcasecmp (slash, "version=1.1")) { b->major = 1; b->minor = 1; } else if (!strcasecmp (slash, "version=2.0")) { b->major = 2; b->minor = 0; } else if (!strcasecmp (slash, "version=2.1")) { b->major = 2; b->minor = 1; } else if (!strcasecmp (slash, "version=2.2")) { b->major = 2; b->minor = 2; } else { debug_printf ("ignoring unknown server option: %s\n", slash); } } else b->major = 0; colon = strchr (b->server, ':'); if (colon) { char *endptr; unsigned long n; *colon++ = '\0'; n = strtoul (colon, &endptr, 10); if (endptr != colon && n < INT_MAX) b->port = (int) n; } BrowsePoll[NumBrowsePoll++] = b; } } else if (!strcasecmp(line, "BrowseAllow")) { if (read_browseallow_value (value, ALLOW_ALLOW)) debug_printf ("BrowseAllow value \"%s\" not understood\n", value); else { browse_allow_line_found = 1; browse_line_found = 1; } } else if (!strcasecmp(line, "BrowseDeny")) { if (read_browseallow_value (value, ALLOW_DENY)) debug_printf ("BrowseDeny value \"%s\" not understood\n", value); else { browse_deny_line_found = 1; browse_line_found = 1; } } else if (!strcasecmp(line, "BrowseOrder") && value) { if (!strncasecmp(value, "Allow", 5) && strcasestr(value, "Deny")) { /* Allow,Deny */ browse_order = ORDER_ALLOW_DENY; browse_order_line_found = 1; browse_line_found = 1; } else if (!strncasecmp(value, "Deny", 4) && strcasestr(value, "Allow")) { /* Deny,Allow */ browse_order = ORDER_DENY_ALLOW; browse_order_line_found = 1; browse_line_found = 1; } else debug_printf ("BrowseOrder value \"%s\" not understood\n", value); } else if (!strcasecmp(line, "BrowseFilter") && value) { ptr = value; /* Skip white space */ while (*ptr && isspace(*ptr)) ptr ++; /* Premature line end */ if (!*ptr) goto browse_filter_fail; filter = calloc (1, sizeof (browse_filter_t)); if (!filter) goto browse_filter_fail; browse_filter_options = 1; filter->sense = FILTER_MATCH; exact_match = 0; while (browse_filter_options) { if (!strncasecmp(ptr, "NOT", 3) && *(ptr + 3) && isspace(*(ptr + 3))) { /* Accept remote printers where regexp does NOT match or where the boolean field is false */ filter->sense = FILTER_NOT_MATCH; ptr += 4; /* Skip white space until next word */ while (*ptr && isspace(*ptr)) ptr ++; /* Premature line end without field name */ if (!*ptr) goto browse_filter_fail; } else if (!strncasecmp(ptr, "EXACT", 5) && *(ptr + 5) && isspace(*(ptr + 5))) { /* Consider the rest of the line after the field name a string which has to match the field exactly */ exact_match = 1; ptr += 6; /* Skip white space until next word */ while (*ptr && isspace(*ptr)) ptr ++; /* Premature line end without field name */ if (!*ptr) goto browse_filter_fail; } else /* No more options, consider next word the name of the field which should match the regexp */ browse_filter_options = 0; } start = ptr; while (*ptr && !isspace(*ptr)) ptr ++; if (*ptr) { /* Mark end of the field name */ *ptr = '\0'; /* Skip white space until regexp or line end */ ptr ++; while (*ptr && isspace(*ptr)) ptr ++; } filter->field = strdup(start); if (!*ptr) { /* Only field name and no regexp is given, so this rule is about matching a boolean value */ filter->regexp = NULL; filter->cregexp = NULL; } else { /* The rest of the line is the regexp, store and compile it */ filter->regexp = strdup(ptr); if (!exact_match) { /* Compile the regexp only if the line does not require an exact match (using the EXACT option */ filter->cregexp = calloc(1, sizeof (regex_t)); if ((err = regcomp(filter->cregexp, filter->regexp, REG_EXTENDED | REG_ICASE)) != 0) { regerror(err, filter->cregexp, errbuf, sizeof(errbuf)); debug_printf ("BrowseFilter line with error in regular expression \"%s\": %s\n", filter->regexp, errbuf); goto browse_filter_fail; } } else filter->cregexp = NULL; } cupsArrayAdd (browsefilter, filter); continue; browse_filter_fail: if (filter) { if (filter->field) free(filter->field); if (filter->regexp) free(filter->regexp); if (filter->cregexp) regfree(filter->cregexp); free(filter); filter = NULL; } } else if ((!strcasecmp(line, "BrowseInterval") || !strcasecmp(line, "BrowseTimeout")) && value) { int t = atoi(value); if (t >= 0) { if (!strcasecmp(line, "BrowseInterval")) BrowseInterval = t; else if (!strcasecmp(line, "BrowseTimeout")) BrowseTimeout = t; debug_printf("Set %s to %d sec.\n", line, t); } else debug_printf("Invalid %s value: %d\n", line, t); } else if (!strcasecmp(line, "DomainSocket") && value) { if (value[0] != '\0') { if (DomainSocket != NULL) free(DomainSocket); DomainSocket = strdup(value); } } else if ((!strcasecmp(line, "HttpLocalTimeout") || !strcasecmp(line, "HttpRemoteTimeout")) && value) { int t = atoi(value); if (t >= 0) { if (!strcasecmp(line, "HttpLocalTimeout")) HttpLocalTimeout = t; else if (!strcasecmp(line, "HttpRemoteTimeout")) HttpRemoteTimeout = t; debug_printf("Set %s to %d sec.\n", line, t); } else debug_printf("Invalid %s value: %d\n", line, t); } else if (!strcasecmp(line, "NotifLeaseDuration") && value) { int t = atoi(value); if (t >= 300) { notify_lease_duration = t; debug_printf("Set %s to %d sec.\n", line, t); } else debug_printf("Invalid %s value: %d\n", line, t); } else if (!strcasecmp(line, "HttpMaxRetries") && value) { int t = atoi(value); if (t > 0) { HttpMaxRetries = t; debug_printf("Set %s to %d retries.\n", line, t); } else debug_printf("Invalid %s value: %d\n", line, t); } else if (!strcasecmp(line, "DNSSDBasedDeviceURIs") && value) { if (!strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "1")) DNSSDBasedDeviceURIs = 1; else if (!strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "0")) DNSSDBasedDeviceURIs = 0; } else if (!strcasecmp(line, "IPBasedDeviceURIs") && value) { if (!strcasecmp(value, "IPv4") || !strcasecmp(value, "IPv4Only")) IPBasedDeviceURIs = IP_BASED_URIS_IPV4_ONLY; else if (!strcasecmp(value, "IPv6") || !strcasecmp(value, "IPv6Only")) IPBasedDeviceURIs = IP_BASED_URIS_IPV6_ONLY; else if (!strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "1") || !strcasecmp(value, "IP") || !strcasecmp(value, "IPAddress")) IPBasedDeviceURIs = IP_BASED_URIS_ANY; else if (!strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "0") || !strcasecmp(value, "Name") || !strcasecmp(value, "HostName")) IPBasedDeviceURIs = IP_BASED_URIS_NO; } else if (!strcasecmp(line, "LocalQueueNamingRemoteCUPS") && value) { if (strcasestr(value, "DNSSD") || strcasestr(value, "DNS-SD")) LocalQueueNamingRemoteCUPS = LOCAL_QUEUE_NAMING_DNSSD; else if (strcasestr(value, "Make") && strcasestr(value, "Model")) LocalQueueNamingRemoteCUPS = LOCAL_QUEUE_NAMING_MAKE_MODEL; else if (strcasestr(value, "Remote") || strcasestr(value, "Name")) LocalQueueNamingRemoteCUPS = LOCAL_QUEUE_NAMING_REMOTE_NAME; } else if (!strcasecmp(line, "LocalQueueNamingIPPPrinter") && value) { if (strcasestr(value, "DNSSD") || strcasestr(value, "DNS-SD")) LocalQueueNamingIPPPrinter = LOCAL_QUEUE_NAMING_DNSSD; else if (strcasestr(value, "Make") && strcasestr(value, "Model")) LocalQueueNamingIPPPrinter = LOCAL_QUEUE_NAMING_MAKE_MODEL; } else if (!strcasecmp(line, "OnlyUnsupportedByCUPS") && value) { if (!strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "1")) OnlyUnsupportedByCUPS = 1; else if (!strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "0")) OnlyUnsupportedByCUPS = 0; } else if (!strcasecmp(line, "UseCUPSGeneratedPPDs") && value) { if (!strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "1")) UseCUPSGeneratedPPDs = 1; else if (!strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "0")) UseCUPSGeneratedPPDs = 0; } else if (!strcasecmp(line, "CreateRemoteRawPrinterQueues") && value) { if (!strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "1")) CreateRemoteRawPrinterQueues = 1; else if (!strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "0")) CreateRemoteRawPrinterQueues = 0; } else if (!strcasecmp(line, "CreateRemoteCUPSPrinterQueues") && value) { if (!strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "1")) CreateRemoteCUPSPrinterQueues = 1; else if (!strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "0")) CreateRemoteCUPSPrinterQueues = 0; } else if (!strcasecmp(line, "CreateIPPPrinterQueues") && value) { if (!strcasecmp(value, "all") || !strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "1")) CreateIPPPrinterQueues = IPP_PRINTERS_ALL; else if (!strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "0")) CreateIPPPrinterQueues = IPP_PRINTERS_NO; else if (strcasestr(value, "local") || strcasestr(value, "usb")) CreateIPPPrinterQueues = IPP_PRINTERS_LOCAL_ONLY; else if (strcasestr(value, "driver") && strcasestr(value, "less")) CreateIPPPrinterQueues = IPP_PRINTERS_DRIVERLESS; else if (strcasestr(value, "every") || strcasestr(value, "pwg")) CreateIPPPrinterQueues = IPP_PRINTERS_PWGRASTER; else if (strcasestr(value, "apple") || strcasestr(value, "air")) CreateIPPPrinterQueues = IPP_PRINTERS_APPLERASTER; else if (strcasestr(value, "pclm") || strcasestr(value, "pcl-m")) CreateIPPPrinterQueues = IPP_PRINTERS_PCLM; else if (strcasestr(value, "pdf")) CreateIPPPrinterQueues = IPP_PRINTERS_PDF; } else if (!strcasecmp(line, "IPPPrinterQueueType") && value) { if (!strncasecmp(value, "Auto", 4)) IPPPrinterQueueType = PPD_YES; else if (!strncasecmp(value, "PPD", 3)) IPPPrinterQueueType = PPD_YES; else if (!strncasecmp(value, "NoPPD", 5)) IPPPrinterQueueType = PPD_NO; else if (!strncasecmp(value, "Interface", 9)) IPPPrinterQueueType = PPD_NO; } else if (!strcasecmp(line, "NewIPPPrinterQueuesShared") && value) { if (!strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "1")) NewIPPPrinterQueuesShared = 1; else if (!strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "0")) NewIPPPrinterQueuesShared = 0; } else if(!strcasecmp(line, "DebugLogFileSize") && value) { int val = atoi(value); if(val<=0){ DebugLogFileSize=0; } else DebugLogFileSize=val; } else if (!strcasecmp(line, "AllowResharingRemoteCUPSPrinters") && value) { if (!strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "1")) AllowResharingRemoteCUPSPrinters = 1; else if (!strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "0")) AllowResharingRemoteCUPSPrinters = 0; } else if (!strcasecmp(line, "NewBrowsePollQueuesShared") && value) { if (!strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "1")) NewBrowsePollQueuesShared = 1; else if (!strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "0")) NewBrowsePollQueuesShared = 0; } else if (!strcasecmp(line, "KeepGeneratedQueuesOnShutdown") && value) { if (!strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "1")) KeepGeneratedQueuesOnShutdown = 1; else if (!strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "0")) KeepGeneratedQueuesOnShutdown = 0; } else if (!strcasecmp(line, "AutoClustering") && value) { if (!strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "1")) AutoClustering = 1; else if (!strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "0")) AutoClustering = 0; } else if (!strcasecmp(line, "Cluster") && value) { ptr = value; ptr2 = NULL; /* Skip white space */ while (*ptr && isspace(*ptr)) ptr ++; /* Premature line end */ if (!*ptr) goto cluster_fail; /* Find the local queue name for the cluster */ start = ptr; while (*ptr && !isspace(*ptr) && *ptr != ':') ptr ++; if (*ptr) { /* Mark end of the local queue name */ *ptr = '\0'; /* Skip colon and white space until next word or line end */ ptr ++; while (*ptr && (isspace(*ptr) || *ptr == ':')) ptr ++; } /* Empty queue name */ if (strlen(start) <= 0) goto cluster_fail; /* Clean queue name */ ptr2 = remove_bad_chars(start, 0); /* Check whether we have already a cluster with this name */ for (cluster = cupsArrayFirst(clusters); cluster; cluster = cupsArrayNext(clusters)) if (!strcasecmp(ptr2, cluster->local_queue_name)) { debug_printf("Duplicate cluster with queue name \"%s\".\n", ptr2); cluster = NULL; goto cluster_fail; } /* Create the new cluster definition */ cluster = calloc (1, sizeof (cluster_t)); if (!cluster) goto cluster_fail; cluster->local_queue_name = ptr2; cluster->members = cupsArrayNew(NULL, NULL); ptr2 = NULL; if (!*ptr) { /* Only local queue name given, so assume this name as the only member name (only remote queues with this name match) */ cupsArrayAdd(cluster->members, remove_bad_chars(ptr2, 2)); } else { /* The rest of the line lists one or more member queue names */ while (*ptr) { start = ptr; while (*ptr && !isspace(*ptr)) ptr ++; if (*ptr) { /* Mark end of the current word */ *ptr = '\0'; /* Skip white space until next word or line end */ ptr ++; while (*ptr && isspace(*ptr)) ptr ++; } /* Add member queue name to the list */ if (strlen(start) > 0) cupsArrayAdd(cluster->members, remove_bad_chars(start, 2)); } } cupsArrayAdd (clusters, cluster); if (ptr2 != NULL) { free(ptr2); ptr2 = NULL; } continue; cluster_fail: if (cluster) { if (cluster->local_queue_name) free(cluster->local_queue_name); if (cluster->members) { while ((ptr = cupsArrayFirst (cluster->members)) != NULL) { cupsArrayRemove (cluster->members, ptr); free (ptr); } cupsArrayDelete (cluster->members); } free(cluster); cluster = NULL; } if (ptr2 != NULL) { free(ptr2); ptr2 = NULL; } } else if (!strcasecmp(line, "LoadBalancing") && value) { if (!strncasecmp(value, "QueueOnClient", 13)) LoadBalancingType = QUEUE_ON_CLIENT; else if (!strncasecmp(value, "QueueOnServers", 14)) LoadBalancingType = QUEUE_ON_SERVERS; } else if (!strcasecmp(line, "DefaultOptions") && value) { if (DefaultOptions == NULL && strlen(value) > 0) DefaultOptions = strdup(value); } else if (!strcasecmp(line, "AutoShutdown") && value) { char *p, *saveptr; p = strtok_r (value, delim, &saveptr); while (p) { if (!strcasecmp(p, "On") || !strcasecmp(p, "Yes") || !strcasecmp(p, "True") || !strcasecmp(p, "1")) { autoshutdown = 1; debug_printf("Turning on auto shutdown mode.\n"); } else if (!strcasecmp(p, "Off") || !strcasecmp(p, "No") || !strcasecmp(p, "False") || !strcasecmp(p, "0")) { autoshutdown = 0; debug_printf("Turning off auto shutdown mode (permanent mode).\n"); } else if (!strcasecmp(p, "avahi")) { autoshutdown_avahi = 1; debug_printf("Turning on auto shutdown control by appearing and disappearing of the Avahi server.\n"); } else if (strcasecmp(p, "none")) debug_printf("Unknown mode '%s'\n", p); p = strtok_r (NULL, delim, &saveptr); } } else if (!strcasecmp(line, "AutoShutdownTimeout") && value) { int t = atoi(value); if (t >= 0) { autoshutdown_timeout = t; debug_printf("Set auto shutdown timeout to %d sec.\n", t); } else debug_printf("Invalid auto shutdown timeout value: %d\n", t); } else if (!strcasecmp(line, "AutoShutdownOn") && value) { int success = 0; if (!strncasecmp(value, "no", 2)) { if (strcasestr(value + 2, "queue")) { autoshutdown_on = NO_QUEUES; success = 1; } else if (strcasestr(value + 2, "job")) { autoshutdown_on = NO_JOBS; success = 1; } } if (success) debug_printf("Set auto shutdown inactivity type to no %s.\n", autoshutdown_on == NO_QUEUES ? "queues" : "jobs"); else debug_printf("Invalid auto shutdown inactivity type value: %s\n", value); } else if (!strcasecmp(line, "UpdateCUPSQueuesMaxPerCall") && value) { int n = atoi(value); if (n >= 0) { update_cups_queues_max_per_call = n; if (n > 0) debug_printf("Set maximum of CUPS queue updates per call of update_cups_queues() to %d.\n", n); else debug_printf("Do not limit the number of CUPS queue updates per call of update_cups_queues().\n"); } else debug_printf("Invalid value for maximum number of CUPS queue updates per call of update_cups_queues(): %d\n", n); } else if (!strcasecmp(line, "PauseBetweenCUPSQueueUpdates") && value) { int t = atoi(value); if (t >= 0) { pause_between_cups_queue_updates = t; debug_printf("Set pause between calls of update_cups_queues() to %d sec.\n", t); } else debug_printf("Invalid value for pause between calls of update_cups_queues(): %d\n", t); } #ifdef HAVE_LDAP else if (!strcasecmp(line, "BrowseLDAPBindDN") && value) { if (value[0] != '\0') BrowseLDAPBindDN = strdup(value); } # ifdef HAVE_LDAP_SSL else if (!strcasecmp(line, "BrowseLDAPCACertFile") && value) { if (value[0] != '\0') BrowseLDAPCACertFile = strdup(value); } # endif /* HAVE_LDAP_SSL */ else if (!strcasecmp(line, "BrowseLDAPDN") && value) { if (value[0] != '\0') BrowseLDAPDN = strdup(value); } else if (!strcasecmp(line, "BrowseLDAPPassword") && value) { if (value[0] != '\0') BrowseLDAPPassword = strdup(value); } else if (!strcasecmp(line, "BrowseLDAPServer") && value) { if (value[0] != '\0') BrowseLDAPServer = strdup(value); } else if (!strcasecmp(line, "BrowseLDAPFilter") && value) { if (value[0] != '\0') BrowseLDAPFilter = strdup(value); } #endif /* HAVE_LDAP */ } if (browse_line_found == 0) { /* No "Browse..." lines at all */ browseallow_all = 1; browse_order = ORDER_DENY_ALLOW; debug_printf("No \"Browse...\" line at all, accept all servers (\"BrowseOrder Deny,Allow\").\n"); } else if (browse_order_line_found == 0) { /* No "BrowseOrder" line */ if (browse_allow_line_found == 0) { /* Only "BrowseDeny" lines */ browse_order = ORDER_DENY_ALLOW; debug_printf("No \"BrowseOrder\" line and only \"BrowseDeny\" lines, accept all except what matches the \"BrowseDeny\" lines (\"BrowseOrder Deny,Allow\").\n"); } else if (browse_deny_line_found == 0) { /* Only "BrowseAllow" lines */ browse_order = ORDER_ALLOW_DENY; debug_printf("No \"BrowseOrder\" line and only \"BrowseAllow\" lines, deny all except what matches the \"BrowseAllow\" lines (\"BrowseOrder Allow,Deny\").\n"); } else { /* Default for "BrowseOrder" */ browse_order = ORDER_DENY_ALLOW; debug_printf("No \"BrowseOrder\" line, use \"BrowseOrder Deny,Allow\" as default.\n"); } } cupsFileClose(fp); } static void defer_update_netifs (void) { if (update_netifs_sourceid) g_source_remove (update_netifs_sourceid); update_netifs_sourceid = g_timeout_add_seconds (10, update_netifs, NULL); } static void nm_properties_changed (GDBusProxy *proxy, GVariant *changed_properties, const gchar *const *invalidated_properties, gpointer user_data) { GVariantIter *iter; const gchar *key; GVariant *value; debug_printf("nm_properties_changed() in THREAD %ld\n", pthread_self()); g_variant_get (changed_properties, "a{sv}", &iter); while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { if (!strcmp (key, "ActiveConnections")) { debug_printf ("NetworkManager ActiveConnections changed\n"); defer_update_netifs (); break; } } g_variant_iter_free (iter); } static void find_previous_queue (gpointer key, gpointer value, gpointer user_data) { const char *name = key; const local_printer_t *printer = value; remote_printer_t *p; debug_printf("find_previous_queue() in THREAD %ld\n", pthread_self()); if (printer->cups_browsed_controlled) { /* Queue found, add to our list */ p = create_remote_printer_entry (name, "", "", "", "", "", 0, "", "", "", "", "", 0, NULL, 0, 0, NULL, -1); if (p) { /* Mark as unconfirmed, if no Avahi report of this queue appears in a certain time frame, we will remove the queue */ p->status = STATUS_UNCONFIRMED; if (BrowseRemoteProtocols & BROWSE_CUPS) p->timeout = time(NULL) + BrowseInterval * 3 / 2; else p->timeout = time(NULL) + TIMEOUT_CONFIRM; p->slave_of = NULL; debug_printf("Found CUPS queue %s (URI: %s) from previous session.\n", p->queue_name, p->uri); } else debug_printf("ERROR: Unable to create print queue entry for printer of previous session: %s (%s).\n", name, printer->device_uri); } } int main(int argc, char*argv[]) { int ret = 1; http_t *http; int i; char *val; remote_printer_t *p; GDBusProxy *proxy = NULL; GError *error = NULL; int subscription_id = 0; /* Initialise the command_line_config array */ command_line_config = cupsArrayNew(NULL, NULL); /* Initialise the browseallow array */ browseallow = cupsArrayNew(NULL, NULL); /* Initialise the browsefilter array */ browsefilter = cupsArrayNew(NULL, NULL); /* Initialise the clusters array */ clusters = cupsArrayNew(NULL, NULL); /* Read command line options */ if (argc >= 2) { for (i = 1; i < argc; i++) if (!strcasecmp(argv[i], "--debug") || !strcasecmp(argv[i], "-d") || !strncasecmp(argv[i], "-v", 2)) { /* Turn on debug output mode if requested */ debug_stderr = 1; debug_printf("Reading command line option %s, turning on debug mode (Log on standard error).\n", argv[i]); } else if (!strcasecmp(argv[i], "--logfile") || !strcasecmp(argv[i], "-l")) { /* Turn on debug log file mode if requested */ if (debug_logfile == 0) { debug_logfile = 1; start_debug_logging(); debug_printf("Reading command line option %s, turning on debug mode (Log into log file %s).\n", argv[i], debug_log_file); } } else if (!strncasecmp(argv[i], "-c", 2)) { /* Alternative configuration file */ val = argv[i] + 2; if (strlen(val) == 0) { i ++; if (i < argc && *argv[i] != '-') val = argv[i]; else val = NULL; } if (val) { alt_config_file = strdup(val); debug_printf("Reading command line option -c %s, using alternative configuration file.\n", alt_config_file); } else { fprintf(stderr, "Reading command line option -c, no alternative configuration file name supplied.\n\n"); goto help; } } else if (!strncasecmp(argv[i], "-o", 2)) { /* Configuration option via command line */ val = argv[i] + 2; if (strlen(val) == 0) { i ++; if (i < argc && *argv[i] != '-') val = argv[i]; else val = NULL; } if (val) { cupsArrayAdd (command_line_config, strdup(val)); debug_printf("Reading command line option -o %s, applying extra configuration option.\n", val); } else { fprintf(stderr, "Reading command line option -o, no extra configuration option supplied.\n\n"); goto help; } } else if (!strncasecmp(argv[i], "--autoshutdown-timeout", 22)) { debug_printf("Reading command line: %s\n", argv[i]); if (argv[i][22] == '=' && argv[i][23]) val = argv[i] + 23; else if (!argv[i][22] && i < argc -1) { i++; debug_printf("Reading command line: %s\n", argv[i]); val = argv[i]; } else { fprintf(stderr, "Expected auto shutdown timeout setting after \"--autoshutdown-timeout\" option.\n\n"); goto help; } int t = atoi(val); if (t >= 0) { autoshutdown_timeout = t; debug_printf("Set auto shutdown timeout to %d sec.\n", t); } else { fprintf(stderr, "Invalid auto shutdown timeout value: %d\n\n", t); goto help; } } else if (!strncasecmp(argv[i], "--autoshutdown-on", 17)) { debug_printf("Reading command line: %s\n", argv[i]); if (argv[i][17] == '=' && argv[i][18]) val = argv[i] + 18; else if (!argv[i][17] && i < argc - 1) { i++; debug_printf("Reading command line: %s\n", argv[i]); val = argv[i]; } else { fprintf(stderr, "Expected auto shutdown inactivity type (\"no-queues\" or \"no-jobs\") after \"--autoshutdown-on\" option.\n\n"); goto help; } int success = 0; if (!strncasecmp(val, "no", 2)) { if (strcasestr(val + 2, "queue")) { autoshutdown_on = NO_QUEUES; success = 1; } else if (strcasestr(val + 2, "job")) { autoshutdown_on = NO_JOBS; success = 1; } } if (success) debug_printf("Set auto shutdown inactivity type to no %s.\n", autoshutdown_on == NO_QUEUES ? "queues" : "jobs"); else debug_printf("Invalid auto shutdown inactivity type value: %s\n", val); } else if (!strncasecmp(argv[i], "--autoshutdown", 14)) { debug_printf("Reading command line: %s\n", argv[i]); if (argv[i][14] == '=' && argv[i][15]) val = argv[i] + 15; else if (!argv[i][14] && i < argc -1) { i++; debug_printf("Reading command line: %s\n", argv[i]); val = argv[i]; } else { fprintf(stderr, "Expected auto shutdown setting after \"--autoshutdown\" option.\n\n"); goto help; } if (!strcasecmp(val, "On") || !strcasecmp(val, "Yes") || !strcasecmp(val, "True") || !strcasecmp(val, "1")) { autoshutdown = 1; debug_printf("Turning on auto shutdown mode.\n"); } else if (!strcasecmp(val, "Off") || !strcasecmp(val, "No") || !strcasecmp(val, "False") || !strcasecmp(val, "0")) { autoshutdown = 0; debug_printf("Turning off auto shutdown mode (permanent mode).\n"); } else if (!strcasecmp(val, "avahi")) { autoshutdown_avahi = 1; debug_printf("Turning on auto shutdown control by appearing and disappearing of the Avahi server.\n"); } else if (strcasecmp(val, "none")) { fprintf(stderr, "Unknown mode '%s'\n\n", val); goto help; } } else if (!strcasecmp(argv[i], "--version") || !strcasecmp(argv[i], "--help") || !strcasecmp(argv[i], "-h")) { /* Help!! */ goto help; } else { /* Unknown option */ fprintf(stderr, "Reading command line option %s, unknown command line option.\n\n", argv[i]); goto help; } } debug_printf("cups-browsed of cups-filters version "VERSION" starting.\n"); /* Read in cups-browsed.conf */ read_configuration (alt_config_file); /* Set the paths of the auxiliary files */ if (cachedir[0] == '\0') strncpy(cachedir, DEFAULT_CACHEDIR, sizeof(cachedir) - 1); if (logdir[0] == '\0') strncpy(logdir, DEFAULT_LOGDIR, sizeof(logdir) - 1); strncpy(local_default_printer_file, cachedir, sizeof(local_default_printer_file) - 1); strncpy(local_default_printer_file + strlen(cachedir), LOCAL_DEFAULT_PRINTER_FILE, sizeof(local_default_printer_file) - strlen(cachedir) - 1); strncpy(remote_default_printer_file, cachedir, sizeof(remote_default_printer_file) - 1); strncpy(remote_default_printer_file + strlen(cachedir), REMOTE_DEFAULT_PRINTER_FILE, sizeof(remote_default_printer_file) - strlen(cachedir) - 1); strncpy(save_options_file, cachedir, sizeof(save_options_file) - 1); strncpy(save_options_file + strlen(cachedir), SAVE_OPTIONS_FILE, sizeof(save_options_file) - strlen(cachedir) - 1); strncpy(debug_log_file, logdir, sizeof(debug_log_file) - 1); strncpy(debug_log_file + strlen(logdir), DEBUG_LOG_FILE, sizeof(debug_log_file) - strlen(logdir) - 1); strncpy(debug_log_file_bckp, logdir, sizeof(debug_log_file_bckp) - 1); strncpy(debug_log_file_bckp + strlen(logdir), DEBUG_LOG_FILE_2, sizeof(debug_log_file_bckp) - strlen(logdir) - 1); if (debug_logfile == 1) start_debug_logging(); debug_printf("main() in THREAD %ld\n", pthread_self()); /* If a port is selected via the IPP_PORT environment variable, set this first */ if (getenv("IPP_PORT") != NULL) { snprintf(local_server_str, sizeof(local_server_str) - 1, "localhost:%s", getenv("IPP_PORT")); local_server_str[sizeof(local_server_str) - 1] = '\0'; cupsSetServer(local_server_str); debug_printf("Set port on which CUPS is listening via env variable: IPP_PORT=%s\n", getenv("IPP_PORT")); } /* Point to selected CUPS server or domain socket via the CUPS_SERVER environment variable or DomainSocket configuration file option. Default to localhost:631 (and not to CUPS default to override client.conf files as cups-browsed works only with a local CUPS daemon, not with remote ones. */ local_server_str[0] = '\0'; if (getenv("CUPS_SERVER") != NULL) { strncpy(local_server_str, getenv("CUPS_SERVER"), sizeof(local_server_str) - 1); local_server_str[sizeof(local_server_str) - 1] = '\0'; cupsSetServer(local_server_str); debug_printf("Set host/port/domain socket which CUPS is listening via env variable: CUPS_SERVER=%s\n", getenv("CUPS_SERVER")); } else { if (DomainSocket != NULL) { debug_printf("Set host/port/domain socket on which CUPS is listening via cups-browsed directive DomainSocket: %s\n", DomainSocket); struct stat sockinfo; /* Domain socket information */ if (strcasecmp(DomainSocket, "None") != 0 && strcasecmp(DomainSocket, "Off") != 0 && !stat(DomainSocket, &sockinfo) && (sockinfo.st_mode & S_IROTH) != 0 && (sockinfo.st_mode & S_IWOTH) != 0) { strncpy(local_server_str, DomainSocket, sizeof(local_server_str) - 1); local_server_str[sizeof(local_server_str) - 1] = '\0'; cupsSetServer(local_server_str); } else debug_printf("DomainSocket %s not accessible: %s\n", DomainSocket, strerror(errno)); } } if (local_server_str[0]) setenv("CUPS_SERVER", local_server_str, 1); if (BrowseLocalProtocols & BROWSE_DNSSD) { debug_printf("Local support for DNSSD not implemented\n"); BrowseLocalProtocols &= ~BROWSE_DNSSD; } if (BrowseLocalProtocols & BROWSE_LDAP) { debug_printf("Local support for LDAP not implemented\n"); BrowseLocalProtocols &= ~BROWSE_LDAP; } #ifndef HAVE_AVAHI if (BrowseRemoteProtocols & BROWSE_DNSSD) { debug_printf("Remote support for DNSSD not supported\n"); BrowseRemoteProtocols &= ~BROWSE_DNSSD; } #endif /* HAVE_AVAHI */ #ifndef HAVE_LDAP if (BrowseRemoteProtocols & BROWSE_LDAP) { debug_printf("Remote support for LDAP not supported\n"); BrowseRemoteProtocols &= ~BROWSE_LDAP; } #endif /* HAVE_LDAP */ /* Wait for CUPS daemon to start */ while ((http = http_connect_local ()) == NULL) sleep(1); /* Initialise the array of network interfaces */ netifs = cupsArrayNew(NULL, NULL); local_hostnames = cupsArrayNew(NULL, NULL); update_netifs (NULL); local_printers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_local_printer); cups_supported_remote_printers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_local_printer); /* Read out the currently defined CUPS queues and find the ones which we have added in an earlier session */ update_local_printers (); if ((val = get_cups_default_printer()) != NULL) { default_printer = strdup(val); free(val); } remote_printers = cupsArrayNew(NULL, NULL); g_hash_table_foreach (local_printers, find_previous_queue, NULL); /* Redirect SIGINT and SIGTERM so that we do a proper shutdown, removing the CUPS queues which we have created Use SIGUSR1 and SIGUSR2 to turn off and turn on auto shutdown mode resp. */ #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */ sigset(SIGTERM, sigterm_handler); sigset(SIGINT, sigterm_handler); sigset(SIGUSR1, sigusr1_handler); sigset(SIGUSR2, sigusr2_handler); debug_printf("Using signal handler SIGSET\n"); #elif defined(HAVE_SIGACTION) struct sigaction action; /* Actions for POSIX signals */ memset(&action, 0, sizeof(action)); sigemptyset(&action.sa_mask); sigaddset(&action.sa_mask, SIGTERM); action.sa_handler = sigterm_handler; sigaction(SIGTERM, &action, NULL); sigemptyset(&action.sa_mask); sigaddset(&action.sa_mask, SIGINT); action.sa_handler = sigterm_handler; sigaction(SIGINT, &action, NULL); sigemptyset(&action.sa_mask); sigaddset(&action.sa_mask, SIGUSR1); action.sa_handler = sigusr1_handler; sigaction(SIGUSR1, &action, NULL); sigemptyset(&action.sa_mask); sigaddset(&action.sa_mask, SIGUSR2); action.sa_handler = sigusr2_handler; sigaction(SIGUSR2, &action, NULL); debug_printf("Using signal handler SIGACTION\n"); #else signal(SIGTERM, sigterm_handler); signal(SIGINT, sigterm_handler); signal(SIGUSR1, sigusr1_handler); signal(SIGUSR2, sigusr2_handler); debug_printf("Using signal handler SIGNAL\n"); #endif /* HAVE_SIGSET */ #ifdef HAVE_AVAHI if (autoshutdown_avahi) autoshutdown = 1; avahi_init(); #endif /* HAVE_AVAHI */ if (autoshutdown == 1) { /* If there are no printers or no jobs schedule the shutdown in autoshutdown_timeout seconds */ if (!autoshutdown_exec_id && (cupsArrayCount(remote_printers) == 0 || (autoshutdown_on == NO_JOBS && check_jobs() == 0))) { debug_printf ("We set auto shutdown mode and no printers are there to make available or no jobs on them, shutting down in %d sec...\n", autoshutdown_timeout); autoshutdown_exec_id = g_timeout_add_seconds (autoshutdown_timeout, autoshutdown_execute, NULL); } } if (BrowseLocalProtocols & BROWSE_CUPS || BrowseRemoteProtocols & BROWSE_CUPS) { /* Set up our CUPS Browsing socket */ browsesocket = socket (AF_INET, SOCK_DGRAM, 0); if (browsesocket == -1) { debug_printf("failed to create CUPS Browsing socket: %s\n", strerror (errno)); } else { struct sockaddr_in addr; memset (&addr, 0, sizeof (addr)); addr.sin_addr.s_addr = htonl (INADDR_ANY); addr.sin_family = AF_INET; addr.sin_port = htons (BrowsePort); if (bind (browsesocket, (struct sockaddr *)&addr, sizeof (addr))) { debug_printf("failed to bind CUPS Browsing socket: %s\n", strerror (errno)); close (browsesocket); browsesocket = -1; } else { int on = 1; if (setsockopt (browsesocket, SOL_SOCKET, SO_BROADCAST, &on, sizeof (on))) { debug_printf("failed to allow broadcast: %s\n", strerror (errno)); BrowseLocalProtocols &= ~BROWSE_CUPS; } } } if (browsesocket == -1) { BrowseLocalProtocols &= ~BROWSE_CUPS; BrowseRemoteProtocols &= ~BROWSE_CUPS; } } if (BrowseLocalProtocols == 0 && BrowseRemoteProtocols == 0 && !BrowsePoll) { debug_printf("nothing left to do\n"); ret = 0; goto fail; } /* Override the default password callback so we don't end up * prompting for it. */ cupsSetPasswordCB2 (password_callback, NULL); /* Watch NetworkManager for network interface changes */ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, NULL, /* GDBusInterfaceInfo */ "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", "org.freedesktop.NetworkManager", NULL, /* GCancellable */ NULL); /* GError */ if (proxy) g_signal_connect (proxy, "g-properties-changed", G_CALLBACK (nm_properties_changed), NULL); /* Run the main loop */ gmainloop = g_main_loop_new (NULL, FALSE); recheck_timer (); if (BrowseRemoteProtocols & BROWSE_CUPS) { GIOChannel *browse_channel = g_io_channel_unix_new (browsesocket); g_io_channel_set_close_on_unref (browse_channel, FALSE); g_io_add_watch (browse_channel, G_IO_IN, process_browse_data, NULL); } if (BrowseLocalProtocols & BROWSE_CUPS) { debug_printf ("will send browse data every %ds\n", BrowseInterval); g_idle_add (send_browse_data, NULL); } #ifdef HAVE_LDAP if (BrowseRemoteProtocols & BROWSE_LDAP) { debug_printf ("will browse poll LDAP every %ds\n", BrowseInterval); g_idle_add (browse_ldap_poll, NULL); } #endif /* HAVE_LDAP */ if (BrowsePoll) { size_t index; for (index = 0; index < NumBrowsePoll; index++) { debug_printf ("will browse poll %s every %ds\n", BrowsePoll[index]->server, BrowseInterval); g_idle_add (browse_poll, BrowsePoll[index]); } } /* Subscribe to CUPS' D-Bus notifications and create a proxy to receive the notifications */ subscription_id = create_subscription (); g_timeout_add_seconds (notify_lease_duration / 2, renew_subscription_timeout, &subscription_id); cups_notifier = cups_notifier_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, 0, NULL, CUPS_DBUS_PATH, NULL, &error); if (error) { fprintf (stderr, "Error creating cups notify handler: %s", error->message); g_error_free (error); cups_notifier = NULL; } if (cups_notifier != NULL) { g_signal_connect (cups_notifier, "printer-state-changed", G_CALLBACK (on_printer_state_changed), NULL); g_signal_connect (cups_notifier, "job-state", G_CALLBACK (on_job_state), NULL); g_signal_connect (cups_notifier, "printer-deleted", G_CALLBACK (on_printer_deleted), NULL); g_signal_connect (cups_notifier, "printer-modified", G_CALLBACK (on_printer_modified), NULL); } /* If auto shutdown is active and we do not find any printers initially, schedule the shutdown in autoshutdown_timeout seconds */ if (autoshutdown && !autoshutdown_exec_id && cupsArrayCount(remote_printers) == 0) { debug_printf ("No printers found to make available, shutting down in %d sec...\n", autoshutdown_timeout); autoshutdown_exec_id = g_timeout_add_seconds (autoshutdown_timeout, autoshutdown_execute, NULL); } g_main_loop_run (gmainloop); debug_printf("main loop exited\n"); g_main_loop_unref (gmainloop); gmainloop = NULL; ret = 0; fail: /* Clean up things */ in_shutdown = 1; if (proxy) g_object_unref (proxy); /* Remove all queues which we have set up */ if (KeepGeneratedQueuesOnShutdown == 0) for (p = (remote_printer_t *)cupsArrayFirst(remote_printers); p; p = (remote_printer_t *)cupsArrayNext(remote_printers)) { if (p->status != STATUS_TO_BE_RELEASED) p->status = STATUS_DISAPPEARED; p->timeout = time(NULL) + TIMEOUT_IMMEDIATELY; } update_cups_queues(NULL); cancel_subscription (subscription_id); if (cups_notifier) g_object_unref (cups_notifier); if (BrowsePoll) { size_t index; for (index = 0; index < NumBrowsePoll; index++) { if (BrowsePoll[index]->can_subscribe && BrowsePoll[index]->subscription_id != -1) browse_poll_cancel_subscription (BrowsePoll[index]); free (BrowsePoll[index]->server); g_list_free_full (BrowsePoll[index]->printers, browsepoll_printer_free); free (BrowsePoll[index]); } free (BrowsePoll); } if (local_printers_context) { browse_poll_cancel_subscription (local_printers_context); #ifdef HAVE_CUPS_2_0 free(local_printers_context->server); #endif g_list_free_full (local_printers_context->printers, browsepoll_printer_free); free (local_printers_context); } http_close_local (); #ifdef HAVE_AVAHI avahi_shutdown(); #endif /* HAVE_AVAHI */ #ifdef HAVE_LDAP if (((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_LDAP) && BrowseLDAPHandle) { ldap_disconnect(BrowseLDAPHandle); BrowseLDAPHandle = NULL; } #endif /* HAVE_LDAP */ if (browsesocket != -1) close (browsesocket); g_hash_table_destroy (local_printers); g_hash_table_destroy (cups_supported_remote_printers); if (BrowseLocalProtocols & BROWSE_CUPS) g_list_free_full (browse_data, browse_data_free); /* Close log file if we have one */ if (debug_logfile == 1) stop_debug_logging(); if (deleted_master != NULL) free(deleted_master); if (DefaultOptions != NULL) free(DefaultOptions); if (DomainSocket != NULL) free(DomainSocket); return ret; help: fprintf(stderr, "cups-browsed of cups-filters version "VERSION"\n\n" "Usage: cups-browsed [options]\n" "Options:\n" " -c cups-browsed.conf Set alternative cups-browsed.conf file to use.\n" " -d\n" " -v\n" " --debug Run in debug mode (logging to stderr).\n" " -l\n" " --logfile Run in debug mode (logging into file).\n" " -h\n" " --help\n" " --version Show this usage message.\n" " -o Option=Value Supply configuration option via command line,\n" " options are the same as in cups-browsed.conf.\n" " --autoshutdown= Automatically shut down cups-browsed when inactive:\n" " can be set to Off, On, or avahi, where Off\n" " means that cups-browsed stays running permanently\n" " (default), On means that it shuts down after 30\n" " seconds (or any given timeout) of inactivity, and\n" " avahi means that cups-browsed shuts down when\n" " avahi-daemon shuts down.\n" " --autoshutdown-timout=