diff options
Diffstat (limited to 'scheduler/dirsvc.c')
-rw-r--r-- | scheduler/dirsvc.c | 5584 |
1 files changed, 5584 insertions, 0 deletions
diff --git a/scheduler/dirsvc.c b/scheduler/dirsvc.c new file mode 100644 index 0000000..00e8807 --- /dev/null +++ b/scheduler/dirsvc.c @@ -0,0 +1,5584 @@ +/* + * "$Id: dirsvc.c 10243 2012-02-11 02:05:21Z mike $" + * + * Directory services routines for the CUPS scheduler. + * + * Copyright 2007-2011 by Apple Inc. + * Copyright 1997-2007 by Easy Software Products, all rights reserved. + * + * These coded instructions, statements, and computer programs are the + * property of Apple Inc. and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "LICENSE.txt" + * which should have been included with this file. If this file is + * file is missing or damaged, see the license at "http://www.cups.org/". + * + * Contents: + * + * cupsdDeregisterPrinter() - Stop sending broadcast information for a local + * printer and remove any pending references to + * remote printers. + * cupsdLoadRemoteCache() - Load the remote printer cache. + * cupsdRegisterPrinter() - Start sending broadcast information for a + * printer or update the broadcast contents. + * cupsdRestartPolling() - Restart polling servers as needed. + * cupsdSaveRemoteCache() - Save the remote printer cache. + * cupsdSendBrowseList() - Send new browsing information as necessary. + * ldap_rebind_proc() - Callback function for LDAP rebind + * ldap_connect() - Start new LDAP connection + * ldap_reconnect() - Reconnect to LDAP Server + * ldap_disconnect() - Disconnect from LDAP Server + * cupsdStartBrowsing() - Start sending and receiving broadcast + * information. + * cupsdStartPolling() - Start polling servers as needed. + * cupsdStopBrowsing() - Stop sending and receiving broadcast + * information. + * cupsdStopPolling() - Stop polling servers as needed. + * cupsdUpdateDNSSDName() - Update the computer name we use for + * browsing... + * cupsdUpdateLDAPBrowse() - Scan for new printers via LDAP... + * cupsdUpdateSLPBrowse() - Get browsing information via SLP. + * dequote() - Remote quotes from a string. + * dnssdAddAlias() - Add a DNS-SD alias name. + * dnssdBuildTxtRecord() - Build a TXT record from printer info. + * dnssdComparePrinters() - Compare the registered names of two printers. + * dnssdDeregisterPrinter() - Stop sending broadcast information for a + * printer. + * dnssdPackTxtRecord() - Pack an array of key/value pairs into the TXT + * record format. + * dnssdRegisterCallback() - DNSServiceRegister callback. + * dnssdRegisterPrinter() - Start sending broadcast information for a + * printer or update the broadcast contents. + * dnssdStop() - Stop all DNS-SD registrations. + * dnssdUpdate() - Handle DNS-SD queries. + * get_auth_info_required() - Get the auth-info-required value to advertise. + * get_hostconfig() - Get an /etc/hostconfig service setting. + * is_local_queue() - Determine whether the URI points at a local + * queue. + * process_browse_data() - Process new browse data. + * process_implicit_classes() - Create/update implicit classes as needed. + * send_cups_browse() - Send new browsing information using the CUPS + * protocol. + * ldap_search_rec() - LDAP Search with reconnect + * ldap_freeres() - Free LDAPMessage + * ldap_getval_char() - Get first LDAP value and convert to string + * send_ldap_ou() - Send LDAP ou registrations. + * send_ldap_browse() - Send LDAP printer registrations. + * ldap_dereg_printer() - Delete printer from directory + * ldap_dereg_ou() - Remove the organizational unit. + * send_slp_browse() - Register the specified printer with SLP. + * slp_attr_callback() - SLP attribute callback + * slp_dereg_printer() - SLPDereg() the specified printer + * slp_get_attr() - Get an attribute from an SLP registration. + * slp_reg_callback() - Empty SLPRegReport. + * slp_url_callback() - SLP service url callback + * update_cups_browse() - Update the browse lists using the CUPS + * protocol. + * update_lpd() - Update the LPD configuration as needed. + * update_polling() - Read status messages from the poll daemons. + * update_smb() - Update the SMB configuration as needed. + */ + +/* + * Include necessary headers... + */ + +#include "cupsd.h" +#include <grp.h> + +#ifdef HAVE_DNSSD +# include <dns_sd.h> +# ifdef __APPLE__ +# include <nameser.h> +# ifdef HAVE_COREFOUNDATION +# include <CoreFoundation/CoreFoundation.h> +# endif /* HAVE_COREFOUNDATION */ +# ifdef HAVE_SYSTEMCONFIGURATION +# include <SystemConfiguration/SystemConfiguration.h> +# endif /* HAVE_SYSTEMCONFIGURATION */ +# endif /* __APPLE__ */ +#endif /* HAVE_DNSSD */ + + +/* + * Local functions... + */ + +static char *dequote(char *d, const char *s, int dlen); +static char *get_auth_info_required(cupsd_printer_t *p, char *buffer, + size_t bufsize); +#ifdef __APPLE__ +static int get_hostconfig(const char *name); +#endif /* __APPLE__ */ +static int is_local_queue(const char *uri, char *host, int hostlen, + char *resource, int resourcelen); +static void process_browse_data(const char *uri, const char *host, + const char *resource, cups_ptype_t type, + ipp_pstate_t state, const char *location, + const char *info, const char *make_model, + int num_attrs, cups_option_t *attrs); +static void process_implicit_classes(void); +static void send_cups_browse(cupsd_printer_t *p); +#ifdef HAVE_LDAP +static LDAP *ldap_connect(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); +static void send_ldap_ou(char *ou, char *basedn, char *descstring); +static void send_ldap_browse(cupsd_printer_t *p); +static void ldap_dereg_printer(cupsd_printer_t *p); +static void ldap_dereg_ou(char *ou, char *basedn); +# 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 */ +#ifdef HAVE_LIBSLP +static void send_slp_browse(cupsd_printer_t *p); +#endif /* HAVE_LIBSLP */ +static void update_cups_browse(void); +static void update_lpd(int onoff); +static void update_polling(void); +static void update_smb(int onoff); + + +#ifdef HAVE_DNSSD +# ifdef HAVE_COREFOUNDATION +static void dnssdAddAlias(const void *key, const void *value, + void *context); +# endif /* HAVE_COREFOUNDATION */ +static char *dnssdBuildTxtRecord(int *txt_len, cupsd_printer_t *p, + int for_lpd); +static int dnssdComparePrinters(cupsd_printer_t *a, cupsd_printer_t *b); +static void dnssdDeregisterPrinter(cupsd_printer_t *p); +static char *dnssdPackTxtRecord(int *txt_len, char *keyvalue[][2], + int count); +static void dnssdRegisterCallback(DNSServiceRef sdRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + const char *name, const char *regtype, + const char *domain, void *context); +static void dnssdRegisterPrinter(cupsd_printer_t *p); +static void dnssdStop(void); +static void dnssdUpdate(void); +#endif /* HAVE_DNSSD */ + +#ifdef HAVE_LDAP +static const char * const ldap_attrs[] =/* CUPS LDAP attributes */ + { + "printerDescription", + "printerLocation", + "printerMakeAndModel", + "printerType", + "printerURI", + NULL + }; +#endif /* HAVE_LDAP */ + +#ifdef HAVE_LIBSLP +/* + * SLP definitions... + */ + +/* + * SLP service name for CUPS... + */ + +# define SLP_CUPS_SRVTYPE "service:printer" +# define SLP_CUPS_SRVLEN 15 + + +/* + * Printer service URL structure + */ + +typedef struct _slpsrvurl_s /**** SLP URL list ****/ +{ + struct _slpsrvurl_s *next; /* Next URL in list */ + char url[HTTP_MAX_URI]; + /* URL */ +} slpsrvurl_t; + + +/* + * Local functions... + */ + +static SLPBoolean slp_attr_callback(SLPHandle hslp, const char *attrlist, + SLPError errcode, void *cookie); +static void slp_dereg_printer(cupsd_printer_t *p); +static int slp_get_attr(const char *attrlist, const char *tag, + char **valbuf); +static void slp_reg_callback(SLPHandle hslp, SLPError errcode, + void *cookie); +static SLPBoolean slp_url_callback(SLPHandle hslp, const char *srvurl, + unsigned short lifetime, + SLPError errcode, void *cookie); +#endif /* HAVE_LIBSLP */ + + +/* + * 'cupsdDeregisterPrinter()' - Stop sending broadcast information for a + * local printer and remove any pending + * references to remote printers. + */ + +void +cupsdDeregisterPrinter( + cupsd_printer_t *p, /* I - Printer to register */ + int removeit) /* I - Printer being permanently removed */ +{ + /* + * Only deregister if browsing is enabled and it's a local printer... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "cupsdDeregisterPrinter(p=%p(%s), removeit=%d)", p, p->name, + removeit); + + if (!Browsing || !p->shared || + (p->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT | + CUPS_PRINTER_SCANNER))) + return; + + /* + * Announce the deletion... + */ + + if ((BrowseLocalProtocols & BROWSE_CUPS) && BrowseSocket >= 0) + { + cups_ptype_t savedtype = p->type; /* Saved printer type */ + + p->type |= CUPS_PRINTER_DELETE; + + send_cups_browse(p); + + p->type = savedtype; + } + +#ifdef HAVE_LIBSLP + if (BrowseLocalProtocols & BROWSE_SLP) + slp_dereg_printer(p); +#endif /* HAVE_LIBSLP */ + +#ifdef HAVE_LDAP + if (BrowseLocalProtocols & BROWSE_LDAP) + ldap_dereg_printer(p); +#endif /* HAVE_LDAP */ + +#ifdef HAVE_DNSSD + if (removeit && (BrowseLocalProtocols & BROWSE_DNSSD) && DNSSDRef) + dnssdDeregisterPrinter(p); +#endif /* HAVE_DNSSD */ +} + + +/* + * 'cupsdLoadRemoteCache()' - Load the remote printer cache. + */ + +void +cupsdLoadRemoteCache(void) +{ + int i; /* Looping var */ + cups_file_t *fp; /* remote.cache file */ + int linenum; /* Current line number */ + char line[4096], /* Line from file */ + *value, /* Pointer to value */ + *valueptr, /* Pointer into value */ + scheme[32], /* Scheme portion of URI */ + username[64], /* Username portion of URI */ + host[HTTP_MAX_HOST], + /* Hostname portion of URI */ + resource[HTTP_MAX_URI]; + /* Resource portion of URI */ + int port; /* Port number */ + cupsd_printer_t *p; /* Current printer */ + time_t now; /* Current time */ + + + /* + * Don't load the cache if the remote protocols are disabled... + */ + + if (!Browsing) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, + "cupsdLoadRemoteCache: Not loading remote cache."); + return; + } + + /* + * Open the remote.cache file... + */ + + snprintf(line, sizeof(line), "%s/remote.cache", CacheDir); + if ((fp = cupsdOpenConfFile(line)) == NULL) + return; + + /* + * Read printer configurations until we hit EOF... + */ + + linenum = 0; + p = NULL; + now = time(NULL); + + while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) + { + /* + * Decode the directive... + */ + + if (!_cups_strcasecmp(line, "<Printer") || + !_cups_strcasecmp(line, "<DefaultPrinter")) + { + /* + * <Printer name> or <DefaultPrinter name> + */ + + if (p == NULL && value) + { + /* + * Add the printer and a base file type... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "cupsdLoadRemoteCache: Loading printer %s...", value); + + if ((p = cupsdFindDest(value)) != NULL) + { + if (p->type & CUPS_PRINTER_CLASS) + { + cupsdLogMessage(CUPSD_LOG_WARN, + "Cached remote printer \"%s\" conflicts with " + "existing class!", + value); + p = NULL; + continue; + } + } + else + p = cupsdAddPrinter(value); + + p->accepting = 1; + p->state = IPP_PRINTER_IDLE; + p->type |= CUPS_PRINTER_REMOTE | CUPS_PRINTER_DISCOVERED; + p->browse_time = now; + p->browse_expire = now + BrowseTimeout; + + /* + * Set the default printer as needed... + */ + + if (!_cups_strcasecmp(line, "<DefaultPrinter")) + DefaultPrinter = p; + } + else + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + break; + } + } + else if (!_cups_strcasecmp(line, "<Class") || + !_cups_strcasecmp(line, "<DefaultClass")) + { + /* + * <Class name> or <DefaultClass name> + */ + + if (p == NULL && value) + { + /* + * Add the printer and a base file type... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "cupsdLoadRemoteCache: Loading class %s...", value); + + if ((p = cupsdFindDest(value)) != NULL) + p->type = CUPS_PRINTER_CLASS; + else + p = cupsdAddClass(value); + + p->accepting = 1; + p->state = IPP_PRINTER_IDLE; + p->type |= CUPS_PRINTER_REMOTE | CUPS_PRINTER_DISCOVERED; + p->browse_time = now; + p->browse_expire = now + BrowseTimeout; + + /* + * Set the default printer as needed... + */ + + if (!_cups_strcasecmp(line, "<DefaultClass")) + DefaultPrinter = p; + } + else + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + break; + } + } + else if (!_cups_strcasecmp(line, "</Printer>") || + !_cups_strcasecmp(line, "</Class>")) + { + if (p != NULL) + { + /* + * Close out the current printer... + */ + + cupsdSetPrinterAttrs(p); + + p = NULL; + } + else + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + } + else if (!p) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + } + else if (!_cups_strcasecmp(line, "UUID")) + { + if (value && !strncmp(value, "urn:uuid:", 9)) + cupsdSetString(&(p->uuid), value); + else + cupsdLogMessage(CUPSD_LOG_ERROR, + "Bad UUID on line %d of remote.cache.", linenum); + } + else if (!_cups_strcasecmp(line, "Info")) + { + if (value) + cupsdSetString(&p->info, value); + } + else if (!_cups_strcasecmp(line, "MakeModel")) + { + if (value) + cupsdSetString(&p->make_model, value); + } + else if (!_cups_strcasecmp(line, "Location")) + { + if (value) + cupsdSetString(&p->location, value); + } + else if (!_cups_strcasecmp(line, "DeviceURI")) + { + if (value) + { + httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), + username, sizeof(username), host, sizeof(host), &port, + resource, sizeof(resource)); + + cupsdSetString(&p->hostname, host); + cupsdSetString(&p->uri, value); + cupsdSetDeviceURI(p, value); + } + else + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + } + else if (!_cups_strcasecmp(line, "Option") && value) + { + /* + * Option name value + */ + + for (valueptr = value; *valueptr && !isspace(*valueptr & 255); valueptr ++); + + if (!*valueptr) + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + else + { + for (; *valueptr && isspace(*valueptr & 255); *valueptr++ = '\0'); + + p->num_options = cupsAddOption(value, valueptr, p->num_options, + &(p->options)); + } + } + else if (!_cups_strcasecmp(line, "Reason")) + { + if (value) + { + for (i = 0 ; i < p->num_reasons; i ++) + if (!strcmp(value, p->reasons[i])) + break; + + if (i >= p->num_reasons && + p->num_reasons < (int)(sizeof(p->reasons) / sizeof(p->reasons[0]))) + { + p->reasons[p->num_reasons] = _cupsStrAlloc(value); + p->num_reasons ++; + } + } + else + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + } + else if (!_cups_strcasecmp(line, "State")) + { + /* + * Set the initial queue state... + */ + + if (value && !_cups_strcasecmp(value, "idle")) + p->state = IPP_PRINTER_IDLE; + else if (value && !_cups_strcasecmp(value, "stopped")) + { + p->state = IPP_PRINTER_STOPPED; + cupsdSetPrinterReasons(p, "+paused"); + } + else + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + } + else if (!_cups_strcasecmp(line, "StateMessage")) + { + /* + * Set the initial queue state message... + */ + + if (value) + strlcpy(p->state_message, value, sizeof(p->state_message)); + } + else if (!_cups_strcasecmp(line, "Accepting")) + { + /* + * Set the initial accepting state... + */ + + if (value && + (!_cups_strcasecmp(value, "yes") || + !_cups_strcasecmp(value, "on") || + !_cups_strcasecmp(value, "true"))) + p->accepting = 1; + else if (value && + (!_cups_strcasecmp(value, "no") || + !_cups_strcasecmp(value, "off") || + !_cups_strcasecmp(value, "false"))) + p->accepting = 0; + else + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + } + else if (!_cups_strcasecmp(line, "Type")) + { + if (value) + p->type = atoi(value); + else + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + } + else if (!_cups_strcasecmp(line, "BrowseTime")) + { + if (value) + { + time_t t = atoi(value); + + if (t > p->browse_expire) + p->browse_expire = t; + } + else + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + } + else if (!_cups_strcasecmp(line, "JobSheets")) + { + /* + * Set the initial job sheets... + */ + + if (value) + { + for (valueptr = value; *valueptr && !isspace(*valueptr & 255); valueptr ++); + + if (*valueptr) + *valueptr++ = '\0'; + + cupsdSetString(&p->job_sheets[0], value); + + while (isspace(*valueptr & 255)) + valueptr ++; + + if (*valueptr) + { + for (value = valueptr; *valueptr && !isspace(*valueptr & 255); valueptr ++); + + if (*valueptr) + *valueptr = '\0'; + + cupsdSetString(&p->job_sheets[1], value); + } + } + else + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + } + else if (!_cups_strcasecmp(line, "AllowUser")) + { + if (value) + { + p->deny_users = 0; + cupsdAddString(&(p->users), value); + } + else + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + } + else if (!_cups_strcasecmp(line, "DenyUser")) + { + if (value) + { + p->deny_users = 1; + cupsdAddString(&(p->users), value); + } + else + cupsdLogMessage(CUPSD_LOG_ERROR, + "Syntax error on line %d of remote.cache.", linenum); + } + else + { + /* + * Something else we don't understand... + */ + + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unknown configuration directive %s on line %d of remote.cache.", + line, linenum); + } + } + + cupsFileClose(fp); + + /* + * Do auto-classing if needed... + */ + + process_implicit_classes(); +} + + +/* + * 'cupsdRegisterPrinter()' - Start sending broadcast information for a + * printer or update the broadcast contents. + */ + +void +cupsdRegisterPrinter(cupsd_printer_t *p)/* I - Printer */ +{ + cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdRegisterPrinter(p=%p(%s))", p, + p->name); + + if (!Browsing || !BrowseLocalProtocols || + (p->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT | + CUPS_PRINTER_SCANNER))) + return; + +#ifdef HAVE_LIBSLP +/* if (BrowseLocalProtocols & BROWSE_SLP) + slpRegisterPrinter(p); */ +#endif /* HAVE_LIBSLP */ + +#ifdef HAVE_DNSSD + if ((BrowseLocalProtocols & BROWSE_DNSSD) && DNSSDRef) + dnssdRegisterPrinter(p); +#endif /* HAVE_DNSSD */ +} + + +/* + * 'cupsdRestartPolling()' - Restart polling servers as needed. + */ + +void +cupsdRestartPolling(void) +{ + int i; /* Looping var */ + cupsd_dirsvc_poll_t *pollp; /* Current polling server */ + + + for (i = 0, pollp = Polled; i < NumPolled; i ++, pollp ++) + if (pollp->pid) + kill(pollp->pid, SIGHUP); +} + + +/* + * 'cupsdSaveRemoteCache()' - Save the remote printer cache. + */ + +void +cupsdSaveRemoteCache(void) +{ + int i; /* Looping var */ + cups_file_t *fp; /* remote.cache file */ + char filename[1024], /* remote.cache filename */ + temp[1024], /* Temporary string */ + value[2048], /* Value string */ + *name; /* Current user name */ + cupsd_printer_t *printer; /* Current printer class */ + time_t curtime; /* Current time */ + struct tm *curdate; /* Current date */ + cups_option_t *option; /* Current option */ + + + /* + * Create the remote.cache file... + */ + + snprintf(filename, sizeof(filename), "%s/remote.cache", CacheDir); + + if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL) + return; + + cupsdLogMessage(CUPSD_LOG_DEBUG, "Saving remote.cache..."); + + /* + * Write a small header to the file... + */ + + curtime = time(NULL); + curdate = localtime(&curtime); + strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", curdate); + + cupsFilePuts(fp, "# Remote cache file for " CUPS_SVERSION "\n"); + cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp); + + /* + * Write each local printer known to the system... + */ + + for (printer = (cupsd_printer_t *)cupsArrayFirst(Printers); + printer; + printer = (cupsd_printer_t *)cupsArrayNext(Printers)) + { + /* + * Skip local destinations... + */ + + if (!(printer->type & CUPS_PRINTER_DISCOVERED)) + continue; + + /* + * Write printers as needed... + */ + + if (printer == DefaultPrinter) + cupsFilePuts(fp, "<Default"); + else + cupsFilePutChar(fp, '<'); + + if (printer->type & CUPS_PRINTER_CLASS) + cupsFilePrintf(fp, "Class %s>\n", printer->name); + else + cupsFilePrintf(fp, "Printer %s>\n", printer->name); + + cupsFilePrintf(fp, "BrowseTime %d\n", (int)printer->browse_expire); + + cupsFilePrintf(fp, "UUID %s\n", printer->uuid); + + if (printer->info) + cupsFilePutConf(fp, "Info", printer->info); + + if (printer->location) + cupsFilePutConf(fp, "Location", printer->location); + + if (printer->make_model) + cupsFilePutConf(fp, "MakeModel", printer->make_model); + + cupsFilePutConf(fp, "DeviceURI", printer->device_uri); + + if (printer->state == IPP_PRINTER_STOPPED) + cupsFilePuts(fp, "State Stopped\n"); + else + cupsFilePuts(fp, "State Idle\n"); + + for (i = 0; i < printer->num_reasons; i ++) + cupsFilePutConf(fp, "Reason", printer->reasons[i]); + + cupsFilePrintf(fp, "Type %d\n", printer->type); + + if (printer->accepting) + cupsFilePuts(fp, "Accepting Yes\n"); + else + cupsFilePuts(fp, "Accepting No\n"); + + snprintf(value, sizeof(value), "%s %s", printer->job_sheets[0], + printer->job_sheets[1]); + cupsFilePutConf(fp, "JobSheets", value); + + for (name = (char *)cupsArrayFirst(printer->users); + name; + name = (char *)cupsArrayNext(printer->users)) + cupsFilePutConf(fp, printer->deny_users ? "DenyUser" : "AllowUser", name); + + for (i = printer->num_options, option = printer->options; + i > 0; + i --, option ++) + { + snprintf(value, sizeof(value), "%s %s", option->name, option->value); + cupsFilePutConf(fp, "Option", value); + } + + if (printer->type & CUPS_PRINTER_CLASS) + cupsFilePuts(fp, "</Class>\n"); + else + cupsFilePuts(fp, "</Printer>\n"); + } + + cupsdCloseCreatedConfFile(fp, filename); +} + + +/* + * 'cupsdSendBrowseList()' - Send new browsing information as necessary. + */ + +void +cupsdSendBrowseList(void) +{ + int count; /* Number of dests to update */ + cupsd_printer_t *p; /* Current printer */ + time_t ut, /* Minimum update time */ + to; /* Timeout time */ + + + if (!Browsing || !Printers) + return; + + /* + * Compute the update and timeout times... + */ + + to = time(NULL); + ut = to - BrowseInterval; + + /* + * Figure out how many printers need an update... + */ + + if (BrowseInterval > 0 && BrowseLocalProtocols) + { + int max_count; /* Maximum number to update */ + + + /* + * Throttle the number of printers we'll be updating this time + * around based on the number of queues that need updating and + * the maximum number of queues to update each second... + */ + + max_count = 2 * cupsArrayCount(Printers) / BrowseInterval + 1; + + for (count = 0, p = (cupsd_printer_t *)cupsArrayFirst(Printers); + count < max_count && p != NULL; + p = (cupsd_printer_t *)cupsArrayNext(Printers)) + if (!(p->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT | + CUPS_PRINTER_SCANNER)) && + p->shared && p->browse_time < ut) + count ++; + + /* + * Loop through all of the printers and send local updates as needed... + */ + + if (BrowseNext) + p = (cupsd_printer_t *)cupsArrayFind(Printers, BrowseNext); + else + p = (cupsd_printer_t *)cupsArrayFirst(Printers); + + for (; + count > 0; + p = (cupsd_printer_t *)cupsArrayNext(Printers)) + { + /* + * Check for wraparound... + */ + + if (!p) + p = (cupsd_printer_t *)cupsArrayFirst(Printers); + + if (!p) + break; + else if ((p->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT | + CUPS_PRINTER_SCANNER)) || + !p->shared) + continue; + else if (p->browse_time < ut) + { + /* + * Need to send an update... + */ + + count --; + + p->browse_time = time(NULL); + + if ((BrowseLocalProtocols & BROWSE_CUPS) && BrowseSocket >= 0) + send_cups_browse(p); + +#ifdef HAVE_LIBSLP + if (BrowseLocalProtocols & BROWSE_SLP) + send_slp_browse(p); +#endif /* HAVE_LIBSLP */ + +#ifdef HAVE_LDAP + if (BrowseLocalProtocols & BROWSE_LDAP) + send_ldap_browse(p); +#endif /* HAVE_LDAP */ + } + } + + /* + * Save where we left off so that all printers get updated... + */ + + BrowseNext = p; + } + + /* + * Loop through all of the printers and timeout old printers as needed... + */ + + for (p = (cupsd_printer_t *)cupsArrayFirst(Printers); + p; + p = (cupsd_printer_t *)cupsArrayNext(Printers)) + { + /* + * If this is a remote queue, see if it needs to be timed out... + */ + + if ((p->type & CUPS_PRINTER_DISCOVERED) && + !(p->type & CUPS_PRINTER_IMPLICIT) && + p->browse_expire < to) + { + cupsdAddEvent(CUPSD_EVENT_PRINTER_DELETED, p, NULL, + "%s \'%s\' deleted by directory services (timeout).", + (p->type & CUPS_PRINTER_CLASS) ? "Class" : "Printer", + p->name); + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Remote destination \"%s\" has timed out; " + "deleting it...", + p->name); + + cupsArraySave(Printers); + cupsdDeletePrinter(p, 1); + cupsArrayRestore(Printers); + cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP | CUPSD_DIRTY_REMOTE); + } + } +} + + +#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... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "ldap_rebind_proc: Rebind to %s", 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... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "ldap_rebind_proc: Free values..."); + + if (dnp && *dnp) + free(*dnp); + + if (passwdp && *passwdp) + free(*passwdp); + break; + + case 0: + /* + * Return credentials for LDAP referal... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "ldap_rebind_proc: Return necessary values..."); + + *dnp = strdup(BrowseLDAPBindDN); + *passwdp = strdup(BrowseLDAPPassword); + *authmethodp = LDAP_AUTH_SIMPLE; + break; + + default: + /* + * Should never happen... + */ + + cupsdLogMessage(CUPSD_LOG_ERROR, + "LDAP rebind has been called with wrong freeit value!"); + break; + } + + return (LDAP_SUCCESS); +} +# endif /* defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000) */ +#endif /* HAVE_LDAP_REBIND_PROC */ + + +#ifdef HAVE_LDAP +/* + * 'ldap_connect()' - Start new LDAP connection + */ + +static LDAP * /* O - LDAP handle */ +ldap_connect(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) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, + "ldap_connect: Setting CA certificate file \"%s\"", + BrowseLDAPCACertFile); + + if ((rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, + (void *)BrowseLDAPCACertFile)) != LDAP_SUCCESS) + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to set CA certificate file for LDAP " + "connections: %d - %s", rc, ldap_err2string(rc)); + } +# endif /* HAVE_LDAP_SSL */ + + /* + * Initialize OPENLDAP connection... + * LDAP stuff currently only supports ldapi EXTERNAL SASL binds... + */ + + if (!BrowseLDAPServer || !_cups_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) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "BrowseLDAPServer not configured!"); + cupsdLogMessage(CUPSD_LOG_ERROR, "Disabling LDAP browsing!"); + 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 + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unrecognized LDAP protocol (%s)!", + ldap_protocol); + cupsdLogMessage(CUPSD_LOG_ERROR, "Disabling LDAP browsing!"); + BrowseLocalProtocols &= ~BROWSE_LDAP; + BrowseRemoteProtocols &= ~BROWSE_LDAP; + return (NULL); + } + + if (ldap_port == 0) + { + if (ldap_ssl) + ldap_port = LDAPS_PORT; + else + ldap_port = LDAP_PORT; + } + + cupsdLogMessage(CUPSD_LOG_DEBUG, "ldap_connect: PROT:%s HOST:%s PORT:%d", + 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) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Failed to initialize LDAP SSL client!"); + rc = LDAP_OPERATIONS_ERROR; + } + else + { + if ((TempBrowseLDAPHandle = ldapssl_init(ldap_host, ldap_port, + 1)) == NULL) + rc = LDAP_OPERATIONS_ERROR; + else + rc = LDAP_SUCCESS; + } + } + else + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "LDAP SSL certificate file/database not configured!"); + rc = LDAP_OPERATIONS_ERROR; + } + +# else /* HAVE_LDAP_SSL */ + + /* + * Return error, because client libraries doesn't support SSL + */ + + cupsdLogMessage(CUPSD_LOG_ERROR, + "LDAP client libraries do not support SSL"); + rc = LDAP_OPERATIONS_ERROR; + +# endif /* HAVE_LDAP_SSL */ + } +# endif /* HAVE_OPENLDAP */ + + /* + * Check return code from LDAP initialize... + */ + + if (rc != LDAP_SUCCESS) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to initialize LDAP!"); + + if (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR) + cupsdLogMessage(CUPSD_LOG_ERROR, "Temporarily disabling LDAP browsing..."); + else + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Disabling LDAP browsing!"); + + 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) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to set LDAP protocol version %d!", + version); + cupsdLogMessage(CUPSD_LOG_ERROR, "Disabling LDAP browsing!"); + + 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) + cupsdLogMessage(CUPSD_LOG_ERROR, + "Setting LDAP rebind function failed with status %d: %s", + 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 || !_cups_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) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "LDAP bind failed with error %d: %s", + 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) + cupsdLogMessage(CUPSD_LOG_ERROR, "LDAP SSL error %d: %s", ssl_err, + ldapssl_err2string(ssl_err)); + } +# endif /* defined(HAVE_LDAP_SSL) && defined (HAVE_MOZILLA_LDAP) */ + + ldap_disconnect(TempBrowseLDAPHandle); + + return (NULL); + } + + cupsdLogMessage(CUPSD_LOG_INFO, "LDAP connection established"); + + 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. + */ + + cupsdLogMessage(CUPSD_LOG_INFO, "Try LDAP reconnect..."); + + TempBrowseLDAPHandle = ldap_connect(); + + 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) + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unbind from LDAP server failed with status %d: %s", + rc, ldap_err2string(rc)); +} +#endif /* HAVE_LDAP */ + + +/* + * 'cupsdStartBrowsing()' - Start sending and receiving broadcast information. + */ + +void +cupsdStartBrowsing(void) +{ + int val; /* Socket option value */ + struct sockaddr_in addr; /* Broadcast address */ + cupsd_printer_t *p; /* Current printer */ + + + BrowseNext = NULL; + + if (!Browsing || !(BrowseLocalProtocols | BrowseRemoteProtocols)) + return; + + if ((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_CUPS) + { + if (BrowseSocket < 0) + { + /* + * Create the broadcast socket... + */ + + if ((BrowseSocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to create broadcast socket - %s.", + strerror(errno)); + BrowseLocalProtocols &= ~BROWSE_CUPS; + BrowseRemoteProtocols &= ~BROWSE_CUPS; + + if (FatalErrors & CUPSD_FATAL_BROWSE) + cupsdEndProcess(getpid(), 0); + } + } + + if (BrowseSocket >= 0) + { + /* + * Bind the socket to browse port... + */ + + 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))) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to bind broadcast socket - %s.", + strerror(errno)); + +#ifdef WIN32 + closesocket(BrowseSocket); +#else + close(BrowseSocket); +#endif /* WIN32 */ + + BrowseSocket = -1; + BrowseLocalProtocols &= ~BROWSE_CUPS; + BrowseRemoteProtocols &= ~BROWSE_CUPS; + + if (FatalErrors & CUPSD_FATAL_BROWSE) + cupsdEndProcess(getpid(), 0); + } + } + + if (BrowseSocket >= 0) + { + /* + * Set the "broadcast" flag... + */ + + val = 1; + if (setsockopt(BrowseSocket, SOL_SOCKET, SO_BROADCAST, &val, sizeof(val))) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to set broadcast mode - %s.", + strerror(errno)); + +#ifdef WIN32 + closesocket(BrowseSocket); +#else + close(BrowseSocket); +#endif /* WIN32 */ + + BrowseSocket = -1; + BrowseLocalProtocols &= ~BROWSE_CUPS; + BrowseRemoteProtocols &= ~BROWSE_CUPS; + + if (FatalErrors & CUPSD_FATAL_BROWSE) + cupsdEndProcess(getpid(), 0); + } + } + + if (BrowseSocket >= 0) + { + /* + * Close the socket on exec... + */ + + fcntl(BrowseSocket, F_SETFD, fcntl(BrowseSocket, F_GETFD) | FD_CLOEXEC); + + /* + * Finally, add the socket to the input selection set as needed... + */ + + if (BrowseRemoteProtocols & BROWSE_CUPS) + { + /* + * We only listen if we want remote printers... + */ + + cupsdAddSelect(BrowseSocket, (cupsd_selfunc_t)update_cups_browse, + NULL, NULL); + } + } + } + else + BrowseSocket = -1; + +#ifdef HAVE_DNSSD + if ((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_DNSSD) + { + DNSServiceErrorType error; /* Error from service creation */ + cupsd_listener_t *lis; /* Current listening socket */ + + + /* + * First create a "master" connection for all registrations... + */ + + if ((error = DNSServiceCreateConnection(&DNSSDRef)) + != kDNSServiceErr_NoError) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to create master DNS-SD reference: %d", error); + + if (FatalErrors & CUPSD_FATAL_BROWSE) + cupsdEndProcess(getpid(), 0); + } + else + { + /* + * Add the master connection to the select list... + */ + + int fd = DNSServiceRefSockFD(DNSSDRef); + + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); + + cupsdAddSelect(fd, (cupsd_selfunc_t)dnssdUpdate, NULL, NULL); + + /* + * Then get the port we use for registrations. If we are not listening + * on any non-local ports, there is no sense sharing local printers via + * Bonjour... + */ + + DNSSDPort = 0; + + for (lis = (cupsd_listener_t *)cupsArrayFirst(Listeners); + lis; + lis = (cupsd_listener_t *)cupsArrayNext(Listeners)) + { + if (httpAddrLocalhost(&(lis->address))) + continue; + + DNSSDPort = _httpAddrPort(&(lis->address)); + break; + } + + /* + * Create an array to track the printers we share... + */ + + if (BrowseRemoteProtocols & BROWSE_DNSSD) + DNSSDPrinters = cupsArrayNew((cups_array_func_t)dnssdComparePrinters, + NULL); + + /* + * Set the computer name and register the web interface... + */ + + cupsdUpdateDNSSDName(); + } + } +#endif /* HAVE_DNSSD */ + +#ifdef HAVE_LIBSLP + if ((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_SLP) + { + /* + * Open SLP handle... + */ + + if (SLPOpen("en", SLP_FALSE, &BrowseSLPHandle) != SLP_OK) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to open an SLP handle; disabling SLP browsing!"); + BrowseLocalProtocols &= ~BROWSE_SLP; + BrowseRemoteProtocols &= ~BROWSE_SLP; + BrowseSLPHandle = NULL; + + if (FatalErrors & CUPSD_FATAL_BROWSE) + cupsdEndProcess(getpid(), 0); + } + + BrowseSLPRefresh = 0; + } + else + BrowseSLPHandle = NULL; +#endif /* HAVE_LIBSLP */ + +#ifdef HAVE_LDAP + if ((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_LDAP) + { + if (!BrowseLDAPDN) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Need to set BrowseLDAPDN to use LDAP browsing!"); + BrowseLocalProtocols &= ~BROWSE_LDAP; + BrowseRemoteProtocols &= ~BROWSE_LDAP; + + if (FatalErrors & CUPSD_FATAL_BROWSE) + cupsdEndProcess(getpid(), 0); + } + else + { + /* + * Open LDAP handle... + */ + + if ((BrowseLDAPHandle = ldap_connect()) == NULL && + (FatalErrors & CUPSD_FATAL_BROWSE)) + cupsdEndProcess(getpid(), 0); + } + + BrowseLDAPRefresh = 0; + } +#endif /* HAVE_LDAP */ + + /* + * Enable LPD and SMB printer sharing as needed through external programs... + */ + + if (BrowseLocalProtocols & BROWSE_LPD) + update_lpd(1); + + if (BrowseLocalProtocols & BROWSE_SMB) + update_smb(1); + + /* + * Register the individual printers + */ + + for (p = (cupsd_printer_t *)cupsArrayFirst(Printers); + p; + p = (cupsd_printer_t *)cupsArrayNext(Printers)) + if (!(p->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT | + CUPS_PRINTER_SCANNER))) + cupsdRegisterPrinter(p); +} + + +/* + * 'cupsdStartPolling()' - Start polling servers as needed. + */ + +void +cupsdStartPolling(void) +{ + int i; /* Looping var */ + cupsd_dirsvc_poll_t *pollp; /* Current polling server */ + char polld[1024]; /* Poll daemon path */ + char sport[255]; /* Server port */ + char bport[255]; /* Browser port */ + char interval[255]; /* Poll interval */ + int statusfds[2]; /* Status pipe */ + char *argv[6]; /* Arguments */ + char *envp[100]; /* Environment */ + + + /* + * Don't do anything if we aren't polling... + */ + + if (NumPolled == 0 || BrowseSocket < 0) + { + PollPipe = -1; + PollStatusBuffer = NULL; + return; + } + + /* + * Setup string arguments for polld, port and interval options. + */ + + snprintf(polld, sizeof(polld), "%s/daemon/cups-polld", ServerBin); + + sprintf(bport, "%d", BrowsePort); + + if (BrowseInterval) + sprintf(interval, "%d", BrowseInterval); + else + strcpy(interval, "30"); + + argv[0] = "cups-polld"; + argv[2] = sport; + argv[3] = interval; + argv[4] = bport; + argv[5] = NULL; + + cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0]))); + + /* + * Create a pipe that receives the status messages from each + * polling daemon... + */ + + if (cupsdOpenPipe(statusfds)) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to create polling status pipes - %s.", + strerror(errno)); + PollPipe = -1; + PollStatusBuffer = NULL; + return; + } + + PollPipe = statusfds[0]; + PollStatusBuffer = cupsdStatBufNew(PollPipe, "[Poll]"); + + /* + * Run each polling daemon, redirecting stderr to the polling pipe... + */ + + for (i = 0, pollp = Polled; i < NumPolled; i ++, pollp ++) + { + sprintf(sport, "%d", pollp->port); + + argv[1] = pollp->hostname; + + if (cupsdStartProcess(polld, argv, envp, -1, -1, statusfds[1], -1, -1, + 0, DefaultProfile, NULL, &(pollp->pid)) < 0) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "cupsdStartPolling: Unable to fork polling daemon - %s", + strerror(errno)); + pollp->pid = 0; + break; + } + else + cupsdLogMessage(CUPSD_LOG_DEBUG, + "cupsdStartPolling: Started polling daemon for %s:%d, pid = %d", + pollp->hostname, pollp->port, pollp->pid); + } + + close(statusfds[1]); + + /* + * Finally, add the pipe to the input selection set... + */ + + cupsdAddSelect(PollPipe, (cupsd_selfunc_t)update_polling, NULL, NULL); +} + + +/* + * 'cupsdStopBrowsing()' - Stop sending and receiving broadcast information. + */ + +void +cupsdStopBrowsing(void) +{ + cupsd_printer_t *p; /* Current printer */ + + + if (!Browsing || !(BrowseLocalProtocols | BrowseRemoteProtocols)) + return; + + /* + * De-register the individual printers + */ + + for (p = (cupsd_printer_t *)cupsArrayFirst(Printers); + p; + p = (cupsd_printer_t *)cupsArrayNext(Printers)) + if (!(p->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT | + CUPS_PRINTER_SCANNER))) + cupsdDeregisterPrinter(p, 1); + + /* + * Shut down browsing sockets... + */ + + if (((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_CUPS) && + BrowseSocket >= 0) + { + /* + * Close the socket and remove it from the input selection set. + */ + +#ifdef WIN32 + closesocket(BrowseSocket); +#else + close(BrowseSocket); +#endif /* WIN32 */ + + cupsdRemoveSelect(BrowseSocket); + BrowseSocket = -1; + } + +#ifdef HAVE_DNSSD + if ((BrowseLocalProtocols & BROWSE_DNSSD) && DNSSDRef) + dnssdStop(); +#endif /* HAVE_DNSSD */ + +#ifdef HAVE_LIBSLP + if (((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_SLP) && + BrowseSLPHandle) + { + /* + * Close SLP handle... + */ + + SLPClose(BrowseSLPHandle); + BrowseSLPHandle = NULL; + } +#endif /* HAVE_LIBSLP */ + +#ifdef HAVE_LDAP + if (((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_LDAP) && + BrowseLDAPHandle) + { + ldap_dereg_ou(ServerName, BrowseLDAPDN); + ldap_disconnect(BrowseLDAPHandle); + BrowseLDAPHandle = NULL; + } +#endif /* HAVE_OPENLDAP */ + + /* + * Disable LPD and SMB printer sharing as needed through external programs... + */ + + if (BrowseLocalProtocols & BROWSE_LPD) + update_lpd(0); + + if (BrowseLocalProtocols & BROWSE_SMB) + update_smb(0); +} + + +/* + * 'cupsdStopPolling()' - Stop polling servers as needed. + */ + +void +cupsdStopPolling(void) +{ + int i; /* Looping var */ + cupsd_dirsvc_poll_t *pollp; /* Current polling server */ + + + if (PollPipe >= 0) + { + cupsdStatBufDelete(PollStatusBuffer); + close(PollPipe); + + cupsdRemoveSelect(PollPipe); + + PollPipe = -1; + PollStatusBuffer = NULL; + } + + for (i = 0, pollp = Polled; i < NumPolled; i ++, pollp ++) + if (pollp->pid) + cupsdEndProcess(pollp->pid, 0); +} + + +#ifdef HAVE_DNSSD +/* + * 'cupsdUpdateDNSSDName()' - Update the computer name we use for browsing... + */ + +void +cupsdUpdateDNSSDName(void) +{ + DNSServiceErrorType error; /* Error from service creation */ + char webif[1024]; /* Web interface share name */ +# ifdef HAVE_SYSTEMCONFIGURATION + SCDynamicStoreRef sc; /* Context for dynamic store */ + CFDictionaryRef btmm; /* Back-to-My-Mac domains */ + CFStringEncoding nameEncoding; /* Encoding of computer name */ + CFStringRef nameRef; /* Host name CFString */ + char nameBuffer[1024]; /* C-string buffer */ +# endif /* HAVE_SYSTEMCONFIGURATION */ + + + /* + * Only share the web interface and printers when non-local listening is + * enabled... + */ + + + if (!DNSSDPort) + return; + + /* + * Get the computer name as a c-string... + */ + +# ifdef HAVE_SYSTEMCONFIGURATION + sc = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("cupsd"), NULL, NULL); + + if (sc) + { + /* + * Get the computer name from the dynamic store... + */ + + cupsdClearString(&DNSSDComputerName); + + if ((nameRef = SCDynamicStoreCopyComputerName(sc, &nameEncoding)) != NULL) + { + if (CFStringGetCString(nameRef, nameBuffer, sizeof(nameBuffer), + kCFStringEncodingUTF8)) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Dynamic store computer name is \"%s\".", nameBuffer); + cupsdSetString(&DNSSDComputerName, nameBuffer); + } + + CFRelease(nameRef); + } + + if (!DNSSDComputerName) + { + /* + * Use the ServerName instead... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Using ServerName \"%s\" as computer name.", ServerName); + cupsdSetString(&DNSSDComputerName, ServerName); + } + + /* + * Get the local hostname from the dynamic store... + */ + + cupsdClearString(&DNSSDHostName); + + if ((nameRef = SCDynamicStoreCopyLocalHostName(sc)) != NULL) + { + if (CFStringGetCString(nameRef, nameBuffer, sizeof(nameBuffer), + kCFStringEncodingUTF8)) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Dynamic store host name is \"%s\".", nameBuffer); + cupsdSetString(&DNSSDHostName, nameBuffer); + } + + CFRelease(nameRef); + } + + if (!DNSSDHostName) + { + /* + * Use the ServerName instead... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Using ServerName \"%s\" as host name.", ServerName); + cupsdSetString(&DNSSDHostName, ServerName); + } + + /* + * Get any Back-to-My-Mac domains and add them as aliases... + */ + + cupsdFreeAliases(DNSSDAlias); + DNSSDAlias = NULL; + + btmm = SCDynamicStoreCopyValue(sc, CFSTR("Setup:/Network/BackToMyMac")); + if (btmm && CFGetTypeID(btmm) == CFDictionaryGetTypeID()) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, "%d Back to My Mac aliases to add.", + (int)CFDictionaryGetCount(btmm)); + CFDictionaryApplyFunction(btmm, dnssdAddAlias, NULL); + } + else if (btmm) + cupsdLogMessage(CUPSD_LOG_ERROR, + "Bad Back to My Mac data in dynamic store!"); + else + cupsdLogMessage(CUPSD_LOG_DEBUG, "No Back to My Mac aliases to add."); + + if (btmm) + CFRelease(btmm); + + CFRelease(sc); + } + else +# endif /* HAVE_SYSTEMCONFIGURATION */ + { + cupsdSetString(&DNSSDComputerName, ServerName); + cupsdSetString(&DNSSDHostName, ServerName); + } + + /* + * Then (re)register the web interface if enabled... + */ + + if (BrowseWebIF) + { + if (DNSSDComputerName) + snprintf(webif, sizeof(webif), "CUPS @ %s", DNSSDComputerName); + else + strlcpy(webif, "CUPS Web Interface", sizeof(webif)); + + if (WebIFRef) + DNSServiceRefDeallocate(WebIFRef); + + WebIFRef = DNSSDRef; + if ((error = DNSServiceRegister(&WebIFRef, + kDNSServiceFlagsShareConnection, + 0, webif, "_http._tcp", NULL, + NULL, htons(DNSSDPort), 7, + "\006path=/", dnssdRegisterCallback, + NULL)) != kDNSServiceErr_NoError) + cupsdLogMessage(CUPSD_LOG_ERROR, + "DNS-SD web interface registration failed: %d", error); + } +} +#endif /* HAVE_DNSSD */ + + +#ifdef HAVE_LDAP +/* + * '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 */ + location[1024], /* Printer location */ + info[1024], /* Printer information */ + make_model[1024], /* Printer make and model */ + type_num[30]; /* Printer type number */ + int type; /* Printer type */ + int rc; /* LDAP status */ + int limit; /* Size limit */ + LDAPMessage *res, /* LDAP search results */ + *e; /* Current entry from search */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "UpdateLDAPBrowse: %s", ServerName); + + BrowseLDAPRefresh = time(NULL) + BrowseInterval; + + /* + * 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, + "(objectclass=cupsPrinter)", (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; + cupsdLogMessage(CUPSD_LOG_INFO, + "LDAP update temporary disabled"); + } + return; + } + + /* + * If LDAP updates were disabled, we will reenable them... + */ + + if (! BrowseLDAPUpdate) + { + BrowseLDAPUpdate = TRUE; + cupsdLogMessage(CUPSD_LOG_INFO, + "LDAP update enabled"); + } + + /* + * Count LDAP entries and return if no entry exist... + */ + + limit = ldap_count_entries(BrowseLDAPHandle, res); + cupsdLogMessage(CUPSD_LOG_DEBUG2, "LDAP search returned %d entries", 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; + + type = atoi(type_num); + + if (ldap_getval_firststring(BrowseLDAPHandle, e, + "printerURI", uri, sizeof(uri)) == -1) + continue; + + /* + * Process the entry as browse data... + */ + + if (!is_local_queue(uri, host, sizeof(host), resource, sizeof(resource))) + process_browse_data(uri, host, resource, type, IPP_PRINTER_IDLE, + location, info, make_model, 0, NULL); + + } + + ldap_freeres(res); +} +#endif /* HAVE_LDAP */ + + +#ifdef HAVE_LIBSLP +/* + * 'cupsdUpdateSLPBrowse()' - Get browsing information via SLP. + */ + +void +cupsdUpdateSLPBrowse(void) +{ + slpsrvurl_t *s, /* Temporary list of service URLs */ + *next; /* Next service in list */ + cupsd_printer_t p; /* Printer information */ + const char *uri; /* Pointer to printer URI */ + char host[HTTP_MAX_URI], /* Host portion of URI */ + resource[HTTP_MAX_URI]; /* Resource portion of URI */ + + + /* + * Reset the refresh time... + */ + + BrowseSLPRefresh = time(NULL) + BrowseInterval; + + /* + * Poll for remote printers using SLP... + */ + + s = NULL; + + SLPFindSrvs(BrowseSLPHandle, SLP_CUPS_SRVTYPE, "", "", + slp_url_callback, &s); + + /* + * Loop through the list of available printers... + */ + + for (; s; s = next) + { + /* + * Save the "next" pointer... + */ + + next = s->next; + + /* + * Load a cupsd_printer_t structure with the SLP service attributes... + */ + + SLPFindAttrs(BrowseSLPHandle, s->url, "", "", slp_attr_callback, &p); + + /* + * Process this printer entry... + */ + + uri = s->url + SLP_CUPS_SRVLEN + 1; + + if (!strncmp(uri, "http://", 7) || !strncmp(uri, "ipp://", 6)) + { + /* + * Pull the URI apart to see if this is a local or remote printer... + */ + + if (!is_local_queue(uri, host, sizeof(host), resource, sizeof(resource))) + process_browse_data(uri, host, resource, p.type, IPP_PRINTER_IDLE, + p.location, p.info, p.make_model, 0, NULL); + } + + /* + * Free this listing... + */ + + cupsdClearString(&p.info); + cupsdClearString(&p.location); + cupsdClearString(&p.make_model); + + free(s); + } +} +#endif /* HAVE_LIBSLP */ + + +/* + * 'dequote()' - Remote quotes from a string. + */ + +static char * /* O - Dequoted string */ +dequote(char *d, /* I - Destination string */ + const char *s, /* I - Source string */ + int dlen) /* I - Destination length */ +{ + char *dptr; /* Pointer into destination */ + + + if (s) + { + for (dptr = d, dlen --; *s && dlen > 0; s ++) + if (*s != '\"') + { + *dptr++ = *s; + dlen --; + } + + *dptr = '\0'; + } + else + *d = '\0'; + + return (d); +} + + +#ifdef HAVE_DNSSD +# ifdef HAVE_COREFOUNDATION +/* + * 'dnssdAddAlias()' - Add a DNS-SD alias name. + */ + +static void +dnssdAddAlias(const void *key, /* I - Key */ + const void *value, /* I - Value (domain) */ + void *context) /* I - Unused */ +{ + char valueStr[1024], /* Domain string */ + hostname[1024], /* Complete hostname */ + *hostptr; /* Pointer into hostname */ + + + (void)key; + (void)context; + + if (CFGetTypeID((CFStringRef)value) == CFStringGetTypeID() && + CFStringGetCString((CFStringRef)value, valueStr, sizeof(valueStr), + kCFStringEncodingUTF8)) + { + snprintf(hostname, sizeof(hostname), "%s.%s", DNSSDHostName, valueStr); + hostptr = hostname + strlen(hostname) - 1; + if (*hostptr == '.') + *hostptr = '\0'; /* Strip trailing dot */ + + if (!DNSSDAlias) + DNSSDAlias = cupsArrayNew(NULL, NULL); + + cupsdAddAlias(DNSSDAlias, hostname); + cupsdLogMessage(CUPSD_LOG_DEBUG, "Added Back to My Mac ServerAlias %s", + hostname); + } + else + cupsdLogMessage(CUPSD_LOG_ERROR, + "Bad Back to My Mac domain in dynamic store!"); +} +# endif /* HAVE_COREFOUNDATION */ + + +/* + * 'dnssdBuildTxtRecord()' - Build a TXT record from printer info. + */ + +static char * /* O - TXT record */ +dnssdBuildTxtRecord( + int *txt_len, /* O - TXT record length */ + cupsd_printer_t *p, /* I - Printer information */ + int for_lpd) /* I - 1 = LPD, 0 = IPP */ +{ + int i; /* Looping var */ + char admin_hostname[256], /* .local hostname for admin page */ + adminurl_str[256], /* URL for the admin page */ + type_str[32], /* Type to string buffer */ + state_str[32], /* State to string buffer */ + rp_str[1024], /* Queue name string buffer */ + air_str[1024], /* auth-info-required string buffer */ + *keyvalue[32][2]; /* Table of key/value pairs */ + + + /* + * Load up the key value pairs... + */ + + i = 0; + + keyvalue[i ][0] = "txtvers"; + keyvalue[i++][1] = "1"; + + keyvalue[i ][0] = "qtotal"; + keyvalue[i++][1] = "1"; + + keyvalue[i ][0] = "rp"; + keyvalue[i++][1] = rp_str; + if (for_lpd) + strlcpy(rp_str, p->name, sizeof(rp_str)); + else + snprintf(rp_str, sizeof(rp_str), "%s/%s", + (p->type & CUPS_PRINTER_CLASS) ? "classes" : "printers", p->name); + + keyvalue[i ][0] = "ty"; + keyvalue[i++][1] = p->make_model ? p->make_model : "Unknown"; + + snprintf(admin_hostname, sizeof(admin_hostname), "%s.local.", DNSSDHostName); + httpAssembleURIf(HTTP_URI_CODING_ALL, adminurl_str, sizeof(adminurl_str), + "http", NULL, admin_hostname, DNSSDPort, "/%s/%s", + (p->type & CUPS_PRINTER_CLASS) ? "classes" : "printers", + p->name); + keyvalue[i ][0] = "adminurl"; + keyvalue[i++][1] = adminurl_str; + + keyvalue[i ][0] = "note"; + keyvalue[i++][1] = p->location ? p->location : ""; + + keyvalue[i ][0] = "priority"; + keyvalue[i++][1] = for_lpd ? "100" : "0"; + + keyvalue[i ][0] = "product"; + keyvalue[i++][1] = p->pc && p->pc->product ? p->pc->product : "Unknown"; + + keyvalue[i ][0] = "pdl"; + keyvalue[i++][1] = p->pdl ? p->pdl : "application/postscript"; + + if (get_auth_info_required(p, air_str, sizeof(air_str))) + { + keyvalue[i ][0] = "air"; + keyvalue[i++][1] = air_str; + } + + keyvalue[i ][0] = "UUID"; + keyvalue[i++][1] = p->uuid + 9; + +#ifdef HAVE_SSL + keyvalue[i ][0] = "TLS"; + keyvalue[i++][1] = "1.2"; +#endif /* HAVE_SSL */ + + keyvalue[i ][0] = "Transparent"; + keyvalue[i++][1] = "F"; + + keyvalue[i ][0] = "Binary"; + keyvalue[i++][1] = "F"; + + keyvalue[i ][0] = "Fax"; + keyvalue[i++][1] = (p->type & CUPS_PRINTER_FAX) ? "T" : "F"; + + keyvalue[i ][0] = "Color"; + keyvalue[i++][1] = (p->type & CUPS_PRINTER_COLOR) ? "T" : "F"; + + keyvalue[i ][0] = "Duplex"; + keyvalue[i++][1] = (p->type & CUPS_PRINTER_DUPLEX) ? "T" : "F"; + + keyvalue[i ][0] = "Staple"; + keyvalue[i++][1] = (p->type & CUPS_PRINTER_STAPLE) ? "T" : "F"; + + keyvalue[i ][0] = "Copies"; + keyvalue[i++][1] = (p->type & CUPS_PRINTER_COPIES) ? "T" : "F"; + + keyvalue[i ][0] = "Collate"; + keyvalue[i++][1] = (p->type & CUPS_PRINTER_COLLATE) ? "T" : "F"; + + keyvalue[i ][0] = "Punch"; + keyvalue[i++][1] = (p->type & CUPS_PRINTER_PUNCH) ? "T" : "F"; + + keyvalue[i ][0] = "Bind"; + keyvalue[i++][1] = (p->type & CUPS_PRINTER_BIND) ? "T" : "F"; + + keyvalue[i ][0] = "Sort"; + keyvalue[i++][1] = (p->type & CUPS_PRINTER_SORT) ? "T" : "F"; + + keyvalue[i ][0] = "Scan"; + keyvalue[i++][1] = (p->type & CUPS_PRINTER_MFP) ? "T" : "F"; + + snprintf(type_str, sizeof(type_str), "0x%X", p->type | CUPS_PRINTER_REMOTE); + snprintf(state_str, sizeof(state_str), "%d", p->state); + + keyvalue[i ][0] = "printer-state"; + keyvalue[i++][1] = state_str; + + keyvalue[i ][0] = "printer-type"; + keyvalue[i++][1] = type_str; + + /* + * Then pack them into a proper txt record... + */ + + return (dnssdPackTxtRecord(txt_len, keyvalue, i)); +} + + +/* + * 'dnssdComparePrinters()' - Compare the registered names of two printers. + */ + +static int /* O - Result of comparison */ +dnssdComparePrinters(cupsd_printer_t *a,/* I - First printer */ + cupsd_printer_t *b)/* I - Second printer */ +{ + return (_cups_strcasecmp(a->reg_name, b->reg_name)); +} + + +/* + * 'dnssdDeregisterPrinter()' - Stop sending broadcast information for a + * printer. + */ + +static void +dnssdDeregisterPrinter( + cupsd_printer_t *p) /* I - Printer */ +{ + cupsdLogMessage(CUPSD_LOG_DEBUG2, "dnssdDeregisterPrinter(%s)", p->name); + + /* + * Closing the socket deregisters the service + */ + + if (p->ipp_ref) + { + DNSServiceRefDeallocate(p->ipp_ref); + p->ipp_ref = NULL; + } + + if (p->ipp_txt) + { + /* + * p->ipp_txt is malloc'd, not _cupsStrAlloc'd... + */ + + free(p->ipp_txt); + p->ipp_txt = NULL; + } + + if (p->printer_ref) + { + DNSServiceRefDeallocate(p->printer_ref); + p->printer_ref = NULL; + } + + if (p->printer_txt) + { + /* + * p->printer_txt is malloc'd, not _cupsStrAlloc'd... + */ + + free(p->printer_txt); + p->printer_txt = NULL; + } + + /* + * Remove the printer from the array of DNS-SD printers, then clear the + * registered name... + */ + + cupsArrayRemove(DNSSDPrinters, p); + cupsdClearString(&p->reg_name); +} + + +/* + * 'dnssdPackTxtRecord()' - Pack an array of key/value pairs into the + * TXT record format. + */ + +static char * /* O - TXT record */ +dnssdPackTxtRecord(int *txt_len, /* O - TXT record length */ + char *keyvalue[][2], /* I - Table of key value pairs */ + int count) /* I - Items in table */ +{ + int i; /* Looping var */ + int length; /* Length of TXT record */ + int length2; /* Length of value */ + char *txtRecord; /* TXT record buffer */ + char *cursor; /* Looping pointer */ + + + /* + * Calculate the buffer size + */ + + if (count <= 0) + return (NULL); + + for (length = i = 0; i < count; i++) + length += 1 + strlen(keyvalue[i][0]) + + (keyvalue[i][1] ? 1 + strlen(keyvalue[i][1]) : 0); + + /* + * Allocate and fill it + */ + + txtRecord = malloc(length); + if (txtRecord) + { + *txt_len = length; + + for (cursor = txtRecord, i = 0; i < count; i++) + { + /* + * Drop in the p-string style length byte followed by the data + */ + + length = strlen(keyvalue[i][0]); + length2 = keyvalue[i][1] ? 1 + strlen(keyvalue[i][1]) : 0; + + *cursor++ = (unsigned char)(length + length2); + + memcpy(cursor, keyvalue[i][0], length); + cursor += length; + + if (length2) + { + length2 --; + *cursor++ = '='; + memcpy(cursor, keyvalue[i][1], length2); + cursor += length2; + } + } + } + + return (txtRecord); +} + + +/* + * 'dnssdRegisterCallback()' - DNSServiceRegister callback. + */ + +static void +dnssdRegisterCallback( + DNSServiceRef sdRef, /* I - DNS Service reference */ + DNSServiceFlags flags, /* I - Reserved for future use */ + DNSServiceErrorType errorCode, /* I - Error code */ + const char *name, /* I - Service name */ + const char *regtype, /* I - Service type */ + const char *domain, /* I - Domain. ".local" for now */ + void *context) /* I - User-defined context */ +{ + cupsd_printer_t *p = (cupsd_printer_t *)context; + /* Current printer */ + + + (void)sdRef; + (void)flags; + (void)domain; + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "dnssdRegisterCallback(%s, %s) for %s (%s)", + name, regtype, p ? p->name : "Web Interface", + p ? (p->reg_name ? p->reg_name : "(null)") : "NA"); + + if (errorCode) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "DNSServiceRegister failed with error %d", (int)errorCode); + return; + } + else if (p && (!p->reg_name || _cups_strcasecmp(name, p->reg_name))) + { + cupsdLogMessage(CUPSD_LOG_INFO, "Using service name \"%s\" for \"%s\"", + name, p->name); + + cupsArrayRemove(DNSSDPrinters, p); + cupsdSetString(&p->reg_name, name); + cupsArrayAdd(DNSSDPrinters, p); + + LastEvent |= CUPSD_EVENT_PRINTER_MODIFIED; + } +} + + +/* + * 'dnssdRegisterPrinter()' - Start sending broadcast information for a printer + * or update the broadcast contents. + */ + +static void +dnssdRegisterPrinter(cupsd_printer_t *p)/* I - Printer */ +{ + DNSServiceErrorType se; /* dnssd errors */ + char *ipp_txt, /* IPP TXT record buffer */ + *printer_txt, /* LPD TXT record buffer */ + name[1024], /* Service name */ + *nameptr; /* Pointer into name */ + int ipp_len, /* IPP TXT record length */ + printer_len, /* LPD TXT record length */ + printer_port; /* LPD port number */ + const char *regtype; /* Registration type */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "dnssdRegisterPrinter(%s) %s", p->name, + !p->ipp_ref ? "new" : "update"); + + /* + * If per-printer sharing was just disabled make sure we're not + * registered before returning. + */ + + if (!p->shared) + { + dnssdDeregisterPrinter(p); + return; + } + + /* + * The registered name takes the form of "<printer-info> @ <computer name>"... + */ + + if (p->info && strlen(p->info) > 0) + { + if (DNSSDComputerName) + snprintf(name, sizeof(name), "%s @ %s", p->info, DNSSDComputerName); + else + strlcpy(name, p->info, sizeof(name)); + } + else if (DNSSDComputerName) + snprintf(name, sizeof(name), "%s @ %s", p->name, DNSSDComputerName); + else + strlcpy(name, p->name, sizeof(name)); + + /* + * If an existing printer was renamed, unregister it and start over... + */ + + if (p->reg_name && strcmp(p->reg_name, name)) + dnssdDeregisterPrinter(p); + + if (!p->reg_name) + { + cupsdSetString(&p->reg_name, name); + cupsArrayAdd(DNSSDPrinters, p); + } + + /* + * Register IPP and (optionally) LPD... + */ + + ipp_len = 0; /* anti-compiler-warning-code */ + ipp_txt = dnssdBuildTxtRecord(&ipp_len, p, 0); + + if (p->ipp_ref && + (ipp_len != p->ipp_len || memcmp(ipp_txt, p->ipp_txt, ipp_len))) + { + /* + * Update the existing registration... + */ + + /* A TTL of 0 means use record's original value (Radar 3176248) */ + if ((se = DNSServiceUpdateRecord(p->ipp_ref, NULL, 0, ipp_len, ipp_txt, + 0)) == kDNSServiceErr_NoError) + { + if (p->ipp_txt) + free(p->ipp_txt); + + p->ipp_txt = ipp_txt; + p->ipp_len = ipp_len; + ipp_txt = NULL; + } + else + { + /* + * Failed to update record, lets close this reference and move on... + */ + + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to update IPP DNS-SD record for %s - %d", p->name, + se); + + DNSServiceRefDeallocate(p->ipp_ref); + p->ipp_ref = NULL; + } + } + + if (!p->ipp_ref) + { + /* + * Initial registration. Use the _fax-ipp regtype for fax queues... + */ + + regtype = (p->type & CUPS_PRINTER_FAX) ? "_fax-ipp._tcp" : DNSSDRegType; + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Registering DNS-SD printer %s with name \"%s\" and " + "type \"%s\"", p->name, name, regtype); + + /* + * Register the queue, dropping characters as needed until we succeed... + */ + + nameptr = name + strlen(name); + + do + { + p->ipp_ref = DNSSDRef; + if ((se = DNSServiceRegister(&p->ipp_ref, kDNSServiceFlagsShareConnection, + 0, name, regtype, NULL, NULL, + htons(DNSSDPort), ipp_len, ipp_txt, + dnssdRegisterCallback, + p)) == kDNSServiceErr_BadParam) + { + /* + * Name is too long, drop trailing characters, taking into account + * UTF-8 encoding... + */ + + nameptr --; + + while (nameptr > name && (*nameptr & 0xc0) == 0x80) + nameptr --; + + if (nameptr > name) + *nameptr = '\0'; + } + } + while (se == kDNSServiceErr_BadParam && nameptr > name); + + if (se == kDNSServiceErr_NoError) + { + p->ipp_txt = ipp_txt; + p->ipp_len = ipp_len; + ipp_txt = NULL; + } + else + cupsdLogMessage(CUPSD_LOG_WARN, + "DNS-SD IPP registration of \"%s\" failed: %d", + p->name, se); + } + + if (ipp_txt) + free(ipp_txt); + + if (BrowseLocalProtocols & BROWSE_LPD) + { + printer_len = 0; /* anti-compiler-warning-code */ + printer_port = 515; + printer_txt = dnssdBuildTxtRecord(&printer_len, p, 1); + } + else + { + printer_len = 0; + printer_port = 0; + printer_txt = NULL; + } + + if (p->printer_ref && + (printer_len != p->printer_len || + memcmp(printer_txt, p->printer_txt, printer_len))) + { + /* + * Update the existing registration... + */ + + /* A TTL of 0 means use record's original value (Radar 3176248) */ + if ((se = DNSServiceUpdateRecord(p->printer_ref, NULL, 0, printer_len, + printer_txt, + 0)) == kDNSServiceErr_NoError) + { + if (p->printer_txt) + free(p->printer_txt); + + p->printer_txt = printer_txt; + p->printer_len = printer_len; + printer_txt = NULL; + } + else + { + /* + * Failed to update record, lets close this reference and move on... + */ + + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to update LPD DNS-SD record for %s - %d", + p->name, se); + + DNSServiceRefDeallocate(p->printer_ref); + p->printer_ref = NULL; + } + } + + if (!p->printer_ref) + { + /* + * Initial registration... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Registering DNS-SD printer %s with name \"%s\" and " + "type \"_printer._tcp\"", p->name, name); + + p->printer_ref = DNSSDRef; + if ((se = DNSServiceRegister(&p->printer_ref, + kDNSServiceFlagsShareConnection, + 0, name, "_printer._tcp", NULL, NULL, + htons(printer_port), printer_len, printer_txt, + dnssdRegisterCallback, + p)) == kDNSServiceErr_NoError) + { + p->printer_txt = printer_txt; + p->printer_len = printer_len; + printer_txt = NULL; + } + else + cupsdLogMessage(CUPSD_LOG_WARN, + "DNS-SD LPD registration of \"%s\" failed: %d", + p->name, se); + } + + if (printer_txt) + free(printer_txt); +} + + +/* + * 'dnssdStop()' - Stop all DNS-SD registrations. + */ + +static void +dnssdStop(void) +{ + cupsd_printer_t *p; /* Current printer */ + + + /* + * De-register the individual printers + */ + + for (p = (cupsd_printer_t *)cupsArrayFirst(Printers); + p; + p = (cupsd_printer_t *)cupsArrayNext(Printers)) + dnssdDeregisterPrinter(p); + + /* + * Shutdown the rest of the service refs... + */ + + if (WebIFRef) + { + DNSServiceRefDeallocate(WebIFRef); + WebIFRef = NULL; + } + + if (RemoteRef) + { + DNSServiceRefDeallocate(RemoteRef); + RemoteRef = NULL; + } + + cupsdRemoveSelect(DNSServiceRefSockFD(DNSSDRef)); + + DNSServiceRefDeallocate(DNSSDRef); + DNSSDRef = NULL; + + cupsArrayDelete(DNSSDPrinters); + DNSSDPrinters = NULL; + + DNSSDPort = 0; +} + + +/* + * 'dnssdUpdate()' - Handle DNS-SD queries. + */ + +static void +dnssdUpdate(void) +{ + DNSServiceErrorType sdErr; /* Service discovery error */ + + + if ((sdErr = DNSServiceProcessResult(DNSSDRef)) != kDNSServiceErr_NoError) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "DNS Service Discovery registration error %d!", + sdErr); + dnssdStop(); + } +} +#endif /* HAVE_DNSSD */ + + +/* + * 'get_auth_info_required()' - Get the auth-info-required value to advertise. + */ + +static char * /* O - String or NULL if none */ +get_auth_info_required( + cupsd_printer_t *p, /* I - Printer */ + char *buffer, /* I - Value buffer */ + size_t bufsize) /* I - Size of value buffer */ +{ + cupsd_location_t *auth; /* Pointer to authentication element */ + char resource[1024]; /* Printer/class resource path */ + + + /* + * If auth-info-required is set for this printer, return that... + */ + + if (p->num_auth_info_required > 0 && strcmp(p->auth_info_required[0], "none")) + { + int i; /* Looping var */ + char *bufptr; /* Pointer into buffer */ + + for (i = 0, bufptr = buffer; i < p->num_auth_info_required; i ++) + { + if (bufptr >= (buffer + bufsize - 2)) + break; + + if (i) + *bufptr++ = ','; + + strlcpy(bufptr, p->auth_info_required[i], bufsize - (bufptr - buffer)); + bufptr += strlen(bufptr); + } + + return (buffer); + } + + /* + * Figure out the authentication data requirements to advertise... + */ + + if (p->type & CUPS_PRINTER_CLASS) + snprintf(resource, sizeof(resource), "/classes/%s", p->name); + else + snprintf(resource, sizeof(resource), "/printers/%s", p->name); + + if ((auth = cupsdFindBest(resource, HTTP_POST)) == NULL || + auth->type == CUPSD_AUTH_NONE) + auth = cupsdFindPolicyOp(p->op_policy_ptr, IPP_PRINT_JOB); + + if (auth) + { + int auth_type; /* Authentication type */ + + if ((auth_type = auth->type) == CUPSD_AUTH_DEFAULT) + auth_type = DefaultAuthType; + + switch (auth_type) + { + case CUPSD_AUTH_NONE : + return (NULL); + + case CUPSD_AUTH_NEGOTIATE : + strlcpy(buffer, "negotiate", bufsize); + break; + + default : + strlcpy(buffer, "username,password", bufsize); + break; + } + + return (buffer); + } + + return ("none"); +} + + +#ifdef __APPLE__ +/* + * 'get_hostconfig()' - Get an /etc/hostconfig service setting. + */ + +static int /* O - 1 for YES or AUTOMATIC, 0 for NO */ +get_hostconfig(const char *name) /* I - Name of service */ +{ + cups_file_t *fp; /* Hostconfig file */ + char line[1024], /* Line from file */ + *ptr; /* Pointer to value */ + int state = 1; /* State of service */ + + + /* + * Try opening the /etc/hostconfig file; if we can't open it, assume that + * the service is enabled/auto. + */ + + if ((fp = cupsFileOpen("/etc/hostconfig", "r")) != NULL) + { + /* + * Read lines from the file until we find the service... + */ + + while (cupsFileGets(fp, line, sizeof(line))) + { + if (line[0] == '#' || (ptr = strchr(line, '=')) == NULL) + continue; + + *ptr++ = '\0'; + + if (!_cups_strcasecmp(line, name)) + { + /* + * Found the service, see if it is set to "-NO-"... + */ + + if (!_cups_strncasecmp(ptr, "-NO-", 4)) + state = 0; + break; + } + } + + cupsFileClose(fp); + } + + return (state); +} +#endif /* __APPLE__ */ + + +/* + * 'is_local_queue()' - Determine whether the URI points at a local queue. + */ + +static int /* O - 1 = local, 0 = remote, -1 = bad URI */ +is_local_queue(const char *uri, /* I - Printer URI */ + char *host, /* O - Host string */ + int hostlen, /* I - Length of host buffer */ + char *resource, /* O - Resource string */ + int resourcelen) /* I - Length of resource buffer */ +{ + char scheme[32], /* Scheme portion of URI */ + username[HTTP_MAX_URI]; /* Username portion of URI */ + int port; /* Port portion of URI */ + cupsd_netif_t *iface; /* Network interface */ + + + /* + * Pull the URI apart to see if this is a local or remote printer... + */ + + if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), + username, sizeof(username), host, hostlen, &port, + resource, resourcelen) < HTTP_URI_OK) + return (-1); + + DEBUG_printf(("host=\"%s\", ServerName=\"%s\"\n", host, ServerName)); + + /* + * Check for local server addresses... + */ + + if (!_cups_strcasecmp(host, ServerName) && port == LocalPort) + return (1); + + cupsdNetIFUpdate(); + + for (iface = (cupsd_netif_t *)cupsArrayFirst(NetIFList); + iface; + iface = (cupsd_netif_t *)cupsArrayNext(NetIFList)) + if (!_cups_strcasecmp(host, iface->hostname) && port == iface->port) + return (1); + + /* + * If we get here, the printer is remote... + */ + + return (0); +} + + +/* + * 'process_browse_data()' - Process new browse data. + */ + +static void +process_browse_data( + const char *uri, /* I - URI of printer/class */ + const char *host, /* I - Hostname */ + const char *resource, /* I - Resource path */ + cups_ptype_t type, /* I - Printer type */ + ipp_pstate_t state, /* I - Printer state */ + const char *location, /* I - Printer location */ + const char *info, /* I - Printer information */ + const char *make_model, /* I - Printer make and model */ + int num_attrs, /* I - Number of attributes */ + cups_option_t *attrs) /* I - Attributes */ +{ + int i; /* Looping var */ + int update; /* Update printer attributes? */ + char finaluri[HTTP_MAX_URI], /* Final URI for printer */ + name[IPP_MAX_NAME], /* Name of printer */ + newname[IPP_MAX_NAME], /* New name of printer */ + *hptr, /* Pointer into hostname */ + *sptr; /* Pointer into ServerName */ + const char *shortname; /* Short queue name (queue) */ + char local_make_model[IPP_MAX_NAME]; + /* Local make and model */ + cupsd_printer_t *p; /* Printer information */ + const char *ipp_options, /* ipp-options value */ + *lease_duration, /* lease-duration value */ + *uuid; /* uuid value */ + int is_class; /* Is this queue a class? */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "process_browse_data(uri=\"%s\", host=\"%s\", " + "resource=\"%s\", type=%x, state=%d, location=\"%s\", " + "info=\"%s\", make_model=\"%s\", num_attrs=%d, attrs=%p)", + uri, host, resource, type, state, + location ? location : "(nil)", info ? info : "(nil)", + make_model ? make_model : "(nil)", num_attrs, attrs); + + /* + * Determine if the URI contains any illegal characters in it... + */ + + if (strncmp(uri, "ipp://", 6) || !host[0] || + (strncmp(resource, "/printers/", 10) && + strncmp(resource, "/classes/", 9))) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Bad printer URI in browse data: %s", uri); + return; + } + + if (strchr(resource, '?') || + (!strncmp(resource, "/printers/", 10) && strchr(resource + 10, '/')) || + (!strncmp(resource, "/classes/", 9) && strchr(resource + 9, '/'))) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Bad resource in browse data: %s", + resource); + return; + } + + /* + * OK, this isn't a local printer; add any remote options... + */ + + ipp_options = cupsGetOption("ipp-options", num_attrs, attrs); + + if (BrowseRemoteOptions) + { + if (BrowseRemoteOptions[0] == '?') + { + /* + * Override server-supplied options... + */ + + snprintf(finaluri, sizeof(finaluri), "%s%s", uri, BrowseRemoteOptions); + } + else if (ipp_options) + { + /* + * Combine the server and local options... + */ + + snprintf(finaluri, sizeof(finaluri), "%s?%s+%s", uri, ipp_options, + BrowseRemoteOptions); + } + else + { + /* + * Just use the local options... + */ + + snprintf(finaluri, sizeof(finaluri), "%s?%s", uri, BrowseRemoteOptions); + } + + uri = finaluri; + } + else if (ipp_options) + { + /* + * Just use the server-supplied options... + */ + + snprintf(finaluri, sizeof(finaluri), "%s?%s", uri, ipp_options); + uri = finaluri; + } + + /* + * See if we already have it listed in the Printers list, and add it if not... + */ + + type |= CUPS_PRINTER_REMOTE | CUPS_PRINTER_DISCOVERED; + type &= ~CUPS_PRINTER_IMPLICIT; + update = 0; + hptr = strchr(host, '.'); + sptr = strchr(ServerName, '.'); + is_class = type & CUPS_PRINTER_CLASS; + uuid = cupsGetOption("uuid", num_attrs, attrs); + + if (!ServerNameIsIP && sptr != NULL && hptr != NULL) + { + /* + * Strip the common domain name components... + */ + + while (hptr != NULL) + { + if (!_cups_strcasecmp(hptr, sptr)) + { + *hptr = '\0'; + break; + } + else + hptr = strchr(hptr + 1, '.'); + } + } + + if (is_class) + { + /* + * Remote destination is a class... + */ + + if (!strncmp(resource, "/classes/", 9)) + snprintf(name, sizeof(name), "%s@%s", resource + 9, host); + else + return; + + shortname = resource + 9; + } + else + { + /* + * Remote destination is a printer... + */ + + if (!strncmp(resource, "/printers/", 10)) + snprintf(name, sizeof(name), "%s@%s", resource + 10, host); + else + return; + + shortname = resource + 10; + } + + if (hptr && !*hptr) + *hptr = '.'; /* Resource FQDN */ + + if ((p = cupsdFindDest(name)) == NULL && BrowseShortNames) + { + /* + * Long name doesn't exist, try short name... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG, "process_browse_data: %s not found...", + name); + + if ((p = cupsdFindDest(shortname)) == NULL) + { + /* + * Short name doesn't exist, use it for this shared queue. + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "process_browse_data: %s not found...", + shortname); + strlcpy(name, shortname, sizeof(name)); + } + else + { + /* + * Short name exists... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "process_browse_data: %s found, type=%x, hostname=%s...", + shortname, p->type, p->hostname ? p->hostname : "(nil)"); + + if (p->type & CUPS_PRINTER_IMPLICIT) + p = NULL; /* Don't replace implicit classes */ + else if (p->hostname && _cups_strcasecmp(p->hostname, host)) + { + /* + * Short name exists but is for a different host. If this is a remote + * queue, rename it and use the long name... + */ + + if (p->type & CUPS_PRINTER_REMOTE) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Renamed remote %s \"%s\" to \"%s@%s\"...", + is_class ? "class" : "printer", p->name, p->name, + p->hostname); + cupsdAddEvent(CUPSD_EVENT_PRINTER_DELETED, p, NULL, + "%s \'%s\' deleted by directory services.", + is_class ? "Class" : "Printer", p->name); + + snprintf(newname, sizeof(newname), "%s@%s", p->name, p->hostname); + cupsdRenamePrinter(p, newname); + + cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED, p, NULL, + "%s \'%s\' added by directory services.", + is_class ? "Class" : "Printer", p->name); + } + + /* + * Force creation with long name... + */ + + p = NULL; + } + } + } + else if (p) + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "process_browse_data: %s found, type=%x, hostname=%s...", + name, p->type, p->hostname ? p->hostname : "(nil)"); + + if (!p) + { + /* + * Queue doesn't exist; add it... + */ + + if (is_class) + p = cupsdAddClass(name); + else + p = cupsdAddPrinter(name); + + if (!p) + return; + + cupsdClearString(&(p->hostname)); + + cupsdLogMessage(CUPSD_LOG_DEBUG, "Added remote %s \"%s\"...", + is_class ? "class" : "printer", name); + + cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED, p, NULL, + "%s \'%s\' added by directory services.", + is_class ? "Class" : "Printer", name); + + /* + * Force the URI to point to the real server... + */ + + p->type = type & ~CUPS_PRINTER_REJECTING; + p->accepting = 1; + + cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP); + } + + if (!p->hostname) + { + /* + * Hostname not set, so this must be a cached remote printer + * that was created for a pending print job... + */ + + cupsdSetString(&p->hostname, host); + cupsdSetString(&p->uri, uri); + cupsdSetString(&p->device_uri, uri); + update = 1; + + cupsdMarkDirty(CUPSD_DIRTY_REMOTE); + } + + /* + * Update the state... + */ + + p->state = state; + p->browse_time = time(NULL); + + if ((lease_duration = cupsGetOption("lease-duration", num_attrs, + attrs)) != NULL) + { + /* + * Grab the lease-duration for the browse data; anything less then 1 + * second or more than 1 week gets the default BrowseTimeout... + */ + + i = atoi(lease_duration); + if (i < 1 || i > 604800) + i = BrowseTimeout; + + p->browse_expire = p->browse_time + i; + } + else + p->browse_expire = p->browse_time + BrowseTimeout; + + if (type & CUPS_PRINTER_REJECTING) + { + type &= ~CUPS_PRINTER_REJECTING; + + if (p->accepting) + { + update = 1; + p->accepting = 0; + } + } + else if (!p->accepting) + { + update = 1; + p->accepting = 1; + } + + if (p->type != type) + { + p->type = type; + update = 1; + } + + if (uuid && strcmp(p->uuid, uuid)) + { + cupsdSetString(&p->uuid, uuid); + update = 1; + } + + if (location && (!p->location || strcmp(p->location, location))) + { + cupsdSetString(&p->location, location); + update = 1; + } + + if (info && (!p->info || strcmp(p->info, info))) + { + cupsdSetString(&p->info, info); + update = 1; + + cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP | CUPSD_DIRTY_REMOTE); + } + + if (!make_model || !make_model[0]) + { + if (is_class) + snprintf(local_make_model, sizeof(local_make_model), + "Remote Class on %s", host); + else + snprintf(local_make_model, sizeof(local_make_model), + "Remote Printer on %s", host); + } + else + snprintf(local_make_model, sizeof(local_make_model), + "%s on %s", make_model, host); + + if (!p->make_model || strcmp(p->make_model, local_make_model)) + { + cupsdSetString(&p->make_model, local_make_model); + update = 1; + } + + if (p->num_options) + { + if (!update && !(type & CUPS_PRINTER_DELETE)) + { + /* + * See if we need to update the attributes... + */ + + if (p->num_options != num_attrs) + update = 1; + else + { + for (i = 0; i < num_attrs; i ++) + if (strcmp(attrs[i].name, p->options[i].name) || + (!attrs[i].value != !p->options[i].value) || + (attrs[i].value && strcmp(attrs[i].value, p->options[i].value))) + { + update = 1; + break; + } + } + } + + /* + * Free the old options... + */ + + cupsFreeOptions(p->num_options, p->options); + } + + p->num_options = num_attrs; + p->options = attrs; + + if (type & CUPS_PRINTER_DELETE) + { + cupsdAddEvent(CUPSD_EVENT_PRINTER_DELETED, p, NULL, + "%s \'%s\' deleted by directory services.", + is_class ? "Class" : "Printer", p->name); + + cupsdExpireSubscriptions(p, NULL); + + cupsdDeletePrinter(p, 1); + cupsdUpdateImplicitClasses(); + cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP | CUPSD_DIRTY_REMOTE); + } + else if (update) + { + cupsdSetPrinterAttrs(p); + cupsdUpdateImplicitClasses(); + } + + /* + * See if we have a default printer... If not, make the first network + * default printer the default. + */ + + if (DefaultPrinter == NULL && Printers != NULL && UseNetworkDefault) + { + /* + * Find the first network default printer and use it... + */ + + for (p = (cupsd_printer_t *)cupsArrayFirst(Printers); + p; + p = (cupsd_printer_t *)cupsArrayNext(Printers)) + if (p->type & CUPS_PRINTER_DEFAULT) + { + DefaultPrinter = p; + cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP | CUPSD_DIRTY_REMOTE); + break; + } + } + + /* + * Do auto-classing if needed... + */ + + process_implicit_classes(); +} + + +/* + * 'process_implicit_classes()' - Create/update implicit classes as needed. + */ + +static void +process_implicit_classes(void) +{ + int i; /* Looping var */ + int update; /* Update printer attributes? */ + char name[IPP_MAX_NAME], /* Name of printer */ + *hptr; /* Pointer into hostname */ + cupsd_printer_t *p, /* Printer information */ + *pclass, /* Printer class */ + *first; /* First printer in class */ + int offset, /* Offset of name */ + len; /* Length of name */ + + + if (!ImplicitClasses || !Printers) + return; + + /* + * Loop through all available printers and create classes as needed... + */ + + for (p = (cupsd_printer_t *)cupsArrayFirst(Printers), len = 0, offset = 0, + update = 0, pclass = NULL, first = NULL; + p != NULL; + p = (cupsd_printer_t *)cupsArrayNext(Printers)) + { + /* + * Skip implicit classes... + */ + + if (p->type & CUPS_PRINTER_IMPLICIT) + { + len = 0; + continue; + } + + /* + * If len == 0, get the length of this printer name up to the "@" + * sign (if any). + */ + + cupsArraySave(Printers); + + if (len > 0 && + !_cups_strncasecmp(p->name, name + offset, len) && + (p->name[len] == '\0' || p->name[len] == '@')) + { + /* + * We have more than one printer with the same name; see if + * we have a class, and if this printer is a member... + */ + + if (pclass && _cups_strcasecmp(pclass->name, name)) + { + if (update) + cupsdSetPrinterAttrs(pclass); + + update = 0; + pclass = NULL; + } + + if (!pclass && (pclass = cupsdFindDest(name)) == NULL) + { + /* + * Need to add the class... + */ + + pclass = cupsdAddPrinter(name); + cupsArrayAdd(ImplicitPrinters, pclass); + + pclass->type |= CUPS_PRINTER_IMPLICIT; + pclass->accepting = 1; + pclass->state = IPP_PRINTER_IDLE; + + cupsdSetString(&pclass->location, p->location); + cupsdSetString(&pclass->info, p->info); + + cupsdSetString(&pclass->job_sheets[0], p->job_sheets[0]); + cupsdSetString(&pclass->job_sheets[1], p->job_sheets[1]); + + update = 1; + + cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP | CUPSD_DIRTY_REMOTE); + + cupsdLogMessage(CUPSD_LOG_DEBUG, "Added implicit class \"%s\"...", + name); + cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED, p, NULL, + "Implicit class \'%s\' added by directory services.", + name); + } + + if (first != NULL) + { + for (i = 0; i < pclass->num_printers; i ++) + if (pclass->printers[i] == first) + break; + + if (i >= pclass->num_printers) + { + first->in_implicit_class = 1; + cupsdAddPrinterToClass(pclass, first); + } + + first = NULL; + } + + for (i = 0; i < pclass->num_printers; i ++) + if (pclass->printers[i] == p) + break; + + if (i >= pclass->num_printers) + { + p->in_implicit_class = 1; + cupsdAddPrinterToClass(pclass, p); + update = 1; + } + } + else + { + /* + * First time around; just get name length and mark it as first + * in the list... + */ + + if ((hptr = strchr(p->name, '@')) != NULL) + len = hptr - p->name; + else + len = strlen(p->name); + + if (len >= sizeof(name)) + { + /* + * If the printer name length somehow is greater than we normally allow, + * skip this printer... + */ + + len = 0; + cupsArrayRestore(Printers); + continue; + } + + strncpy(name, p->name, len); + name[len] = '\0'; + offset = 0; + + if ((first = (hptr ? cupsdFindDest(name) : p)) != NULL && + !(first->type & CUPS_PRINTER_IMPLICIT)) + { + /* + * Can't use same name as a local printer; add "Any" to the + * front of the name, unless we have explicitly disabled + * the "ImplicitAnyClasses"... + */ + + if (ImplicitAnyClasses && len < (sizeof(name) - 4)) + { + /* + * Add "Any" to the class name... + */ + + strcpy(name, "Any"); + strncpy(name + 3, p->name, len); + name[len + 3] = '\0'; + offset = 3; + } + else + { + /* + * Don't create an implicit class if we have a local printer + * with the same name... + */ + + len = 0; + cupsArrayRestore(Printers); + continue; + } + } + + first = p; + } + + cupsArrayRestore(Printers); + } + + /* + * Update the last printer class as needed... + */ + + if (pclass && update) + cupsdSetPrinterAttrs(pclass); +} + + +/* + * 'send_cups_browse()' - Send new browsing information using the CUPS + * protocol. + */ + +static void +send_cups_browse(cupsd_printer_t *p) /* I - Printer to send */ +{ + int i; /* Looping var */ + cups_ptype_t type; /* Printer type */ + cupsd_dirsvc_addr_t *b; /* Browse address */ + int bytes; /* Length of packet */ + char packet[1453], /* Browse data packet */ + uri[1024], /* Printer URI */ + location[1024], /* printer-location */ + info[1024], /* printer-info */ + make_model[1024], + /* printer-make-and-model */ + air[1024]; /* auth-info-required */ + cupsd_netif_t *iface; /* Network interface */ + + + /* + * Figure out the printer type value... + */ + + type = p->type | CUPS_PRINTER_REMOTE; + + if (!p->accepting) + type |= CUPS_PRINTER_REJECTING; + + if (p == DefaultPrinter) + type |= CUPS_PRINTER_DEFAULT; + + /* + * Remove quotes from printer-info, printer-location, and + * printer-make-and-model attributes... + */ + + dequote(location, p->location, sizeof(location)); + dequote(info, p->info, sizeof(info)); + + if (p->make_model) + dequote(make_model, p->make_model, sizeof(make_model)); + else if (p->type & CUPS_PRINTER_CLASS) + { + if (p->num_printers > 0 && p->printers[0]->make_model) + strlcpy(make_model, p->printers[0]->make_model, sizeof(make_model)); + else + strlcpy(make_model, "Local Printer Class", sizeof(make_model)); + } + else if (p->raw) + strlcpy(make_model, "Local Raw Printer", sizeof(make_model)); + else + strlcpy(make_model, "Local System V Printer", sizeof(make_model)); + + if (get_auth_info_required(p, packet, sizeof(packet))) + snprintf(air, sizeof(air), " auth-info-required=%s", packet); + else + air[0] = '\0'; + + /* + * Send a packet to each browse address... + */ + + for (i = NumBrowsers, b = Browsers; i > 0; i --, b ++) + if (b->iface[0]) + { + /* + * Send the browse packet to one or more interfaces... + */ + + if (!strcmp(b->iface, "*")) + { + /* + * Send to all local interfaces... + */ + + cupsdNetIFUpdate(); + + for (iface = (cupsd_netif_t *)cupsArrayFirst(NetIFList); + iface; + iface = (cupsd_netif_t *)cupsArrayNext(NetIFList)) + { + /* + * Only send to local, IPv4 interfaces... + */ + + if (!iface->is_local || !iface->port || + iface->address.addr.sa_family != AF_INET) + continue; + + httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, + iface->hostname, iface->port, + (p->type & CUPS_PRINTER_CLASS) ? "/classes/%s" : + "/printers/%s", + p->name); + snprintf(packet, sizeof(packet), + "%x %x %s \"%s\" \"%s\" \"%s\" %s%s uuid=%s\n", + type, p->state, uri, location, info, make_model, + p->browse_attrs ? p->browse_attrs : "", air, p->uuid); + + bytes = strlen(packet); + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "cupsdSendBrowseList: (%d bytes to \"%s\") %s", bytes, + iface->name, packet); + + iface->broadcast.ipv4.sin_port = htons(BrowsePort); + + sendto(BrowseSocket, packet, bytes, 0, + (struct sockaddr *)&(iface->broadcast), + httpAddrLength(&(iface->broadcast))); + } + } + else if ((iface = cupsdNetIFFind(b->iface)) != NULL) + { + /* + * Send to the named interface using the IPv4 address... + */ + + while (iface) + if (strcmp(b->iface, iface->name)) + { + iface = NULL; + break; + } + else if (iface->address.addr.sa_family == AF_INET && iface->port) + break; + else + iface = (cupsd_netif_t *)cupsArrayNext(NetIFList); + + if (iface) + { + httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, + iface->hostname, iface->port, + (p->type & CUPS_PRINTER_CLASS) ? "/classes/%s" : + "/printers/%s", + p->name); + snprintf(packet, sizeof(packet), + "%x %x %s \"%s\" \"%s\" \"%s\" %s%s uuid=%s\n", + type, p->state, uri, location, info, make_model, + p->browse_attrs ? p->browse_attrs : "", air, p->uuid); + + bytes = strlen(packet); + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "cupsdSendBrowseList: (%d bytes to \"%s\") %s", bytes, + iface->name, packet); + + iface->broadcast.ipv4.sin_port = htons(BrowsePort); + + sendto(BrowseSocket, packet, bytes, 0, + (struct sockaddr *)&(iface->broadcast), + httpAddrLength(&(iface->broadcast))); + } + } + } + else + { + /* + * Send the browse packet to the indicated address using + * the default server name... + */ + + snprintf(packet, sizeof(packet), + "%x %x %s \"%s\" \"%s\" \"%s\" %s%s uuid=%s\n", + type, p->state, p->uri, location, info, make_model, + p->browse_attrs ? p->browse_attrs : "", air, p->uuid); + + bytes = strlen(packet); + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "cupsdSendBrowseList: (%d bytes) %s", bytes, packet); + + if (sendto(BrowseSocket, packet, bytes, 0, + (struct sockaddr *)&(b->to), + httpAddrLength(&(b->to))) <= 0) + { + /* + * Unable to send browse packet, so remove this address from the + * list... + */ + + cupsdLogMessage(CUPSD_LOG_ERROR, + "cupsdSendBrowseList: sendto failed for browser " + "%d - %s.", + (int)(b - Browsers + 1), strerror(errno)); + + if (i > 1) + memmove(b, b + 1, (i - 1) * sizeof(cupsd_dirsvc_addr_t)); + + b --; + NumBrowsers --; + } + } +} + + +#ifdef HAVE_LDAP +/* + * '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) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "LDAP search failed with status %d: %s", + rc, ldap_err2string(rc)); + cupsdLogMessage(CUPSD_LOG_INFO, + "We try the LDAP search once again after reconnecting to " + "the server"); + 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) + cupsdLogMessage(CUPSD_LOG_DEBUG, + "ldap_search_rec: LDAP entry/object not found"); + else if (rc != LDAP_SUCCESS) + cupsdLogMessage(CUPSD_LOG_ERROR, + "ldap_search_rec: LDAP search failed with status %d: %s", + 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) + cupsdLogMessage(CUPSD_LOG_WARN, "Can't free LDAPMessage!"); + else if (rc == 0) + cupsdLogMessage(CUPSD_LOG_DEBUG2, "Freeing LDAPMessage was unnecessary"); +} + + +/* + * '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); + cupsdLogMessage(CUPSD_LOG_WARN, + "Failed to get LDAP value %s for %s!", + 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); + cupsdLogMessage(CUPSD_LOG_WARN, + "Attribute %s is too big! (dn: %s)", + attr, dn); + ldap_memfree(dn); + } + else + size = bval[0]->bv_len + 1; + + strlcpy(retval, bval[0]->bv_val, size); + 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); + cupsdLogMessage(CUPSD_LOG_WARN, "Failed to get LDAP value %s for %s!", + attr, dn); + ldap_memfree(dn); + } + else + { + strlcpy(retval, *value, maxsize); + ldap_value_free(value); + } +# endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ + + return (rc); +} + + +/* + * 'send_ldap_ou()' - Send LDAP ou registrations. + */ + +static void +send_ldap_ou(char *ou, /* I - Servername/ou to register */ + char *basedn, /* I - Our base dn */ + char *descstring) /* I - Description for ou */ +{ + int i; /* Looping var... */ + LDAPMod mods[3]; /* The 3 attributes we will be adding */ + LDAPMod *pmods[4]; /* Pointers to the 3 attributes + NULL */ + LDAPMessage *res, /* Search result token */ + *e; /* Current entry from search */ + int rc; /* LDAP status */ + int rcmod; /* LDAP status for modifications */ + char dn[1024], /* DN of the organizational unit we are adding */ + *desc[2], /* Change records */ + *ou_value[2]; + char old_desc[1024]; /* Old description */ + static const char * const objectClass_values[] = + { /* The 2 objectClass's we use in */ + "top", /* our LDAP entries */ + "organizationalUnit", + NULL + }; + static const char * const ou_attrs[] =/* CUPS LDAP attributes */ + { + "description", + NULL + }; + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "send_ldap_ou: %s", ou); + + /* + * Reconnect if LDAP Handle is invalid... + */ + + if (!BrowseLDAPHandle) + { + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "send_ldap_ou: LDAP Handle is invalid. Try reconnecting..."); + ldap_reconnect(); + return; + } + + /* + * Prepare ldap search... + */ + + snprintf(dn, sizeof(dn), "ou=%s, %s", ou, basedn); + cupsdLogMessage(CUPSD_LOG_DEBUG2, "send_ldap_ou: dn=\"%s\"", dn); + + ou_value[0] = ou; + ou_value[1] = NULL; + desc[0] = descstring; + desc[1] = NULL; + + mods[0].mod_type = "ou"; + mods[0].mod_values = ou_value; + mods[1].mod_type = "description"; + mods[1].mod_values = desc; + mods[2].mod_type = "objectClass"; + mods[2].mod_values = (char **)objectClass_values; + + rc = ldap_search_rec(BrowseLDAPHandle, dn, LDAP_SCOPE_BASE, NULL, + (char **)ou_attrs, 0, &res); + + /* + * If ldap search was not successfull then exit function... + */ + + if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_OBJECT) + return; + + /* + * Check if we need to insert or update the LDAP entry... + */ + + if (ldap_count_entries(BrowseLDAPHandle, res) > 0 && + rc != LDAP_NO_SUCH_OBJECT) + { + /* + * Printserver has already been registered, check if + * modification is required... + */ + + e = ldap_first_entry(BrowseLDAPHandle, res); + + /* + * Get the required values from this entry... + */ + + if (ldap_getval_firststring(BrowseLDAPHandle, e, "description", old_desc, + sizeof(old_desc)) == -1) + old_desc[0] = '\0'; + + /* + * Check if modification is required... + */ + + if ( strcmp(desc[0], old_desc) == 0 ) + { + /* + * LDAP entry for the printer exists. + * Printer has already been registered, + * no modifications required... + */ + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "send_ldap_ou: No updates required for %s", ou); + } + else + { + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "send_ldap_ou: Replace entry for %s", ou); + + for (i = 0; i < 3; i ++) + { + pmods[i] = mods + i; + pmods[i]->mod_op = LDAP_MOD_REPLACE; + } + pmods[i] = NULL; + +# if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 + if ((rcmod = ldap_modify_ext_s(BrowseLDAPHandle, dn, pmods, NULL, + NULL)) != LDAP_SUCCESS) +# else + if ((rcmod = ldap_modify_s(BrowseLDAPHandle, dn, pmods)) != LDAP_SUCCESS) +# endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "LDAP modify for %s failed with status %d: %s", + ou, rcmod, ldap_err2string(rcmod)); + if (rcmod == LDAP_SERVER_DOWN) + ldap_reconnect(); + } + } + } + else + { + /* + * Printserver has never been registered, + * add registration... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "send_ldap_ou: Add entry for %s", ou); + + for (i = 0; i < 3; i ++) + { + pmods[i] = mods + i; + pmods[i]->mod_op = LDAP_MOD_ADD; + } + pmods[i] = NULL; + +# if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 + if ((rcmod = ldap_add_ext_s(BrowseLDAPHandle, dn, pmods, NULL, + NULL)) != LDAP_SUCCESS) +# else + if ((rcmod = ldap_add_s(BrowseLDAPHandle, dn, pmods)) != LDAP_SUCCESS) +# endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "LDAP add for %s failed with status %d: %s", + ou, rcmod, ldap_err2string(rcmod)); + if (rcmod == LDAP_SERVER_DOWN) + ldap_reconnect(); + } + } + + if (rc == LDAP_SUCCESS) + ldap_freeres(res); +} + + +/* + * 'send_ldap_browse()' - Send LDAP printer registrations. + */ + +static void +send_ldap_browse(cupsd_printer_t *p) /* I - Printer to register */ +{ + int i; /* Looping var... */ + LDAPMod mods[7]; /* The 7 attributes we will be adding */ + LDAPMod *pmods[8]; /* Pointers to the 7 attributes + NULL */ + LDAPMessage *res, /* Search result token */ + *e; /* Current entry from search */ + char *cn_value[2], /* Change records */ + *uri[2], + *info[2], + *location[2], + *make_model[2], + *type[2], + typestring[255], /* String to hold printer-type */ + dn[1024]; /* DN of the printer we are adding */ + int rc; /* LDAP status */ + int rcmod; /* LDAP status for modifications */ + char old_uri[HTTP_MAX_URI], /* Printer URI */ + old_location[1024], /* Printer location */ + old_info[1024], /* Printer information */ + old_make_model[1024], /* Printer make and model */ + old_type_string[30]; /* Temporary type number */ + int old_type; /* Printer type */ + static const char * const objectClass_values[] = + { /* The 3 objectClass's we use in */ + "top", /* our LDAP entries */ + "device", + "cupsPrinter", + NULL + }; + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "send_ldap_browse: %s", p->name); + + /* + * Exit function if LDAP updates has been disabled... + */ + + if (!BrowseLDAPUpdate) + { + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "send_ldap_browse: Updates temporary disabled; " + "skipping..."); + return; + } + + /* + * Reconnect if LDAP Handle is invalid... + */ + + if (!BrowseLDAPHandle) + { + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "send_ldap_browse: LDAP Handle is invalid. Try " + "reconnecting..."); + ldap_reconnect(); + return; + } + + /* + * Everything in ldap is ** so we fudge around it... + */ + + sprintf(typestring, "%u", p->type); + + cn_value[0] = p->name; + cn_value[1] = NULL; + info[0] = p->info ? p->info : "Unknown"; + info[1] = NULL; + location[0] = p->location ? p->location : "Unknown"; + location[1] = NULL; + make_model[0] = p->make_model ? p->make_model : "Unknown"; + make_model[1] = NULL; + type[0] = typestring; + type[1] = NULL; + uri[0] = p->uri; + uri[1] = NULL; + + /* + * Get ldap entry for printer ... + */ + + snprintf(dn, sizeof(dn), "cn=%s, ou=%s, %s", p->name, ServerName, + BrowseLDAPDN); + cupsdLogMessage(CUPSD_LOG_DEBUG2, "send_ldap_browse: dn=\"%s\"", dn); + + rc = ldap_search_rec(BrowseLDAPHandle, dn, LDAP_SCOPE_BASE, NULL, + (char **)ldap_attrs, 0, &res); + + /* + * If ldap search was not successfull then exit function + * and temporary disable LDAP updates... + */ + + if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_OBJECT) + { + if (BrowseLDAPUpdate && + (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR)) + { + BrowseLDAPUpdate = FALSE; + cupsdLogMessage(CUPSD_LOG_INFO, + "LDAP update temporary disabled"); + } + + return; + } + + /* + * Fill modification array... + */ + + mods[0].mod_type = "cn"; + mods[0].mod_values = cn_value; + mods[1].mod_type = "printerDescription"; + mods[1].mod_values = info; + mods[2].mod_type = "printerURI"; + mods[2].mod_values = uri; + mods[3].mod_type = "printerLocation"; + mods[3].mod_values = location; + mods[4].mod_type = "printerMakeAndModel"; + mods[4].mod_values = make_model; + mods[5].mod_type = "printerType"; + mods[5].mod_values = type; + mods[6].mod_type = "objectClass"; + mods[6].mod_values = (char **)objectClass_values; + + /* + * Check if we need to insert or update the LDAP entry... + */ + + if (ldap_count_entries(BrowseLDAPHandle, res) > 0 && + rc != LDAP_NO_SUCH_OBJECT) + { + /* + * Printer has already been registered, check if + * modification is required... + */ + + e = ldap_first_entry(BrowseLDAPHandle, res); + + /* + * Get the required values from this entry... + */ + + if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerDescription", + old_info, sizeof(old_info)) == -1) + old_info[0] = '\0'; + + if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerLocation", + old_location, sizeof(old_location)) == -1) + old_info[0] = '\0'; + + if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerMakeAndModel", + old_make_model, sizeof(old_make_model)) == -1) + old_info[0] = '\0'; + + if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerType", + old_type_string, sizeof(old_type_string)) == -1) + old_info[0] = '\0'; + + old_type = atoi(old_type_string); + + if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerURI", old_uri, + sizeof(old_uri)) == -1) + old_info[0] = '\0'; + + /* + * Check if modification is required... + */ + + if (!strcmp(info[0], old_info) && !strcmp(uri[0], old_uri) && + !strcmp(location[0], old_location) && + !strcmp(make_model[0], old_make_model) && p->type == old_type) + { + /* + * LDAP entry for the printer exists. Printer has already been registered, + * no modifications required... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "send_ldap_browse: No updates required for %s", p->name); + } + else + { + /* + * LDAP entry for the printer exists. Printer has already been registered, + * modify the current registration... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "send_ldap_browse: Replace entry for %s", p->name); + + for (i = 0; i < 7; i ++) + { + pmods[i] = mods + i; + pmods[i]->mod_op = LDAP_MOD_REPLACE; + } + pmods[i] = NULL; + +# if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 + if ((rcmod = ldap_modify_ext_s(BrowseLDAPHandle, dn, pmods, NULL, + NULL)) != LDAP_SUCCESS) +# else + if ((rcmod = ldap_modify_s(BrowseLDAPHandle, dn, pmods)) != LDAP_SUCCESS) +# endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "LDAP modify for %s failed with status %d: %s", + p->name, rcmod, ldap_err2string(rcmod)); + if (rcmod == LDAP_SERVER_DOWN) + ldap_reconnect(); + } + } + } + else + { + /* + * No LDAP entry exists for the printer. Printer has never been registered, + * add the current registration... + */ + + send_ldap_ou(ServerName, BrowseLDAPDN, "CUPS Server"); + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "send_ldap_browse: Add entry for %s", p->name); + + for (i = 0; i < 7; i ++) + { + pmods[i] = mods + i; + pmods[i]->mod_op = LDAP_MOD_ADD; + } + pmods[i] = NULL; + +# if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 + if ((rcmod = ldap_add_ext_s(BrowseLDAPHandle, dn, pmods, NULL, + NULL)) != LDAP_SUCCESS) +# else + if ((rcmod = ldap_add_s(BrowseLDAPHandle, dn, pmods)) != LDAP_SUCCESS) +# endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "LDAP add for %s failed with status %d: %s", + p->name, rcmod, ldap_err2string(rcmod)); + if (rcmod == LDAP_SERVER_DOWN) + ldap_reconnect(); + } + } + + if (rc == LDAP_SUCCESS) + ldap_freeres(res); +} + + +/* + * 'ldap_dereg_printer()' - Delete printer from directory + */ + +static void +ldap_dereg_printer(cupsd_printer_t *p) /* I - Printer to deregister */ +{ + char dn[1024]; /* DN of the printer */ + int rc; /* LDAP status */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "ldap_dereg_printer: Remove entry for %s", + p->name); + + /* + * Reconnect if LDAP Handle is invalid... + */ + + if (!BrowseLDAPHandle) + { + ldap_reconnect(); + return; + } + + /* + * Get dn for printer and delete LDAP entry... + */ + + snprintf(dn, sizeof(dn), "cn=%s, ou=%s, %s", p->name, ServerName, + BrowseLDAPDN); + cupsdLogMessage(CUPSD_LOG_DEBUG2, "ldap_dereg_printer: dn=\"%s\"", dn); + +# if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 + if ((rc = ldap_delete_ext_s(BrowseLDAPHandle, dn, NULL, + NULL)) != LDAP_SUCCESS) +# else + if ((rc = ldap_delete_s(BrowseLDAPHandle, dn)) != LDAP_SUCCESS) +# endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ + { + cupsdLogMessage(CUPSD_LOG_WARN, + "LDAP delete for %s failed with status %d: %s", + p->name, rc, ldap_err2string(rc)); + + /* + * If we had a connection problem (connection timed out, etc.) + * we should reconnect and try again to delete the entry... + */ + + if (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR) + { + cupsdLogMessage(CUPSD_LOG_INFO, + "Retry deleting LDAP entry for %s after a reconnect...", p->name); + ldap_reconnect(); + +# if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 + if ((rc = ldap_delete_ext_s(BrowseLDAPHandle, dn, NULL, + NULL)) != LDAP_SUCCESS) +# else + if ((rc = ldap_delete_s(BrowseLDAPHandle, dn)) != LDAP_SUCCESS) +# endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ + cupsdLogMessage(CUPSD_LOG_WARN, + "LDAP delete for %s failed with status %d: %s", + p->name, rc, ldap_err2string(rc)); + } + } +} + + +/* + * 'ldap_dereg_ou()' - Remove the organizational unit. + */ + +static void +ldap_dereg_ou(char *ou, /* I - Organizational unit (servername) */ + char *basedn) /* I - Dase dn */ +{ + char dn[1024]; /* DN of the printer */ + int rc; /* LDAP status */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "ldap_dereg_ou: Remove entry for %s", ou); + + /* + * Reconnect if LDAP Handle is invalid... + */ + + if (!BrowseLDAPHandle) + { + ldap_reconnect(); + return; + } + + /* + * Get dn for printer and delete LDAP entry... + */ + + snprintf(dn, sizeof(dn), "ou=%s, %s", ou, basedn); + cupsdLogMessage(CUPSD_LOG_DEBUG2, "ldap_dereg_ou: dn=\"%s\"", dn); + +# if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 + if ((rc = ldap_delete_ext_s(BrowseLDAPHandle, dn, NULL, + NULL)) != LDAP_SUCCESS) +# else + if ((rc = ldap_delete_s(BrowseLDAPHandle, dn)) != LDAP_SUCCESS) +# endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ + { + cupsdLogMessage(CUPSD_LOG_WARN, + "LDAP delete for %s failed with status %d: %s", + ou, rc, ldap_err2string(rc)); + + /* + * If we had a connection problem (connection timed out, etc.) + * we should reconnect and try again to delete the entry... + */ + + if (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR) + { + cupsdLogMessage(CUPSD_LOG_INFO, + "Retry deleting LDAP entry for %s after a reconnect...", ou); + ldap_reconnect(); +# if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 + if ((rc = ldap_delete_ext_s(BrowseLDAPHandle, dn, NULL, + NULL)) != LDAP_SUCCESS) +# else + if ((rc = ldap_delete_s(BrowseLDAPHandle, dn)) != LDAP_SUCCESS) +# endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */ + cupsdLogMessage(CUPSD_LOG_WARN, + "LDAP delete for %s failed with status %d: %s", + ou, rc, ldap_err2string(rc)); + } + } +} +#endif /* HAVE_LDAP */ + + +#ifdef HAVE_LIBSLP +/* + * 'send_slp_browse()' - Register the specified printer with SLP. + */ + +static void +send_slp_browse(cupsd_printer_t *p) /* I - Printer to register */ +{ + char srvurl[HTTP_MAX_URI], /* Printer service URI */ + attrs[8192], /* Printer attributes */ + finishings[1024], /* Finishings to support */ + make_model[IPP_MAX_NAME * 2], + /* Make and model, quoted */ + location[IPP_MAX_NAME * 2], + /* Location, quoted */ + info[IPP_MAX_NAME * 2], /* Info, quoted */ + *src, /* Pointer to original string */ + *dst; /* Pointer to destination string */ + ipp_attribute_t *authentication; /* uri-authentication-supported value */ + SLPError error; /* SLP error, if any */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG, "send_slp_browse(%p = \"%s\")", p, + p->name); + + /* + * Make the SLP service URL that conforms to the IANA + * 'printer:' template. + */ + + snprintf(srvurl, sizeof(srvurl), SLP_CUPS_SRVTYPE ":%s", p->uri); + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "Service URL = \"%s\"", srvurl); + + /* + * Figure out the finishings string... + */ + + if (p->type & CUPS_PRINTER_STAPLE) + strcpy(finishings, "staple"); + else + finishings[0] = '\0'; + + if (p->type & CUPS_PRINTER_BIND) + { + if (finishings[0]) + strlcat(finishings, ",bind", sizeof(finishings)); + else + strcpy(finishings, "bind"); + } + + if (p->type & CUPS_PRINTER_PUNCH) + { + if (finishings[0]) + strlcat(finishings, ",punch", sizeof(finishings)); + else + strcpy(finishings, "punch"); + } + + if (p->type & CUPS_PRINTER_COVER) + { + if (finishings[0]) + strlcat(finishings, ",cover", sizeof(finishings)); + else + strcpy(finishings, "cover"); + } + + if (p->type & CUPS_PRINTER_SORT) + { + if (finishings[0]) + strlcat(finishings, ",sort", sizeof(finishings)); + else + strcpy(finishings, "sort"); + } + + if (!finishings[0]) + strcpy(finishings, "none"); + + /* + * Quote any commas in the make and model, location, and info strings... + */ + + for (src = p->make_model, dst = make_model; + src && *src && dst < (make_model + sizeof(make_model) - 2);) + { + if (*src == ',' || *src == '\\' || *src == ')') + *dst++ = '\\'; + + *dst++ = *src++; + } + + *dst = '\0'; + + if (!make_model[0]) + strcpy(make_model, "Unknown"); + + for (src = p->location, dst = location; + src && *src && dst < (location + sizeof(location) - 2);) + { + if (*src == ',' || *src == '\\' || *src == ')') + *dst++ = '\\'; + + *dst++ = *src++; + } + + *dst = '\0'; + + if (!location[0]) + strcpy(location, "Unknown"); + + for (src = p->info, dst = info; + src && *src && dst < (info + sizeof(info) - 2);) + { + if (*src == ',' || *src == '\\' || *src == ')') + *dst++ = '\\'; + + *dst++ = *src++; + } + + *dst = '\0'; + + if (!info[0]) + strcpy(info, "Unknown"); + + /* + * Get the authentication value... + */ + + authentication = ippFindAttribute(p->attrs, "uri-authentication-supported", + IPP_TAG_KEYWORD); + + /* + * Make the SLP attribute string list that conforms to + * the IANA 'printer:' template. + */ + + snprintf(attrs, sizeof(attrs), + "(printer-uri-supported=%s)," + "(uri-authentication-supported=%s>)," +#ifdef HAVE_SSL + "(uri-security-supported=tls>)," +#else + "(uri-security-supported=none>)," +#endif /* HAVE_SSL */ + "(printer-name=%s)," + "(printer-location=%s)," + "(printer-info=%s)," + "(printer-more-info=%s)," + "(printer-make-and-model=%s)," + "(printer-type=%d)," + "(charset-supported=utf-8)," + "(natural-language-configured=%s)," + "(natural-language-supported=de,en,es,fr,it)," + "(color-supported=%s)," + "(finishings-supported=%s)," + "(sides-supported=one-sided%s)," + "(multiple-document-jobs-supported=true)" + "(ipp-versions-supported=1.0,1.1)", + p->uri, authentication->values[0].string.text, p->name, location, + info, p->uri, make_model, p->type, DefaultLanguage, + p->type & CUPS_PRINTER_COLOR ? "true" : "false", + finishings, + p->type & CUPS_PRINTER_DUPLEX ? + ",two-sided-long-edge,two-sided-short-edge" : ""); + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "Attributes = \"%s\"", attrs); + + /* + * Register the printer with the SLP server... + */ + + error = SLPReg(BrowseSLPHandle, srvurl, BrowseTimeout, + SLP_CUPS_SRVTYPE, attrs, SLP_TRUE, slp_reg_callback, 0); + + if (error != SLP_OK) + cupsdLogMessage(CUPSD_LOG_ERROR, "SLPReg of \"%s\" failed with status %d!", p->name, + error); +} + + +/* + * 'slp_attr_callback()' - SLP attribute callback + */ + +static SLPBoolean /* O - SLP_TRUE for success */ +slp_attr_callback( + SLPHandle hslp, /* I - SLP handle */ + const char *attrlist, /* I - Attribute list */ + SLPError errcode, /* I - Parsing status for this attr */ + void *cookie) /* I - Current printer */ +{ + char *tmp = 0; /* Temporary string */ + cupsd_printer_t *p = (cupsd_printer_t*)cookie; + /* Current printer */ + + + (void)hslp; /* anti-compiler-warning-code */ + + /* + * Bail if there was an error + */ + + if (errcode != SLP_OK) + return (SLP_TRUE); + + /* + * Parse the attrlist to obtain things needed to build CUPS browse packet + */ + + memset(p, 0, sizeof(cupsd_printer_t)); + + if (slp_get_attr(attrlist, "(printer-location=", &(p->location))) + return (SLP_FALSE); + if (slp_get_attr(attrlist, "(printer-info=", &(p->info))) + return (SLP_FALSE); + if (slp_get_attr(attrlist, "(printer-make-and-model=", &(p->make_model))) + return (SLP_FALSE); + if (!slp_get_attr(attrlist, "(printer-type=", &tmp)) + p->type = atoi(tmp); + else + p->type = CUPS_PRINTER_REMOTE; + + cupsdClearString(&tmp); + + return (SLP_TRUE); +} + + +/* + * 'slp_dereg_printer()' - SLPDereg() the specified printer + */ + +static void +slp_dereg_printer(cupsd_printer_t *p) /* I - Printer */ +{ + char srvurl[HTTP_MAX_URI]; /* Printer service URI */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG, "slp_dereg_printer: printer=\"%s\"", p->name); + + if (!(p->type & CUPS_PRINTER_REMOTE)) + { + /* + * Make the SLP service URL that conforms to the IANA + * 'printer:' template. + */ + + snprintf(srvurl, sizeof(srvurl), SLP_CUPS_SRVTYPE ":%s", p->uri); + + /* + * Deregister the printer... + */ + + SLPDereg(BrowseSLPHandle, srvurl, slp_reg_callback, 0); + } +} + + +/* + * 'slp_get_attr()' - Get an attribute from an SLP registration. + */ + +static int /* O - 0 on success */ +slp_get_attr(const char *attrlist, /* I - Attribute list string */ + const char *tag, /* I - Name of attribute */ + char **valbuf) /* O - Value */ +{ + char *ptr1, /* Pointer into string */ + *ptr2; /* ... */ + + + cupsdClearString(valbuf); + + if ((ptr1 = strstr(attrlist, tag)) != NULL) + { + ptr1 += strlen(tag); + + if ((ptr2 = strchr(ptr1,')')) != NULL) + { + /* + * Copy the value... + */ + + *valbuf = calloc(ptr2 - ptr1 + 1, 1); + strncpy(*valbuf, ptr1, ptr2 - ptr1); + + /* + * Dequote the value... + */ + + for (ptr1 = *valbuf; *ptr1; ptr1 ++) + if (*ptr1 == '\\' && ptr1[1]) + _cups_strcpy(ptr1, ptr1 + 1); + + return (0); + } + } + + return (-1); +} + + +/* + * 'slp_reg_callback()' - Empty SLPRegReport. + */ + +static void +slp_reg_callback(SLPHandle hslp, /* I - SLP handle */ + SLPError errcode, /* I - Error code, if any */ + void *cookie) /* I - App data */ +{ + (void)hslp; + (void)errcode; + (void)cookie; + + return; +} + + +/* + * 'slp_url_callback()' - SLP service url callback + */ + +static SLPBoolean /* O - TRUE = OK, FALSE = error */ +slp_url_callback( + SLPHandle hslp, /* I - SLP handle */ + const char *srvurl, /* I - URL of service */ + unsigned short lifetime, /* I - Life of service */ + SLPError errcode, /* I - Existing error code */ + void *cookie) /* I - Pointer to service list */ +{ + slpsrvurl_t *s, /* New service entry */ + **head; /* Pointer to head of entry */ + + + /* + * Let the compiler know we won't be using these vars... + */ + + (void)hslp; + (void)lifetime; + + /* + * Bail if there was an error + */ + + if (errcode != SLP_OK) + return (SLP_TRUE); + + /* + * Grab the head of the list... + */ + + head = (slpsrvurl_t**)cookie; + + /* + * Allocate a *temporary* slpsrvurl_t to hold this entry. + */ + + if ((s = (slpsrvurl_t *)calloc(1, sizeof(slpsrvurl_t))) == NULL) + return (SLP_FALSE); + + /* + * Copy the SLP service URL... + */ + + strlcpy(s->url, srvurl, sizeof(s->url)); + + /* + * Link the SLP service URL into the head of the list + */ + + if (*head) + s->next = *head; + + *head = s; + + return (SLP_TRUE); +} +#endif /* HAVE_LIBSLP */ + + +/* + * 'update_cups_browse()' - Update the browse lists using the CUPS protocol. + */ + +static void +update_cups_browse(void) +{ + int i; /* Looping var */ + int auth; /* Authorization status */ + int len; /* Length of name string */ + int bytes; /* Number of bytes left */ + char packet[1541], /* Broadcast packet */ + *pptr; /* Pointer into packet */ + socklen_t srclen; /* Length of source address */ + http_addr_t srcaddr; /* Source address */ + char srcname[1024]; /* Source hostname */ + unsigned address[4]; /* Source address */ + unsigned type; /* Printer type */ + unsigned state; /* Printer state */ + char uri[HTTP_MAX_URI], /* Printer URI */ + host[HTTP_MAX_URI], /* Host portion of URI */ + resource[HTTP_MAX_URI], /* Resource portion of URI */ + info[IPP_MAX_NAME], /* Information string */ + location[IPP_MAX_NAME], /* Location string */ + make_model[IPP_MAX_NAME];/* Make and model string */ + int num_attrs; /* Number of attributes */ + cups_option_t *attrs; /* Attributes */ + + + /* + * Read a packet from the browse socket... + */ + + srclen = sizeof(srcaddr); + if ((bytes = recvfrom(BrowseSocket, packet, sizeof(packet) - 1, 0, + (struct sockaddr *)&srcaddr, &srclen)) < 0) + { + /* + * "Connection refused" is returned under Linux if the destination port + * or address is unreachable from a previous sendto(); check for the + * error here and ignore it for now... + */ + + if (errno != ECONNREFUSED && errno != EAGAIN) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Browse recv failed - %s.", + strerror(errno)); + cupsdLogMessage(CUPSD_LOG_ERROR, "CUPS browsing turned off."); + +#ifdef WIN32 + closesocket(BrowseSocket); +#else + close(BrowseSocket); +#endif /* WIN32 */ + + cupsdRemoveSelect(BrowseSocket); + BrowseSocket = -1; + + BrowseLocalProtocols &= ~BROWSE_CUPS; + BrowseRemoteProtocols &= ~BROWSE_CUPS; + } + + return; + } + + packet[bytes] = '\0'; + + /* + * If we're about to sleep, ignore incoming browse packets. + */ + + if (Sleeping) + return; + + /* + * Figure out where it came from... + */ + +#ifdef AF_INET6 + if (srcaddr.addr.sa_family == AF_INET6) + { + address[0] = ntohl(srcaddr.ipv6.sin6_addr.s6_addr32[0]); + address[1] = ntohl(srcaddr.ipv6.sin6_addr.s6_addr32[1]); + address[2] = ntohl(srcaddr.ipv6.sin6_addr.s6_addr32[2]); + address[3] = ntohl(srcaddr.ipv6.sin6_addr.s6_addr32[3]); + } + else +#endif /* AF_INET6 */ + { + address[0] = 0; + address[1] = 0; + address[2] = 0; + address[3] = ntohl(srcaddr.ipv4.sin_addr.s_addr); + } + + if (HostNameLookups) + httpAddrLookup(&srcaddr, srcname, sizeof(srcname)); + else + httpAddrString(&srcaddr, srcname, sizeof(srcname)); + + len = strlen(srcname); + + /* + * Do ACL stuff... + */ + + if (BrowseACL) + { + if (httpAddrLocalhost(&srcaddr) || !_cups_strcasecmp(srcname, "localhost")) + { + /* + * Access from localhost (127.0.0.1) is always allowed... + */ + + auth = CUPSD_AUTH_ALLOW; + } + else + { + /* + * Do authorization checks on the domain/address... + */ + + switch (BrowseACL->order_type) + { + default : + auth = CUPSD_AUTH_DENY; /* anti-compiler-warning-code */ + break; + + case CUPSD_AUTH_ALLOW : /* Order Deny,Allow */ + auth = CUPSD_AUTH_ALLOW; + + if (cupsdCheckAuth(address, srcname, len, BrowseACL->deny)) + auth = CUPSD_AUTH_DENY; + + if (cupsdCheckAuth(address, srcname, len, BrowseACL->allow)) + auth = CUPSD_AUTH_ALLOW; + break; + + case CUPSD_AUTH_DENY : /* Order Allow,Deny */ + auth = CUPSD_AUTH_DENY; + + if (cupsdCheckAuth(address, srcname, len, BrowseACL->allow)) + auth = CUPSD_AUTH_ALLOW; + + if (cupsdCheckAuth(address, srcname, len, BrowseACL->deny)) + auth = CUPSD_AUTH_DENY; + break; + } + } + } + else + auth = CUPSD_AUTH_ALLOW; + + if (auth == CUPSD_AUTH_DENY) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, + "update_cups_browse: Refused %d bytes from %s", bytes, + srcname); + return; + } + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "update_cups_browse: (%d bytes from %s) %s", bytes, + srcname, packet); + + /* + * Parse packet... + */ + + if (sscanf(packet, "%x%x%1023s", &type, &state, uri) < 3) + { + cupsdLogMessage(CUPSD_LOG_WARN, + "update_cups_browse: Garbled browse packet - %s", packet); + return; + } + + strcpy(location, "Location Unknown"); + strcpy(info, "No Information Available"); + make_model[0] = '\0'; + num_attrs = 0; + attrs = NULL; + + if ((pptr = strchr(packet, '\"')) != NULL) + { + /* + * Have extended information; can't use sscanf for it because not all + * sscanf's allow empty strings with %[^\"]... + */ + + for (i = 0, pptr ++; + i < (sizeof(location) - 1) && *pptr && *pptr != '\"'; + i ++, pptr ++) + location[i] = *pptr; + + if (i) + location[i] = '\0'; + + if (*pptr == '\"') + pptr ++; + + while (*pptr && isspace(*pptr & 255)) + pptr ++; + + if (*pptr == '\"') + { + for (i = 0, pptr ++; + i < (sizeof(info) - 1) && *pptr && *pptr != '\"'; + i ++, pptr ++) + info[i] = *pptr; + + info[i] = '\0'; + + if (*pptr == '\"') + pptr ++; + + while (*pptr && isspace(*pptr & 255)) + pptr ++; + + if (*pptr == '\"') + { + for (i = 0, pptr ++; + i < (sizeof(make_model) - 1) && *pptr && *pptr != '\"'; + i ++, pptr ++) + make_model[i] = *pptr; + + if (*pptr == '\"') + pptr ++; + + make_model[i] = '\0'; + + if (*pptr) + num_attrs = cupsParseOptions(pptr, num_attrs, &attrs); + } + } + } + + DEBUG_puts(packet); + DEBUG_printf(("type=%x, state=%x, uri=\"%s\"\n" + "location=\"%s\", info=\"%s\", make_model=\"%s\"\n", + type, state, uri, location, info, make_model)); + + /* + * Pull the URI apart to see if this is a local or remote printer... + */ + + if (is_local_queue(uri, host, sizeof(host), resource, sizeof(resource))) + { + cupsFreeOptions(num_attrs, attrs); + return; + } + + /* + * Do relaying... + */ + + for (i = 0; i < NumRelays; i ++) + if (cupsdCheckAuth(address, srcname, len, Relays[i].from)) + if (sendto(BrowseSocket, packet, bytes, 0, + (struct sockaddr *)&(Relays[i].to), + httpAddrLength(&(Relays[i].to))) <= 0) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "update_cups_browse: sendto failed for relay %d - %s.", + i + 1, strerror(errno)); + cupsFreeOptions(num_attrs, attrs); + return; + } + + /* + * Process the browse data... + */ + + process_browse_data(uri, host, resource, (cups_ptype_t)type, + (ipp_pstate_t)state, location, info, make_model, + num_attrs, attrs); +} + + +/* + * 'update_lpd()' - Update the LPD configuration as needed. + */ + +static void +update_lpd(int onoff) /* - 1 = turn on, 0 = turn off */ +{ + if (!LPDConfigFile) + return; + +#ifdef __APPLE__ + /* + * Allow /etc/hostconfig CUPS_LPD service setting to override cupsd.conf + * setting for backwards-compatibility. + */ + + if (onoff && !get_hostconfig("CUPS_LPD")) + onoff = 0; +#endif /* __APPLE__ */ + + if (!strncmp(LPDConfigFile, "xinetd:///", 10)) + { + /* + * Enable/disable LPD via the xinetd.d config file for cups-lpd... + */ + + char newfile[1024]; /* New cups-lpd.N file */ + cups_file_t *ofp, /* Original file pointer */ + *nfp; /* New file pointer */ + char line[1024]; /* Line from file */ + + + snprintf(newfile, sizeof(newfile), "%s.N", LPDConfigFile + 9); + + if ((ofp = cupsFileOpen(LPDConfigFile + 9, "r")) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open \"%s\" - %s", + LPDConfigFile + 9, strerror(errno)); + return; + } + + if ((nfp = cupsFileOpen(newfile, "w")) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create \"%s\" - %s", + newfile, strerror(errno)); + cupsFileClose(ofp); + return; + } + + /* + * Copy all of the lines from the cups-lpd file... + */ + + while (cupsFileGets(ofp, line, sizeof(line))) + { + if (line[0] == '{') + { + cupsFilePrintf(nfp, "%s\n", line); + snprintf(line, sizeof(line), "\tdisable = %s", + onoff ? "no" : "yes"); + } + else if (!strstr(line, "disable =")) + cupsFilePrintf(nfp, "%s\n", line); + } + + cupsFileClose(nfp); + cupsFileClose(ofp); + rename(newfile, LPDConfigFile + 9); + } +#ifdef __APPLE__ + else if (!strncmp(LPDConfigFile, "launchd:///", 11)) + { + /* + * Enable/disable LPD via the launchctl command... + */ + + char *argv[5], /* Arguments for command */ + *envp[MAX_ENV]; /* Environment for command */ + int pid; /* Process ID */ + + + cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0]))); + argv[0] = (char *)"launchctl"; + argv[1] = (char *)(onoff ? "load" : "unload"); + argv[2] = (char *)"-w"; + argv[3] = LPDConfigFile + 10; + argv[4] = NULL; + + cupsdStartProcess("/bin/launchctl", argv, envp, -1, -1, -1, -1, -1, 1, + NULL, NULL, &pid); + } +#endif /* __APPLE__ */ + else + cupsdLogMessage(CUPSD_LOG_INFO, "Unknown LPDConfigFile scheme!"); +} + + +/* + * 'update_polling()' - Read status messages from the poll daemons. + */ + +static void +update_polling(void) +{ + char *ptr, /* Pointer to end of line in buffer */ + message[1024]; /* Pointer to message text */ + int loglevel; /* Log level for message */ + + + while ((ptr = cupsdStatBufUpdate(PollStatusBuffer, &loglevel, + message, sizeof(message))) != NULL) + { + if (loglevel == CUPSD_LOG_INFO) + cupsdLogMessage(CUPSD_LOG_INFO, "%s", message); + + if (!strchr(PollStatusBuffer->buffer, '\n')) + break; + } + + if (ptr == NULL && !PollStatusBuffer->bufused) + { + /* + * All polling processes have died; stop polling... + */ + + cupsdLogMessage(CUPSD_LOG_ERROR, + "update_polling: all polling processes have exited!"); + cupsdStopPolling(); + } +} + + +/* + * 'update_smb()' - Update the SMB configuration as needed. + */ + +static void +update_smb(int onoff) /* I - 1 = turn on, 0 = turn off */ +{ + if (!SMBConfigFile) + return; + + if (!strncmp(SMBConfigFile, "samba:///", 9)) + { + /* + * Enable/disable SMB via the specified smb.conf config file... + */ + + char newfile[1024]; /* New smb.conf.N file */ + cups_file_t *ofp, /* Original file pointer */ + *nfp; /* New file pointer */ + char line[1024]; /* Line from file */ + int in_printers; /* In [printers] section? */ + + + snprintf(newfile, sizeof(newfile), "%s.N", SMBConfigFile + 8); + + if ((ofp = cupsFileOpen(SMBConfigFile + 8, "r")) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open \"%s\" - %s", + SMBConfigFile + 8, strerror(errno)); + return; + } + + if ((nfp = cupsFileOpen(newfile, "w")) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create \"%s\" - %s", + newfile, strerror(errno)); + cupsFileClose(ofp); + return; + } + + /* + * Copy all of the lines from the smb.conf file... + */ + + in_printers = 0; + + while (cupsFileGets(ofp, line, sizeof(line))) + { + if (in_printers && strstr(line, "printable =")) + snprintf(line, sizeof(line), " printable = %s", + onoff ? "yes" : "no"); + + cupsFilePrintf(nfp, "%s\n", line); + + if (line[0] == '[') + in_printers = !strcmp(line, "[printers]"); + } + + cupsFileClose(nfp); + cupsFileClose(ofp); + rename(newfile, SMBConfigFile + 8); + } + else + cupsdLogMessage(CUPSD_LOG_INFO, "Unknown SMBConfigFile scheme!"); +} + + +/* + * End of "$Id: dirsvc.c 10243 2012-02-11 02:05:21Z mike $". + */ |