#include "cups-private.h"
#include <sys/stat.h>
#ifdef HAVE_NOTIFY_H
# include <notify.h>
#endif
#ifdef HAVE_POLL
# include <poll.h>
#endif
#ifdef HAVE_DNSSD
# include <dns_sd.h>
#endif
#ifdef HAVE_AVAHI
# include <avahi-client/client.h>
# include <avahi-client/lookup.h>
# include <avahi-common/simple-watch.h>
# include <avahi-common/domain.h>
# include <avahi-common/error.h>
# include <avahi-common/malloc.h>
#define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
#endif
#ifdef __APPLE__
# include <SystemConfiguration/SystemConfiguration.h>
# define kCUPSPrintingPrefs CFSTR("org.cups.PrintingPrefs")
# define kDefaultPaperIDKey CFSTR("DefaultPaperID")
# define kLastUsedPrintersKey CFSTR("LastUsedPrinters")
# define kLocationNetworkKey CFSTR("Network")
# define kLocationPrinterIDKey CFSTR("PrinterID")
# define kUseLastPrinter CFSTR("UseLastPrinter")
#endif
#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
typedef enum _cups_dnssd_state_e
{
_CUPS_DNSSD_NEW,
_CUPS_DNSSD_QUERY,
_CUPS_DNSSD_PENDING,
_CUPS_DNSSD_ACTIVE,
_CUPS_DNSSD_LOCAL,
_CUPS_DNSSD_INCOMPATIBLE,
_CUPS_DNSSD_ERROR
} _cups_dnssd_state_t;
typedef struct _cups_dnssd_data_s
{
# ifdef HAVE_DNSSD
DNSServiceRef main_ref;
# else
AvahiSimplePoll *simple_poll;
AvahiClient *client;
int got_data;
# endif
cups_dest_cb_t cb;
void *user_data;
cups_ptype_t type,
mask;
cups_array_t *devices;
} _cups_dnssd_data_t;
typedef struct _cups_dnssd_device_s
{
_cups_dnssd_state_t state;
# ifdef HAVE_DNSSD
DNSServiceRef ref;
# else
AvahiRecordBrowser *ref;
# endif
char *domain,
*fullName,
*regtype;
cups_ptype_t type;
cups_dest_t dest;
} _cups_dnssd_device_t;
typedef struct _cups_dnssd_resolve_s
{
int *cancel;
struct timeval end_time;
} _cups_dnssd_resolve_t;
#endif
#ifdef __APPLE__
static CFArrayRef appleCopyLocations(void);
static CFStringRef appleCopyNetwork(void);
static char *appleGetPaperSize(char *name, int namesize);
static CFStringRef appleGetPrinter(CFArrayRef locations,
CFStringRef network, CFIndex *locindex);
#endif
static cups_dest_t *cups_add_dest(const char *name, const char *instance,
int *num_dests, cups_dest_t **dests);
#ifdef __BLOCKS__
static int cups_block_cb(cups_dest_block_t block, unsigned flags,
cups_dest_t *dest);
#endif
static int cups_compare_dests(cups_dest_t *a, cups_dest_t *b);
#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
# ifdef HAVE_DNSSD
static void cups_dnssd_browse_cb(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *serviceName,
const char *regtype,
const char *replyDomain,
void *context);
# else
static void cups_dnssd_browse_cb(AvahiServiceBrowser *browser,
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiBrowserEvent event,
const char *serviceName,
const char *regtype,
const char *replyDomain,
AvahiLookupResultFlags flags,
void *context);
static void cups_dnssd_client_cb(AvahiClient *client,
AvahiClientState state,
void *context);
# endif
static int cups_dnssd_compare_devices(_cups_dnssd_device_t *a,
_cups_dnssd_device_t *b);
static void cups_dnssd_free_device(_cups_dnssd_device_t *device,
_cups_dnssd_data_t *data);
static _cups_dnssd_device_t *
cups_dnssd_get_device(_cups_dnssd_data_t *data,
const char *serviceName,
const char *regtype,
const char *replyDomain);
# ifdef HAVE_DNSSD
static void cups_dnssd_local_cb(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *serviceName,
const char *regtype,
const char *replyDomain,
void *context);
static void cups_dnssd_query_cb(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *fullName,
uint16_t rrtype, uint16_t rrclass,
uint16_t rdlen, const void *rdata,
uint32_t ttl, void *context);
# else
static int cups_dnssd_poll_cb(struct pollfd *pollfds,
unsigned int num_pollfds,
int timeout, void *context);
static void cups_dnssd_query_cb(AvahiRecordBrowser *browser,
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiBrowserEvent event,
const char *name, uint16_t rrclass,
uint16_t rrtype, const void *rdata,
size_t rdlen,
AvahiLookupResultFlags flags,
void *context);
# endif
static const char *cups_dnssd_resolve(cups_dest_t *dest, const char *uri,
int msec, int *cancel,
cups_dest_cb_t cb, void *user_data);
static int cups_dnssd_resolve_cb(void *context);
static void cups_dnssd_unquote(char *dst, const char *src,
size_t dstsize);
#endif
static int cups_find_dest(const char *name, const char *instance,
int num_dests, cups_dest_t *dests, int prev,
int *rdiff);
static char *cups_get_default(const char *filename, char *namebuf,
size_t namesize, const char **instance);
static int cups_get_dests(const char *filename, const char *match_name,
const char *match_inst, int user_default_set,
int num_dests, cups_dest_t **dests);
static char *cups_make_string(ipp_attribute_t *attr, char *buffer,
size_t bufsize);
int
cupsAddDest(const char *name,
const char *instance,
int num_dests,
cups_dest_t **dests)
{
int i;
cups_dest_t *dest;
cups_dest_t *parent = NULL;
cups_option_t *doption,
*poption;
if (!name || !dests)
return (0);
if (!cupsGetDest(name, instance, num_dests, *dests))
{
if (instance && !cupsGetDest(name, NULL, num_dests, *dests))
return (num_dests);
dest = cups_add_dest(name, instance, &num_dests, dests);
parent = cupsGetDest(name, NULL, num_dests, *dests);
if (instance && parent && parent->num_options > 0)
{
dest->options = calloc(sizeof(cups_option_t), parent->num_options);
if (dest->options)
{
dest->num_options = parent->num_options;
for (i = dest->num_options, doption = dest->options,
poption = parent->options;
i > 0;
i --, doption ++, poption ++)
{
doption->name = _cupsStrRetain(poption->name);
doption->value = _cupsStrRetain(poption->value);
}
}
}
}
return (num_dests);
}
#ifdef __APPLE__
CFStringRef
_cupsAppleCopyDefaultPaperID(void)
{
return (CFPreferencesCopyAppValue(kDefaultPaperIDKey,
kCUPSPrintingPrefs));
}
CFStringRef
_cupsAppleCopyDefaultPrinter(void)
{
CFStringRef network;
CFArrayRef locations;
CFStringRef locprinter;
if (!_cupsAppleGetUseLastPrinter())
{
DEBUG_puts("1_cupsAppleCopyDefaultPrinter: Not using last printer as "
"default.");
return (NULL);
}
if ((network = appleCopyNetwork()) == NULL)
{
DEBUG_puts("1_cupsAppleCopyDefaultPrinter: Unable to get current "
"network.");
return (NULL);
}
if ((locations = appleCopyLocations()) == NULL)
{
DEBUG_puts("1_cupsAppleCopyDefaultPrinter: Missing or bad last used "
"printer array.");
CFRelease(network);
return (NULL);
}
DEBUG_printf(("1_cupsAppleCopyDefaultPrinter: Got locations, %d entries.",
(int)CFArrayGetCount(locations)));
if ((locprinter = appleGetPrinter(locations, network, NULL)) != NULL)
CFRetain(locprinter);
CFRelease(network);
CFRelease(locations);
return (locprinter);
}
int
_cupsAppleGetUseLastPrinter(void)
{
Boolean uselast,
uselast_set;
if (getenv("CUPS_DISABLE_APPLE_DEFAULT"))
return (0);
uselast = CFPreferencesGetAppBooleanValue(kUseLastPrinter,
kCUPSPrintingPrefs,
&uselast_set);
if (!uselast_set)
return (1);
else
return (uselast);
}
void
_cupsAppleSetDefaultPaperID(
CFStringRef name)
{
CFPreferencesSetAppValue(kDefaultPaperIDKey, name, kCUPSPrintingPrefs);
CFPreferencesAppSynchronize(kCUPSPrintingPrefs);
notify_post("com.apple.printerPrefsChange");
}
void
_cupsAppleSetDefaultPrinter(
CFStringRef name)
{
CFStringRef network;
CFArrayRef locations;
CFIndex locindex;
CFStringRef locprinter;
CFMutableArrayRef newlocations;
CFMutableDictionaryRef newlocation;
if ((network = appleCopyNetwork()) == NULL)
{
DEBUG_puts("1_cupsAppleSetDefaultPrinter: Unable to get current network...");
return;
}
if ((locations = appleCopyLocations()) != NULL)
locprinter = appleGetPrinter(locations, network, &locindex);
else
{
locprinter = NULL;
locindex = -1;
}
if (!locprinter || CFStringCompare(locprinter, name, 0) != kCFCompareEqualTo)
{
if (locations)
{
newlocations = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0,
locations);
if (locprinter)
CFArrayRemoveValueAtIndex(newlocations, locindex);
}
else
newlocations = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
newlocation = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (newlocation && newlocations)
{
CFDictionaryAddValue(newlocation, kLocationNetworkKey, network);
CFDictionaryAddValue(newlocation, kLocationPrinterIDKey, name);
CFArrayInsertValueAtIndex(newlocations, 0, newlocation);
while (CFArrayGetCount(newlocations) > 10)
CFArrayRemoveValueAtIndex(newlocations, 10);
CFPreferencesSetAppValue(kLastUsedPrintersKey, newlocations,
kCUPSPrintingPrefs);
CFPreferencesAppSynchronize(kCUPSPrintingPrefs);
notify_post("com.apple.printerPrefsChange");
}
if (newlocations)
CFRelease(newlocations);
if (newlocation)
CFRelease(newlocation);
}
if (locations)
CFRelease(locations);
CFRelease(network);
}
void
_cupsAppleSetUseLastPrinter(
int uselast)
{
CFPreferencesSetAppValue(kUseLastPrinter,
uselast ? kCFBooleanTrue : kCFBooleanFalse,
kCUPSPrintingPrefs);
CFPreferencesAppSynchronize(kCUPSPrintingPrefs);
notify_post("com.apple.printerPrefsChange");
}
#endif
http_t *
cupsConnectDest(
cups_dest_t *dest,
unsigned flags,
int msec,
int *cancel,
char *resource,
size_t resourcesize,
cups_dest_cb_t cb,
void *user_data)
{
const char *uri;
char scheme[32],
userpass[256],
hostname[256],
tempresource[1024];
int port;
char portstr[16];
http_encryption_t encryption;
http_addrlist_t *addrlist;
http_t *http;
if (!dest)
{
if (resource)
*resource = '\0';
_cupsSetError(IPP_INTERNAL_ERROR, strerror(EINVAL), 0);
return (NULL);
}
if (!resource || resourcesize < 1)
{
resource = tempresource;
resourcesize = sizeof(tempresource);
}
if ((uri = cupsGetOption("printer-uri-supported", dest->num_options,
dest->options)) == NULL)
{
_cupsSetError(IPP_INTERNAL_ERROR, strerror(ENOENT), 0);
if (cb)
(*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR,
dest);
return (NULL);
}
#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
if (strstr(uri, "._tcp"))
{
if ((uri = cups_dnssd_resolve(dest, uri, msec, cancel, cb,
user_data)) == NULL)
return (NULL);
}
#endif
if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme),
userpass, sizeof(userpass), hostname, sizeof(hostname),
&port, resource, resourcesize) < HTTP_URI_OK)
{
_cupsSetError(IPP_INTERNAL_ERROR, _("Bad printer URI."), 1);
if (cb)
(*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR,
dest);
return (NULL);
}
if (cb)
(*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_RESOLVING,
dest);
snprintf(portstr, sizeof(portstr), "%d", port);
if ((addrlist = httpAddrGetList(hostname, AF_UNSPEC, portstr)) == NULL)
{
if (cb)
(*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR,
dest);
return (NULL);
}
if (cancel && *cancel)
{
httpAddrFreeList(addrlist);
if (cb)
(*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_CANCELED,
dest);
return (NULL);
}
if (!strcmp(scheme, "ipps") || port == 443)
encryption = HTTP_ENCRYPT_ALWAYS;
else
encryption = HTTP_ENCRYPT_IF_REQUESTED;
http = _httpCreate(hostname, port, addrlist, encryption, AF_UNSPEC);
if (flags & CUPS_DEST_FLAGS_UNCONNECTED)
{
if (cb)
(*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED, dest);
}
else
{
if (cb)
(*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_CONNECTING,
dest);
if (!httpReconnect2(http, msec, cancel) && cb)
{
if (cancel && *cancel)
(*cb)(user_data,
CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_CONNECTING, dest);
else
(*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR,
dest);
}
else if (cb)
(*cb)(user_data, CUPS_DEST_FLAGS_NONE, dest);
}
return (http);
}
#ifdef __BLOCKS__
http_t *
cupsConnectDestBlock(
cups_dest_t *dest,
unsigned flags,
int msec,
int *cancel,
char *resource,
size_t resourcesize,
cups_dest_block_t block)
{
return (cupsConnectDest(dest, flags, msec, cancel, resource, resourcesize,
(cups_dest_cb_t)cups_block_cb, (void *)block));
}
#endif
int
cupsCopyDest(cups_dest_t *dest,
int num_dests,
cups_dest_t **dests)
{
int i;
cups_dest_t *new_dest;
cups_option_t *new_option,
*option;
if (!dest || num_dests < 0 || !dests)
return (num_dests);
if ((new_dest = cupsGetDest(dest->name, dest->instance, num_dests,
*dests)) != NULL)
{
if (new_dest == dest)
return (num_dests);
cupsFreeOptions(new_dest->num_options, new_dest->options);
new_dest->num_options = 0;
new_dest->options = NULL;
}
else
new_dest = cups_add_dest(dest->name, dest->instance, &num_dests, dests);
if (new_dest)
{
if ((new_dest->options = calloc(sizeof(cups_option_t),
dest->num_options)) == NULL)
return (cupsRemoveDest(dest->name, dest->instance, num_dests, dests));
new_dest->num_options = dest->num_options;
for (i = dest->num_options, option = dest->options,
new_option = new_dest->options;
i > 0;
i --, option ++, new_option ++)
{
new_option->name = _cupsStrRetain(option->name);
new_option->value = _cupsStrRetain(option->value);
}
}
return (num_dests);
}
int
cupsEnumDests(
unsigned flags,
int msec,
int *cancel,
cups_ptype_t type,
cups_ptype_t mask,
cups_dest_cb_t cb,
void *user_data)
{
int i,
num_dests;
cups_dest_t *dests = NULL,
*dest;
#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
int count,
remaining;
_cups_dnssd_data_t data;
_cups_dnssd_device_t *device;
# ifdef HAVE_DNSSD
int nfds,
main_fd;
DNSServiceRef ipp_ref,
local_ipp_ref;
# ifdef HAVE_SSL
DNSServiceRef ipps_ref,
local_ipps_ref;
# endif
# ifdef HAVE_POLL
struct pollfd pfd;
# else
fd_set input;
struct timeval timeout;
# endif
# else
int error;
AvahiServiceBrowser *ipp_ref;
# ifdef HAVE_SSL
AvahiServiceBrowser *ipps_ref;
# endif
# endif
#endif
(void)flags;
if (!cb)
return (0);
num_dests = _cupsGetDests(CUPS_HTTP_DEFAULT, CUPS_GET_PRINTERS, NULL, &dests,
type, mask);
for (i = num_dests, dest = dests;
i > 0 && (!cancel || !*cancel);
i --, dest ++)
if (!(*cb)(user_data, i > 1 ? CUPS_DEST_FLAGS_MORE : CUPS_DEST_FLAGS_NONE,
dest))
break;
cupsFreeDests(num_dests, dests);
if (i > 0 || msec == 0)
return (1);
#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
data.type = type;
data.mask = mask;
data.devices = cupsArrayNew3((cups_array_func_t)cups_dnssd_compare_devices,
NULL, NULL, 0, NULL,
(cups_afree_func_t)cups_dnssd_free_device);
# ifdef HAVE_DNSSD
if (DNSServiceCreateConnection(&data.main_ref) != kDNSServiceErr_NoError)
return (0);
main_fd = DNSServiceRefSockFD(data.main_ref);
ipp_ref = data.main_ref;
DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0,
"_ipp._tcp", NULL,
(DNSServiceBrowseReply)cups_dnssd_browse_cb, &data);
local_ipp_ref = data.main_ref;
DNSServiceBrowse(&local_ipp_ref, kDNSServiceFlagsShareConnection,
kDNSServiceInterfaceIndexLocalOnly,
"_ipp._tcp", NULL,
(DNSServiceBrowseReply)cups_dnssd_local_cb, &data);
# ifdef HAVE_SSL
ipps_ref = data.main_ref;
DNSServiceBrowse(&ipps_ref, kDNSServiceFlagsShareConnection, 0,
"_ipps._tcp", NULL,
(DNSServiceBrowseReply)cups_dnssd_browse_cb, &data);
local_ipps_ref = data.main_ref;
DNSServiceBrowse(&local_ipps_ref, kDNSServiceFlagsShareConnection,
kDNSServiceInterfaceIndexLocalOnly,
"_ipps._tcp", NULL,
(DNSServiceBrowseReply)cups_dnssd_local_cb, &data);
# endif
# else
if ((data.simple_poll = avahi_simple_poll_new()) == NULL)
{
DEBUG_puts("cupsEnumDests: Unable to create Avahi simple poll object.");
return (1);
}
avahi_simple_poll_set_func(data.simple_poll, cups_dnssd_poll_cb, &data);
data.client = avahi_client_new(avahi_simple_poll_get(data.simple_poll),
0, cups_dnssd_client_cb, &data,
&error);
if (!data.client)
{
DEBUG_puts("cupsEnumDests: Unable to create Avahi client.");
avahi_simple_poll_free(data.simple_poll);
return (1);
}
ipp_ref = avahi_service_browser_new(data.client, AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC, "_ipp._tcp", NULL,
0, cups_dnssd_browse_cb, &data);
# ifdef HAVE_SSL
ipps_ref = avahi_service_browser_new(data.client, AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC, "_ipps._tcp", NULL,
0, cups_dnssd_browse_cb, &data);
# endif
# endif
if (msec < 0)
remaining = INT_MAX;
else
remaining = msec;
while (remaining > 0 && (!cancel || !*cancel))
{
# ifdef HAVE_DNSSD
# ifdef HAVE_POLL
pfd.fd = main_fd;
pfd.events = POLLIN;
nfds = poll(&pfd, 1, remaining > 250 ? 250 : remaining);
# else
FD_ZERO(&input);
FD_SET(main_fd, &input);
timeout.tv_sec = 0;
timeout.tv_usec = remaining > 250 ? 250000 : remaining * 1000;
nfds = select(main_fd + 1, &input, NULL, NULL, &timeout);
# endif
if (nfds > 0)
DNSServiceProcessResult(data.main_ref);
else if (nfds == 0)
remaining -= 250;
# else
data.got_data = 0;
if ((error = avahi_simple_poll_iterate(data.simple_poll, 250)) > 0)
{
break;
}
if (!data.got_data)
remaining -= 250;
# endif
for (device = (_cups_dnssd_device_t *)cupsArrayFirst(data.devices),
count = 0;
device;
device = (_cups_dnssd_device_t *)cupsArrayNext(data.devices))
{
if (device->ref)
count ++;
if (!device->ref && device->state == _CUPS_DNSSD_NEW)
{
DEBUG_printf(("1cupsEnumDests: Querying '%s'.", device->fullName));
# ifdef HAVE_DNSSD
device->ref = data.main_ref;
if (DNSServiceQueryRecord(&(device->ref),
kDNSServiceFlagsShareConnection,
0, device->fullName,
kDNSServiceType_TXT,
kDNSServiceClass_IN,
(DNSServiceQueryRecordReply)cups_dnssd_query_cb,
&data) == kDNSServiceErr_NoError)
{
count ++;
}
else
{
device->ref = 0;
device->state = _CUPS_DNSSD_ERROR;
DEBUG_puts("1cupsEnumDests: Query failed.");
}
# else
if ((device->ref = avahi_record_browser_new(data.client,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC,
device->fullName,
AVAHI_DNS_CLASS_IN,
AVAHI_DNS_TYPE_TXT,
0,
cups_dnssd_query_cb,
&data)) != NULL)
{
count ++;
}
else
{
device->state = _CUPS_DNSSD_ERROR;
DEBUG_printf(("1cupsEnumDests: Query failed: %s",
avahi_strerror(avahi_client_errno(data.client))));
}
# endif
}
else if (device->ref && device->state == _CUPS_DNSSD_PENDING)
{
if ((device->type & mask) == type)
{
if (!(*cb)(user_data, CUPS_DEST_FLAGS_NONE, &device->dest))
{
remaining = -1;
break;
}
}
device->state = _CUPS_DNSSD_ACTIVE;
}
}
}
cupsArrayDelete(data.devices);
# ifdef HAVE_DNSSD
DNSServiceRefDeallocate(ipp_ref);
DNSServiceRefDeallocate(local_ipp_ref);
# ifdef HAVE_SSL
DNSServiceRefDeallocate(ipp_ref);
DNSServiceRefDeallocate(local_ipp_ref);
# endif
DNSServiceRefDeallocate(data.main_ref);
# else
avahi_service_browser_free(ipp_ref);
# ifdef HAVE_SSL
avahi_service_browser_free(ipps_ref);
# endif
avahi_client_free(data.client);
avahi_simple_poll_free(data.simple_poll);
# endif
#endif
return (1);
}
# ifdef __BLOCKS__
int
cupsEnumDestsBlock(
unsigned flags,
int timeout,
int *cancel,
cups_ptype_t type,
cups_ptype_t mask,
cups_dest_block_t block)
{
return (cupsEnumDests(flags, timeout, cancel, type, mask,
(cups_dest_cb_t)cups_block_cb, (void *)block));
}
# endif
void
cupsFreeDests(int num_dests,
cups_dest_t *dests)
{
int i;
cups_dest_t *dest;
if (num_dests == 0 || dests == NULL)
return;
for (i = num_dests, dest = dests; i > 0; i --, dest ++)
{
_cupsStrFree(dest->name);
_cupsStrFree(dest->instance);
cupsFreeOptions(dest->num_options, dest->options);
}
free(dests);
}
cups_dest_t *
cupsGetDest(const char *name,
const char *instance,
int num_dests,
cups_dest_t *dests)
{
int diff,
match;
if (num_dests <= 0 || !dests)
return (NULL);
if (!name)
{
while (num_dests > 0)
{
if (dests->is_default)
return (dests);
num_dests --;
dests ++;
}
}
else
{
match = cups_find_dest(name, instance, num_dests, dests, -1, &diff);
if (!diff)
return (dests + match);
}
return (NULL);
}
const char *
_cupsGetDestResource(
cups_dest_t *dest,
char *resource,
size_t resourcesize)
{
const char *uri;
char scheme[32],
userpass[256],
hostname[256];
int port;
if (!dest || !resource || resourcesize < 1)
{
if (resource)
*resource = '\0';
_cupsSetError(IPP_INTERNAL_ERROR, strerror(EINVAL), 0);
return (NULL);
}
if ((uri = cupsGetOption("printer-uri-supported", dest->num_options,
dest->options)) == NULL)
{
if (resource)
*resource = '\0';
_cupsSetError(IPP_INTERNAL_ERROR, strerror(ENOENT), 0);
return (NULL);
}
#ifdef HAVE_DNSSD
if (strstr(uri, "._tcp"))
{
if ((uri = cups_dnssd_resolve(dest, uri, 5000, NULL, NULL, NULL)) == NULL)
return (NULL);
}
#endif
if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme),
userpass, sizeof(userpass), hostname, sizeof(hostname),
&port, resource, resourcesize) < HTTP_URI_OK)
{
_cupsSetError(IPP_INTERNAL_ERROR, _("Bad printer URI."), 1);
return (NULL);
}
return (uri);
}
int
_cupsGetDests(http_t *http,
ipp_op_t op,
const char *name,
cups_dest_t **dests,
cups_ptype_t type,
cups_ptype_t mask)
{
int num_dests = 0;
cups_dest_t *dest;
ipp_t *request,
*response;
ipp_attribute_t *attr;
const char *printer_name;
char uri[1024];
int num_options;
cups_option_t *options;
#ifdef __APPLE__
char media_default[41];
#endif
char optname[1024],
value[2048],
*ptr;
static const char * const pattrs[] =
{
"auth-info-required",
"device-uri",
"job-sheets-default",
"marker-change-time",
"marker-colors",
"marker-high-levels",
"marker-levels",
"marker-low-levels",
"marker-message",
"marker-names",
"marker-types",
#ifdef __APPLE__
"media-supported",
#endif
"printer-commands",
"printer-defaults",
"printer-info",
"printer-is-accepting-jobs",
"printer-is-shared",
"printer-location",
"printer-make-and-model",
"printer-name",
"printer-state",
"printer-state-change-time",
"printer-state-reasons",
"printer-type",
"printer-uri-supported"
};
#ifdef __APPLE__
appleGetPaperSize(media_default, sizeof(media_default));
#endif
request = ippNewRequest(op);
ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
"requested-attributes", sizeof(pattrs) / sizeof(pattrs[0]),
NULL, pattrs);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
"requesting-user-name", NULL, cupsUser());
if (name && op != CUPS_GET_DEFAULT)
{
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
"localhost", ippPort(), "/printers/%s", name);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
uri);
}
else if (mask)
{
ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type",
type);
ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type-mask",
mask);
}
if ((response = cupsDoRequest(http, request, "/")) != NULL)
{
for (attr = response->attrs; attr != NULL; attr = attr->next)
{
while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER)
attr = attr->next;
if (attr == NULL)
break;
printer_name = NULL;
num_options = 0;
options = NULL;
for (; attr && attr->group_tag == IPP_TAG_PRINTER; attr = attr->next)
{
if (attr->value_tag != IPP_TAG_INTEGER &&
attr->value_tag != IPP_TAG_ENUM &&
attr->value_tag != IPP_TAG_BOOLEAN &&
attr->value_tag != IPP_TAG_TEXT &&
attr->value_tag != IPP_TAG_TEXTLANG &&
attr->value_tag != IPP_TAG_NAME &&
attr->value_tag != IPP_TAG_NAMELANG &&
attr->value_tag != IPP_TAG_KEYWORD &&
attr->value_tag != IPP_TAG_RANGE &&
attr->value_tag != IPP_TAG_URI)
continue;
if (!strcmp(attr->name, "auth-info-required") ||
!strcmp(attr->name, "device-uri") ||
!strcmp(attr->name, "marker-change-time") ||
!strcmp(attr->name, "marker-colors") ||
!strcmp(attr->name, "marker-high-levels") ||
!strcmp(attr->name, "marker-levels") ||
!strcmp(attr->name, "marker-low-levels") ||
!strcmp(attr->name, "marker-message") ||
!strcmp(attr->name, "marker-names") ||
!strcmp(attr->name, "marker-types") ||
!strcmp(attr->name, "printer-commands") ||
!strcmp(attr->name, "printer-info") ||
!strcmp(attr->name, "printer-is-shared") ||
!strcmp(attr->name, "printer-make-and-model") ||
!strcmp(attr->name, "printer-state") ||
!strcmp(attr->name, "printer-state-change-time") ||
!strcmp(attr->name, "printer-type") ||
!strcmp(attr->name, "printer-is-accepting-jobs") ||
!strcmp(attr->name, "printer-location") ||
!strcmp(attr->name, "printer-state-reasons") ||
!strcmp(attr->name, "printer-uri-supported"))
{
num_options = cupsAddOption(attr->name,
cups_make_string(attr, value,
sizeof(value)),
num_options, &options);
}
#ifdef __APPLE__
else if (!strcmp(attr->name, "media-supported"))
{
int i;
for (i = 0; i < attr->num_values; i ++)
if (!_cups_strcasecmp(media_default, attr->values[i].string.text))
{
num_options = cupsAddOption("media", media_default, num_options,
&options);
break;
}
}
#endif
else if (!strcmp(attr->name, "printer-name") &&
attr->value_tag == IPP_TAG_NAME)
printer_name = attr->values[0].string.text;
else if (strncmp(attr->name, "notify-", 7) &&
(attr->value_tag == IPP_TAG_BOOLEAN ||
attr->value_tag == IPP_TAG_ENUM ||
attr->value_tag == IPP_TAG_INTEGER ||
attr->value_tag == IPP_TAG_KEYWORD ||
attr->value_tag == IPP_TAG_NAME ||
attr->value_tag == IPP_TAG_RANGE) &&
(ptr = strstr(attr->name, "-default")) != NULL)
{
strlcpy(optname, attr->name, sizeof(optname));
optname[ptr - attr->name] = '\0';
if (_cups_strcasecmp(optname, "media") ||
!cupsGetOption("media", num_options, options))
num_options = cupsAddOption(optname,
cups_make_string(attr, value,
sizeof(value)),
num_options, &options);
}
}
if (!printer_name)
{
cupsFreeOptions(num_options, options);
if (attr == NULL)
break;
else
continue;
}
if ((dest = cups_add_dest(printer_name, NULL, &num_dests, dests)) != NULL)
{
dest->num_options = num_options;
dest->options = options;
}
else
cupsFreeOptions(num_options, options);
if (attr == NULL)
break;
}
ippDelete(response);
}
return (num_dests);
}
int
cupsGetDests(cups_dest_t **dests)
{
return (cupsGetDests2(CUPS_HTTP_DEFAULT, dests));
}
int
cupsGetDests2(http_t *http,
cups_dest_t **dests)
{
int i;
int num_dests;
cups_dest_t *dest;
const char *home;
char filename[1024];
const char *defprinter;
char name[1024],
*instance,
*user_default;
int num_reals;
cups_dest_t *reals;
_cups_globals_t *cg = _cupsGlobals();
if (!dests)
{
_cupsSetError(IPP_INTERNAL_ERROR, _("Bad NULL dests pointer"), 1);
return (0);
}
*dests = (cups_dest_t *)0;
num_dests = _cupsGetDests(http, CUPS_GET_PRINTERS, NULL, dests, 0, 0);
if (cupsLastError() >= IPP_REDIRECTION_OTHER_SITE)
{
cupsFreeDests(num_dests, *dests);
*dests = (cups_dest_t *)0;
return (0);
}
if (num_dests > 0)
{
num_reals = num_dests;
reals = calloc(num_reals, sizeof(cups_dest_t));
if (reals)
memcpy(reals, *dests, num_reals * sizeof(cups_dest_t));
else
num_reals = 0;
}
else
{
num_reals = 0;
reals = NULL;
}
if ((user_default = _cupsUserDefault(name, sizeof(name))) != NULL)
defprinter = name;
else if ((defprinter = cupsGetDefault2(http)) != NULL)
{
strlcpy(name, defprinter, sizeof(name));
defprinter = name;
}
if (defprinter)
{
if ((instance = strchr(name, '/')) != NULL)
*instance++ = '\0';
if ((dest = cupsGetDest(name, instance, num_dests, *dests)) != NULL)
dest->is_default = 1;
}
else
instance = NULL;
snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot);
num_dests = cups_get_dests(filename, NULL, NULL, user_default != NULL,
num_dests, dests);
if ((home = getenv("HOME")) != NULL)
{
snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home);
num_dests = cups_get_dests(filename, NULL, NULL, user_default != NULL,
num_dests, dests);
}
if (num_reals)
{
if ((dest = cupsGetDest(NULL, NULL, num_dests, *dests)) != NULL)
{
dest = cupsGetDest(dest->name, NULL, num_reals, reals);
}
if (!dest && *dests && defprinter)
{
for (i = 0; i < num_dests; i ++)
(*dests)[i].is_default = 0;
if ((dest = cupsGetDest(name, instance, num_dests, *dests)) != NULL)
dest->is_default = 1;
}
free(reals);
}
if (num_dests > 0)
_cupsSetError(IPP_OK, NULL, 0);
return (num_dests);
}
cups_dest_t *
cupsGetNamedDest(http_t *http,
const char *name,
const char *instance)
{
cups_dest_t *dest;
char filename[1024],
defname[256];
const char *home = getenv("HOME");
int set_as_default = 0;
ipp_op_t op = IPP_GET_PRINTER_ATTRIBUTES;
_cups_globals_t *cg = _cupsGlobals();
if (!name)
{
set_as_default = 1;
name = _cupsUserDefault(defname, sizeof(defname));
if (name)
{
char *ptr;
if ((ptr = strchr(defname, '/')) != NULL)
{
*ptr++ = '\0';
instance = ptr;
}
else
instance = NULL;
}
else if (home)
{
snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home);
name = cups_get_default(filename, defname, sizeof(defname), &instance);
}
if (!name)
{
snprintf(filename, sizeof(filename), "%s/lpoptions",
cg->cups_serverroot);
name = cups_get_default(filename, defname, sizeof(defname), &instance);
}
if (!name)
{
op = CUPS_GET_DEFAULT;
}
}
if (!_cupsGetDests(http, op, name, &dest, 0, 0))
{
if (op == CUPS_GET_DEFAULT || (name && !set_as_default))
return (NULL);
if (!_cupsGetDests(http, CUPS_GET_DEFAULT, NULL, &dest, 0, 0))
return (NULL);
}
if (instance)
dest->instance = _cupsStrAlloc(instance);
if (set_as_default)
dest->is_default = 1;
snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot);
cups_get_dests(filename, name, instance, 1, 1, &dest);
if (home)
{
snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home);
cups_get_dests(filename, name, instance, 1, 1, &dest);
}
return (dest);
}
int
cupsRemoveDest(const char *name,
const char *instance,
int num_dests,
cups_dest_t **dests)
{
int i;
cups_dest_t *dest;
if ((dest = cupsGetDest(name, instance, num_dests, *dests)) == NULL)
return (num_dests);
_cupsStrFree(dest->name);
_cupsStrFree(dest->instance);
cupsFreeOptions(dest->num_options, dest->options);
num_dests --;
i = dest - *dests;
if (i < num_dests)
memmove(dest, dest + 1, (num_dests - i) * sizeof(cups_dest_t));
return (num_dests);
}
void
cupsSetDefaultDest(
const char *name,
const char *instance,
int num_dests,
cups_dest_t *dests)
{
int i;
cups_dest_t *dest;
if (!name || num_dests <= 0 || !dests)
return;
for (i = num_dests, dest = dests; i > 0; i --, dest ++)
dest->is_default = !_cups_strcasecmp(name, dest->name) &&
((!instance && !dest->instance) ||
(instance && dest->instance &&
!_cups_strcasecmp(instance, dest->instance)));
}
void
cupsSetDests(int num_dests,
cups_dest_t *dests)
{
cupsSetDests2(CUPS_HTTP_DEFAULT, num_dests, dests);
}
int
cupsSetDests2(http_t *http,
int num_dests,
cups_dest_t *dests)
{
int i, j;
int wrote;
cups_dest_t *dest;
cups_option_t *option;
_ipp_option_t *match;
FILE *fp;
#ifndef WIN32
const char *home;
#endif
char filename[1024];
int num_temps;
cups_dest_t *temps = NULL,
*temp;
const char *val;
_cups_globals_t *cg = _cupsGlobals();
if (!num_dests || !dests)
return (-1);
num_temps = _cupsGetDests(http, CUPS_GET_PRINTERS, NULL, &temps, 0, 0);
if (cupsLastError() >= IPP_REDIRECTION_OTHER_SITE)
{
cupsFreeDests(num_temps, temps);
return (-1);
}
snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot);
#ifndef WIN32
if (getuid())
{
num_temps = cups_get_dests(filename, NULL, NULL, 0, num_temps, &temps);
if ((home = getenv("HOME")) != NULL)
{
snprintf(filename, sizeof(filename), "%s/.cups", home);
if (access(filename, 0))
mkdir(filename, 0700);
snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home);
}
}
#endif
if ((fp = fopen(filename, "w")) == NULL)
{
cupsFreeDests(num_temps, temps);
return (-1);
}
#ifndef WIN32
if (!getuid())
fchmod(fileno(fp), 0644);
#endif
for (i = num_dests, dest = dests; i > 0; i --, dest ++)
if (dest->instance != NULL || dest->num_options != 0 || dest->is_default)
{
if (dest->is_default)
{
fprintf(fp, "Default %s", dest->name);
if (dest->instance)
fprintf(fp, "/%s", dest->instance);
wrote = 1;
}
else
wrote = 0;
if ((temp = cupsGetDest(dest->name, dest->instance, num_temps, temps)) == NULL)
temp = cupsGetDest(dest->name, NULL, num_temps, temps);
for (j = dest->num_options, option = dest->options; j > 0; j --, option ++)
{
if ((match = _ippFindOption(option->name)) != NULL &&
match->group_tag == IPP_TAG_PRINTER)
continue;
if (temp &&
(val = cupsGetOption(option->name, temp->num_options,
temp->options)) != NULL &&
!_cups_strcasecmp(val, option->value))
continue;
if (!wrote)
{
fprintf(fp, "Dest %s", dest->name);
if (dest->instance)
fprintf(fp, "/%s", dest->instance);
wrote = 1;
}
if (option->value[0])
{
if (strchr(option->value, ' ') ||
strchr(option->value, '\\') ||
strchr(option->value, '\"') ||
strchr(option->value, '\''))
{
fprintf(fp, " %s=\"", option->name);
for (val = option->value; *val; val ++)
{
if (strchr("\"\'\\", *val))
putc('\\', fp);
putc(*val, fp);
}
putc('\"', fp);
}
else
{
fprintf(fp, " %s=%s", option->name, option->value);
}
}
else
fprintf(fp, " %s", option->name);
}
if (wrote)
fputs("\n", fp);
}
cupsFreeDests(num_temps, temps);
fclose(fp);
#ifdef __APPLE__
if ((dest = cupsGetDest(NULL, NULL, num_dests, dests)) != NULL)
{
CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault,
dest->name,
kCFStringEncodingUTF8);
if (name)
{
_cupsAppleSetDefaultPrinter(name);
CFRelease(name);
}
}
#endif
#ifdef HAVE_NOTIFY_POST
notify_post("com.apple.printerListChange");
#endif
return (0);
}
char *
_cupsUserDefault(char *name,
size_t namesize)
{
const char *env;
#ifdef __APPLE__
CFStringRef locprinter;
#endif
if ((env = getenv("LPDEST")) == NULL)
if ((env = getenv("PRINTER")) != NULL && !strcmp(env, "lp"))
env = NULL;
if (env)
{
strlcpy(name, env, namesize);
return (name);
}
#ifdef __APPLE__
if ((locprinter = _cupsAppleCopyDefaultPrinter()) != NULL)
{
CFStringGetCString(locprinter, name, namesize, kCFStringEncodingUTF8);
CFRelease(locprinter);
}
else
name[0] = '\0';
DEBUG_printf(("1_cupsUserDefault: Returning \"%s\".", name));
return (*name ? name : NULL);
#else
name[0] = '\0';
return (NULL);
#endif
}
#ifdef __APPLE__
static CFArrayRef
appleCopyLocations(void)
{
CFArrayRef locations;
if ((locations = CFPreferencesCopyAppValue(kLastUsedPrintersKey,
kCUPSPrintingPrefs)) == NULL)
return (NULL);
if (CFGetTypeID(locations) != CFArrayGetTypeID())
{
CFRelease(locations);
return (NULL);
}
return (locations);
}
static CFStringRef
appleCopyNetwork(void)
{
SCDynamicStoreRef dynamicStore;
CFStringRef key;
CFDictionaryRef ip_dict;
CFStringRef network = NULL;
if ((dynamicStore = SCDynamicStoreCreate(NULL, CFSTR("libcups"), NULL,
NULL)) != NULL)
{
if ((key = SCDynamicStoreKeyCreateNetworkGlobalEntity(
NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6)) != NULL)
{
if ((ip_dict = SCDynamicStoreCopyValue(dynamicStore, key)) != NULL)
{
if ((network = CFDictionaryGetValue(ip_dict,
kSCPropNetIPv6Router)) != NULL)
CFRetain(network);
CFRelease(ip_dict);
}
CFRelease(key);
}
if (!network)
{
if ((key = SCDynamicStoreKeyCreateNetworkGlobalEntity(
NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4)) != NULL)
{
if ((ip_dict = SCDynamicStoreCopyValue(dynamicStore, key)) != NULL)
{
if ((network = CFDictionaryGetValue(ip_dict,
kSCPropNetIPv4Router)) != NULL)
CFRetain(network);
CFRelease(ip_dict);
}
CFRelease(key);
}
}
CFRelease(dynamicStore);
}
return (network);
}
static char *
appleGetPaperSize(char *name,
int namesize)
{
CFStringRef defaultPaperID;
_pwg_media_t *pwgmedia;
defaultPaperID = _cupsAppleCopyDefaultPaperID();
if (!defaultPaperID ||
CFGetTypeID(defaultPaperID) != CFStringGetTypeID() ||
!CFStringGetCString(defaultPaperID, name, namesize,
kCFStringEncodingUTF8))
name[0] = '\0';
else if ((pwgmedia = _pwgMediaForLegacy(name)) != NULL)
strlcpy(name, pwgmedia->pwg, namesize);
if (defaultPaperID)
CFRelease(defaultPaperID);
return (name);
}
static CFStringRef
appleGetPrinter(CFArrayRef locations,
CFStringRef network,
CFIndex *locindex)
{
CFIndex i,
count;
CFDictionaryRef location;
CFStringRef locnetwork,
locprinter;
for (i = 0, count = CFArrayGetCount(locations); i < count; i ++)
if ((location = CFArrayGetValueAtIndex(locations, i)) != NULL &&
CFGetTypeID(location) == CFDictionaryGetTypeID())
{
if ((locnetwork = CFDictionaryGetValue(location,
kLocationNetworkKey)) != NULL &&
CFGetTypeID(locnetwork) == CFStringGetTypeID() &&
CFStringCompare(network, locnetwork, 0) == kCFCompareEqualTo &&
(locprinter = CFDictionaryGetValue(location,
kLocationPrinterIDKey)) != NULL &&
CFGetTypeID(locprinter) == CFStringGetTypeID())
{
if (locindex)
*locindex = i;
return (locprinter);
}
}
return (NULL);
}
#endif
static cups_dest_t *
cups_add_dest(const char *name,
const char *instance,
int *num_dests,
cups_dest_t **dests)
{
int insert,
diff;
cups_dest_t *dest;
if (*num_dests == 0)
dest = malloc(sizeof(cups_dest_t));
else
dest = realloc(*dests, sizeof(cups_dest_t) * (*num_dests + 1));
if (!dest)
return (NULL);
*dests = dest;
if (*num_dests == 0)
insert = 0;
else
{
insert = cups_find_dest(name, instance, *num_dests, *dests, *num_dests - 1,
&diff);
if (diff > 0)
insert ++;
}
if (insert < *num_dests)
memmove(*dests + insert + 1, *dests + insert,
(*num_dests - insert) * sizeof(cups_dest_t));
(*num_dests) ++;
dest = *dests + insert;
dest->name = _cupsStrAlloc(name);
dest->instance = _cupsStrAlloc(instance);
dest->is_default = 0;
dest->num_options = 0;
dest->options = (cups_option_t *)0;
return (dest);
}
# ifdef __BLOCKS__
static int
cups_block_cb(
cups_dest_block_t block,
unsigned flags,
cups_dest_t *dest)
{
return ((block)(flags, dest));
}
# endif
static int
cups_compare_dests(cups_dest_t *a,
cups_dest_t *b)
{
int diff;
if ((diff = _cups_strcasecmp(a->name, b->name)) != 0)
return (diff);
else if (a->instance && b->instance)
return (_cups_strcasecmp(a->instance, b->instance));
else
return ((a->instance && !b->instance) - (!a->instance && b->instance));
}
#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
# ifdef HAVE_DNSSD
static void
cups_dnssd_browse_cb(
DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *serviceName,
const char *regtype,
const char *replyDomain,
void *context)
{
_cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context;
DEBUG_printf(("5cups_dnssd_browse_cb(sdRef=%p, flags=%x, "
"interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
"regtype=\"%s\", replyDomain=\"%s\", context=%p)",
sdRef, flags, interfaceIndex, errorCode, serviceName, regtype,
replyDomain, context));
if (errorCode != kDNSServiceErr_NoError)
return;
cups_dnssd_get_device(data, serviceName, regtype, replyDomain);
}
# else
static void
cups_dnssd_browse_cb(
AvahiServiceBrowser *browser,
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiBrowserEvent event,
const char *name,
const char *type,
const char *domain,
AvahiLookupResultFlags flags,
void *context)
{
#ifdef DEBUG
AvahiClient *client = avahi_service_browser_get_client(browser);
#endif
_cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context;
(void)interface;
(void)protocol;
(void)context;
switch (event)
{
case AVAHI_BROWSER_FAILURE:
DEBUG_printf(("cups_dnssd_browse_cb: %s",
avahi_strerror(avahi_client_errno(client))));
avahi_simple_poll_quit(data->simple_poll);
break;
case AVAHI_BROWSER_NEW:
if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
{
DEBUG_printf(("cups_dnssd_browse_cb: Ignoring local service \"%s\".",
name));
}
else
{
cups_dnssd_get_device(data, name, type, domain);
}
break;
case AVAHI_BROWSER_REMOVE:
case AVAHI_BROWSER_ALL_FOR_NOW:
case AVAHI_BROWSER_CACHE_EXHAUSTED:
break;
}
}
static void
cups_dnssd_client_cb(
AvahiClient *client,
AvahiClientState state,
void *context)
{
_cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context;
(void)client;
if (state == AVAHI_CLIENT_FAILURE)
{
DEBUG_puts("cups_dnssd_client_cb: Avahi connection failed.");
avahi_simple_poll_quit(data->simple_poll);
}
}
# endif
static int
cups_dnssd_compare_devices(
_cups_dnssd_device_t *a,
_cups_dnssd_device_t *b)
{
return (strcmp(a->dest.name, b->dest.name));
}
static void
cups_dnssd_free_device(
_cups_dnssd_device_t *device,
_cups_dnssd_data_t *data)
{
DEBUG_printf(("5cups_dnssd_free_device(device=%p(%s), data=%p)", device,
device->dest.name, data));
# ifdef HAVE_DNSSD
if (device->ref)
DNSServiceRefDeallocate(device->ref);
# else
if (device->ref)
avahi_record_browser_free(device->ref);
# endif
_cupsStrFree(device->domain);
_cupsStrFree(device->fullName);
_cupsStrFree(device->regtype);
_cupsStrFree(device->dest.name);
cupsFreeOptions(device->dest.num_options, device->dest.options);
free(device);
}
static _cups_dnssd_device_t *
cups_dnssd_get_device(
_cups_dnssd_data_t *data,
const char *serviceName,
const char *regtype,
const char *replyDomain)
{
_cups_dnssd_device_t key,
*device;
char fullName[kDNSServiceMaxDomainName];
DEBUG_printf(("5cups_dnssd_get_device(data=%p, serviceName=\"%s\", "
"regtype=\"%s\", replyDomain=\"%s\")", data, serviceName,
regtype, replyDomain));
key.dest.name = (char *)serviceName;
if ((device = cupsArrayFind(data->devices, &key)) != NULL)
{
int update = 0;
if (!_cups_strcasecmp(replyDomain, "local.") &&
_cups_strcasecmp(device->domain, replyDomain))
{
_cupsStrFree(device->domain);
device->domain = _cupsStrAlloc(replyDomain);
DEBUG_printf(("6cups_dnssd_get_device: Updating '%s' to use local "
"domain.", device->dest.name));
update = 1;
}
if (!_cups_strcasecmp(regtype, "_ipps._tcp") &&
_cups_strcasecmp(device->regtype, regtype))
{
_cupsStrFree(device->regtype);
device->regtype = _cupsStrAlloc(regtype);
DEBUG_printf(("6cups_dnssd_get_device: Updating '%s' to use IPPS.",
device->dest.name));
update = 1;
}
if (!update)
{
DEBUG_printf(("6cups_dnssd_get_device: No changes to '%s'.",
device->dest.name));
return (device);
}
}
else
{
DEBUG_printf(("6cups_dnssd_get_device: Adding '%s' for %s with domain "
"'%s'.", serviceName,
!strcmp(regtype, "_ipps._tcp") ? "IPPS" : "IPP",
replyDomain));
device = calloc(sizeof(_cups_dnssd_device_t), 1);
device->dest.name = _cupsStrAlloc(serviceName);
device->domain = _cupsStrAlloc(replyDomain);
device->regtype = _cupsStrAlloc(regtype);
cupsArrayAdd(data->devices, device);
}
# ifdef HAVE_DNSSD
DNSServiceConstructFullName(fullName, device->dest.name, device->regtype,
device->domain);
# else
avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName,
regtype, replyDomain);
# endif
_cupsStrFree(device->fullName);
device->fullName = _cupsStrAlloc(fullName);
if (device->ref)
{
# ifdef HAVE_DNSSD
DNSServiceRefDeallocate(device->ref);
# else
avahi_record_browser_free(device->ref);
# endif
device->ref = 0;
}
if (device->state == _CUPS_DNSSD_ACTIVE)
{
(*data->cb)(data->user_data, CUPS_DEST_FLAGS_REMOVED, &device->dest);
device->state = _CUPS_DNSSD_NEW;
}
return (device);
}
# ifdef HAVE_DNSSD
static void
cups_dnssd_local_cb(
DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *serviceName,
const char *regtype,
const char *replyDomain,
void *context)
{
_cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context;
_cups_dnssd_device_t *device;
DEBUG_printf(("5cups_dnssd_local_cb(sdRef=%p, flags=%x, "
"interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
"regtype=\"%s\", replyDomain=\"%s\", context=%p)",
sdRef, flags, interfaceIndex, errorCode, serviceName,
regtype, replyDomain, context));
if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
return;
device = cups_dnssd_get_device(data, serviceName, regtype, replyDomain);
DEBUG_printf(("6cups_dnssd_local_cb: Hiding local printer '%s'.",
serviceName));
if (device->ref)
{
DNSServiceRefDeallocate(device->ref);
device->ref = 0;
}
if (device->state == _CUPS_DNSSD_ACTIVE)
(*data->cb)(data->user_data, CUPS_DEST_FLAGS_REMOVED, &device->dest);
device->state = _CUPS_DNSSD_LOCAL;
}
# endif
# ifdef HAVE_AVAHI
static int
cups_dnssd_poll_cb(
struct pollfd *pollfds,
unsigned int num_pollfds,
int timeout,
void *context)
{
_cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context;
int val;
(void)timeout;
val = poll(pollfds, num_pollfds, 250);
if (val < 0)
{
DEBUG_printf(("cups_dnssd_poll_cb: %s", strerror(errno)));
}
else if (val > 0)
data->got_data = 1;
return (val);
}
# endif
# ifdef HAVE_DNSSD
static void
cups_dnssd_query_cb(
DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *fullName,
uint16_t rrtype,
uint16_t rrclass,
uint16_t rdlen,
const void *rdata,
uint32_t ttl,
void *context)
{
# else
static void
cups_dnssd_query_cb(
AvahiRecordBrowser *browser,
AvahiIfIndex interfaceIndex,
AvahiProtocol protocol,
AvahiBrowserEvent event,
const char *fullName,
uint16_t rrclass,
uint16_t rrtype,
const void *rdata,
size_t rdlen,
AvahiLookupResultFlags flags,
void *context)
{
# ifdef DEBUG
AvahiClient *client = avahi_record_browser_get_client(browser);
# endif
# endif
_cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context;
char name[1024],
*ptr;
_cups_dnssd_device_t dkey,
*device;
# ifdef HAVE_DNSSD
DEBUG_printf(("5cups_dnssd_query_cb(sdRef=%p, flags=%x, "
"interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
"rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, "
"context=%p)", sdRef, flags, interfaceIndex, errorCode,
fullName, rrtype, rrclass, rdlen, rdata, ttl, context));
if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
return;
# else
DEBUG_printf(("5cups_dnssd_query_cb(browser=%p, interfaceIndex=%d, "
"protocol=%d, event=%d, fullName=\"%s\", rrclass=%u, "
"rrtype=%u, rdata=%p, rdlen=%u, flags=%x, context=%p)",
browser, interfaceIndex, protocol, event, fullName, rrclass,
rrtype, rdata, (unsigned)rdlen, flags, context));
if (event != AVAHI_BROWSER_NEW)
{
if (event == AVAHI_BROWSER_FAILURE)
DEBUG_printf(("cups_dnssd_query_cb: %s",
avahi_strerror(avahi_client_errno(client))));
return;
}
# endif
dkey.dest.name = name;
cups_dnssd_unquote(name, fullName, sizeof(name));
if ((ptr = strstr(name, "._")) != NULL)
*ptr = '\0';
if ((device = cupsArrayFind(data->devices, &dkey)) != NULL)
{
const uint8_t *txt,
*txtnext,
*txtend;
uint8_t txtlen;
char key[256],
value[256],
make_and_model[512],
model[256],
uriname[1024],
uri[1024];
cups_ptype_t type = CUPS_PRINTER_REMOTE | CUPS_PRINTER_BW;
int saw_printer_type = 0;
device->state = _CUPS_DNSSD_PENDING;
make_and_model[0] = '\0';
strcpy(model, "Unknown");
for (txt = rdata, txtend = txt + rdlen;
txt < txtend;
txt = txtnext)
{
txtlen = *txt++;
if (!txtlen || (txt + txtlen) > txtend)
break;
txtnext = txt + txtlen;
for (ptr = key; txt < txtnext && *txt != '='; txt ++)
*ptr++ = *txt;
*ptr = '\0';
if (txt < txtnext && *txt == '=')
{
txt ++;
if (txt < txtnext)
memcpy(value, txt, txtnext - txt);
value[txtnext - txt] = '\0';
DEBUG_printf(("6cups_dnssd_query_cb: %s=%s", key, value));
}
else
{
DEBUG_printf(("6cups_dnssd_query_cb: '%s' with no value.", key));
continue;
}
if (!_cups_strcasecmp(key, "usb_MFG") ||
!_cups_strcasecmp(key, "usb_MANU") ||
!_cups_strcasecmp(key, "usb_MANUFACTURER"))
strcpy(make_and_model, value);
else if (!_cups_strcasecmp(key, "usb_MDL") ||
!_cups_strcasecmp(key, "usb_MODEL"))
strcpy(model, value);
else if (!_cups_strcasecmp(key, "product") && !strstr(value, "Ghostscript"))
{
if (value[0] == '(')
{
if ((ptr = value + strlen(value) - 1) > value && *ptr == ')')
*ptr = '\0';
strcpy(model, value + 1);
}
else
strcpy(model, value);
}
else if (!_cups_strcasecmp(key, "ty"))
{
strcpy(model, value);
if ((ptr = strchr(model, ',')) != NULL)
*ptr = '\0';
}
else if (!_cups_strcasecmp(key, "note"))
device->dest.num_options = cupsAddOption("printer-location", value,
device->dest.num_options,
&device->dest.options);
else if (!_cups_strcasecmp(key, "pdl"))
{
const char *start, *next;
int have_pdf = 0;
for (start = value; start && *start; start = next)
{
if (!_cups_strncasecmp(start, "application/pdf", 15) &&
(!start[15] || start[15] == ','))
{
have_pdf = 1;
break;
}
if ((next = strchr(start, ',')) != NULL)
next ++;
}
if (!have_pdf)
device->state = _CUPS_DNSSD_INCOMPATIBLE;
}
else if (!_cups_strcasecmp(key, "printer-type"))
{
saw_printer_type = 1;
type = strtol(value, NULL, 0);
}
else if (!saw_printer_type)
{
if (!_cups_strcasecmp(key, "air") &&
!_cups_strcasecmp(value, "t"))
type |= CUPS_PRINTER_AUTHENTICATED;
else if (!_cups_strcasecmp(key, "bind") &&
!_cups_strcasecmp(value, "t"))
type |= CUPS_PRINTER_BIND;
else if (!_cups_strcasecmp(key, "collate") &&
!_cups_strcasecmp(value, "t"))
type |= CUPS_PRINTER_COLLATE;
else if (!_cups_strcasecmp(key, "color") &&
!_cups_strcasecmp(value, "t"))
type |= CUPS_PRINTER_COLOR;
else if (!_cups_strcasecmp(key, "copies") &&
!_cups_strcasecmp(value, "t"))
type |= CUPS_PRINTER_COPIES;
else if (!_cups_strcasecmp(key, "duplex") &&
!_cups_strcasecmp(value, "t"))
type |= CUPS_PRINTER_DUPLEX;
else if (!_cups_strcasecmp(key, "fax") &&
!_cups_strcasecmp(value, "t"))
type |= CUPS_PRINTER_MFP;
else if (!_cups_strcasecmp(key, "papercustom") &&
!_cups_strcasecmp(value, "t"))
type |= CUPS_PRINTER_VARIABLE;
else if (!_cups_strcasecmp(key, "papermax"))
{
if (!_cups_strcasecmp(value, "legal-a4"))
type |= CUPS_PRINTER_SMALL;
else if (!_cups_strcasecmp(value, "isoc-a2"))
type |= CUPS_PRINTER_MEDIUM;
else if (!_cups_strcasecmp(value, ">isoc-a2"))
type |= CUPS_PRINTER_LARGE;
}
else if (!_cups_strcasecmp(key, "punch") &&
!_cups_strcasecmp(value, "t"))
type |= CUPS_PRINTER_PUNCH;
else if (!_cups_strcasecmp(key, "scan") &&
!_cups_strcasecmp(value, "t"))
type |= CUPS_PRINTER_MFP;
else if (!_cups_strcasecmp(key, "sort") &&
!_cups_strcasecmp(value, "t"))
type |= CUPS_PRINTER_SORT;
else if (!_cups_strcasecmp(key, "staple") &&
!_cups_strcasecmp(value, "t"))
type |= CUPS_PRINTER_STAPLE;
}
}
device->dest.num_options = cupsAddOption("printer-info", name,
device->dest.num_options,
&device->dest.options);
if (make_and_model[0])
{
strlcat(make_and_model, " ", sizeof(make_and_model));
strlcat(make_and_model, model, sizeof(make_and_model));
device->dest.num_options = cupsAddOption("printer-make-and-model",
make_and_model,
device->dest.num_options,
&device->dest.options);
}
else
device->dest.num_options = cupsAddOption("printer-make-and-model",
model,
device->dest.num_options,
&device->dest.options);
device->type = type;
snprintf(value, sizeof(value), "%u", type);
device->dest.num_options = cupsAddOption("printer-type", value,
device->dest.num_options,
&device->dest.options);
cups_dnssd_unquote(uriname, device->fullName, sizeof(uriname));
httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri),
!strcmp(device->regtype, "_ipps._tcp") ? "ipps" : "ipp",
NULL, uriname, 0, saw_printer_type ? "/cups" : "/");
DEBUG_printf(("6cups_dnssd_query: printer-uri-supported=\"%s\"", uri));
device->dest.num_options = cupsAddOption("printer-uri-supported", uri,
device->dest.num_options,
&device->dest.options);
}
else
DEBUG_printf(("6cups_dnssd_query: Ignoring TXT record for '%s'.",
fullName));
}
static const char *
cups_dnssd_resolve(
cups_dest_t *dest,
const char *uri,
int msec,
int *cancel,
cups_dest_cb_t cb,
void *user_data)
{
char tempuri[1024];
_cups_dnssd_resolve_t resolve;
resolve.cancel = cancel;
gettimeofday(&resolve.end_time, NULL);
if (msec > 0)
{
resolve.end_time.tv_sec += msec / 1000;
resolve.end_time.tv_usec += (msec % 1000) * 1000;
while (resolve.end_time.tv_usec >= 1000000)
{
resolve.end_time.tv_sec ++;
resolve.end_time.tv_usec -= 1000000;
}
}
else
resolve.end_time.tv_sec += 75;
if (cb)
(*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_RESOLVING,
dest);
if ((uri = _httpResolveURI(uri, tempuri, sizeof(tempuri),
_HTTP_RESOLVE_FQDN, cups_dnssd_resolve_cb,
&resolve)) == NULL)
{
_cupsSetError(IPP_INTERNAL_ERROR, _("Unable to resolve printer URI."), 1);
if (cb)
(*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR,
dest);
return (NULL);
}
dest->num_options = cupsAddOption("printer-uri-supported", uri,
dest->num_options, &dest->options);
return (cupsGetOption("printer-uri-supported", dest->num_options,
dest->options));
}
static int
cups_dnssd_resolve_cb(void *context)
{
_cups_dnssd_resolve_t *resolve = (_cups_dnssd_resolve_t *)context;
struct timeval curtime;
if (*resolve->cancel)
return (0);
gettimeofday(&curtime, NULL);
return (curtime.tv_sec > resolve->end_time.tv_sec ||
(curtime.tv_sec == resolve->end_time.tv_sec &&
curtime.tv_usec > resolve->end_time.tv_usec));
}
static void
cups_dnssd_unquote(char *dst,
const char *src,
size_t dstsize)
{
char *dstend = dst + dstsize - 1;
while (*src && dst < dstend)
{
if (*src == '\\')
{
src ++;
if (isdigit(src[0] & 255) && isdigit(src[1] & 255) &&
isdigit(src[2] & 255))
{
*dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0';
src += 3;
}
else
*dst++ = *src++;
}
else
*dst++ = *src ++;
}
*dst = '\0';
}
#endif
static int
cups_find_dest(const char *name,
const char *instance,
int num_dests,
cups_dest_t *dests,
int prev,
int *rdiff)
{
int left,
right,
current,
diff;
cups_dest_t key;
key.name = (char *)name;
key.instance = (char *)instance;
if (prev >= 0)
{
if ((diff = cups_compare_dests(&key, dests + prev)) == 0 ||
(diff < 0 && prev == 0) ||
(diff > 0 && prev == (num_dests - 1)))
{
*rdiff = diff;
return (prev);
}
else if (diff < 0)
{
left = 0;
right = prev;
}
else
{
left = prev;
right = num_dests - 1;
}
}
else
{
left = 0;
right = num_dests - 1;
}
do
{
current = (left + right) / 2;
diff = cups_compare_dests(&key, dests + current);
if (diff == 0)
break;
else if (diff < 0)
right = current;
else
left = current;
}
while ((right - left) > 1);
if (diff != 0)
{
if ((diff = cups_compare_dests(&key, dests + left)) <= 0)
current = left;
else
{
diff = cups_compare_dests(&key, dests + right);
current = right;
}
}
*rdiff = diff;
return (current);
}
static char *
cups_get_default(const char *filename,
char *namebuf,
size_t namesize,
const char **instance)
{
cups_file_t *fp;
char line[8192],
*value,
*nameptr;
int linenum;
*namebuf = '\0';
if ((fp = cupsFileOpen(filename, "r")) != NULL)
{
linenum = 0;
while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
{
if (!_cups_strcasecmp(line, "default") && value)
{
strlcpy(namebuf, value, namesize);
if ((nameptr = strchr(namebuf, ' ')) != NULL)
*nameptr = '\0';
if ((nameptr = strchr(namebuf, '\t')) != NULL)
*nameptr = '\0';
if ((nameptr = strchr(namebuf, '/')) != NULL)
*nameptr++ = '\0';
*instance = nameptr;
break;
}
}
cupsFileClose(fp);
}
return (*namebuf ? namebuf : NULL);
}
static int
cups_get_dests(
const char *filename,
const char *match_name,
const char *match_inst,
int user_default_set,
int num_dests,
cups_dest_t **dests)
{
int i;
cups_dest_t *dest;
cups_file_t *fp;
char line[8192],
*lineptr,
*name,
*instance;
int linenum;
DEBUG_printf(("7cups_get_dests(filename=\"%s\", match_name=\"%s\", "
"match_inst=\"%s\", user_default_set=%d, num_dests=%d, "
"dests=%p)", filename, match_name, match_inst,
user_default_set, num_dests, dests));
if ((fp = cupsFileOpen(filename, "r")) == NULL)
return (num_dests);
linenum = 0;
while (cupsFileGetConf(fp, line, sizeof(line), &lineptr, &linenum))
{
DEBUG_printf(("9cups_get_dests: linenum=%d line=\"%s\" lineptr=\"%s\"",
linenum, line, lineptr));
if ((_cups_strcasecmp(line, "dest") && _cups_strcasecmp(line, "default")) || !lineptr)
{
DEBUG_puts("9cups_get_dests: Not a dest or default line...");
continue;
}
name = lineptr;
while (!isspace(*lineptr & 255) && *lineptr && *lineptr != '/')
lineptr ++;
if (*lineptr == '/')
{
*lineptr++ = '\0';
instance = lineptr;
while (!isspace(*lineptr & 255) && *lineptr)
lineptr ++;
}
else
instance = NULL;
if (*lineptr)
*lineptr++ = '\0';
DEBUG_printf(("9cups_get_dests: name=\"%s\", instance=\"%s\"", name,
instance));
if (match_name)
{
if (_cups_strcasecmp(name, match_name) ||
(!instance && match_inst) ||
(instance && !match_inst) ||
(instance && _cups_strcasecmp(instance, match_inst)))
continue;
dest = *dests;
}
else if (cupsGetDest(name, NULL, num_dests, *dests) == NULL)
{
DEBUG_puts("9cups_get_dests: Not found!");
continue;
}
else
{
num_dests = cupsAddDest(name, instance, num_dests, dests);
if ((dest = cupsGetDest(name, instance, num_dests, *dests)) == NULL)
{
DEBUG_puts("9cups_get_dests: Out of memory!");
break;
}
}
dest->num_options = cupsParseOptions(lineptr, dest->num_options,
&(dest->options));
if (match_name)
break;
if (!user_default_set && !_cups_strcasecmp(line, "default"))
{
DEBUG_puts("9cups_get_dests: Setting as default...");
for (i = 0; i < num_dests; i ++)
(*dests)[i].is_default = 0;
dest->is_default = 1;
}
}
cupsFileClose(fp);
return (num_dests);
}
static char *
cups_make_string(
ipp_attribute_t *attr,
char *buffer,
size_t bufsize)
{
int i;
char *ptr,
*end,
*valptr;
if (attr->num_values == 1 &&
attr->value_tag != IPP_TAG_INTEGER &&
attr->value_tag != IPP_TAG_ENUM &&
attr->value_tag != IPP_TAG_BOOLEAN &&
attr->value_tag != IPP_TAG_RANGE)
return (attr->values[0].string.text);
end = buffer + bufsize - 1;
for (i = 0, ptr = buffer; i < attr->num_values && ptr < end; i ++)
{
if (i)
*ptr++ = ',';
switch (attr->value_tag)
{
case IPP_TAG_INTEGER :
case IPP_TAG_ENUM :
snprintf(ptr, end - ptr + 1, "%d", attr->values[i].integer);
break;
case IPP_TAG_BOOLEAN :
if (attr->values[i].boolean)
strlcpy(ptr, "true", end - ptr + 1);
else
strlcpy(ptr, "false", end - ptr + 1);
break;
case IPP_TAG_RANGE :
if (attr->values[i].range.lower == attr->values[i].range.upper)
snprintf(ptr, end - ptr + 1, "%d", attr->values[i].range.lower);
else
snprintf(ptr, end - ptr + 1, "%d-%d", attr->values[i].range.lower,
attr->values[i].range.upper);
break;
default :
for (valptr = attr->values[i].string.text;
*valptr && ptr < end;)
{
if (strchr(" \t\n\\\'\"", *valptr))
{
if (ptr >= (end - 1))
break;
*ptr++ = '\\';
}
*ptr++ = *valptr++;
}
*ptr = '\0';
break;
}
ptr += strlen(ptr);
}
*ptr = '\0';
return (buffer);
}