From 1106b00e75e37e7f25a28ee95da560de55520018 Mon Sep 17 00:00:00 2001 From: msweet Date: Wed, 17 Nov 2010 18:58:56 +0000 Subject: [PATCH] Merge changes from CUPS 1.5svn-r9374. git-svn-id: svn+ssh://src.apple.com/svn/cups/easysw/current@2859 a1ca3aef-8c08-0410-bb20-df032aa958be --- CHANGES-1.4.txt | 4 + backend/ipp.c | 3 +- cups/adminutil.c | 77 +- cups/http-addr.c | 47 +- cups/http-private.h | 5 +- cups/http-support.c | 17 + cups/http.c | 7 +- cups/ipp-support.c | 52 +- cups/ipp.c | 10 +- cups/ipp.h | 3 +- cups/thread-private.h | 11 + cups/thread.c | 173 ++ man/ipptool.man | 10 +- scheduler/dirsvc.c | 8 +- test/Dependencies | 8 + test/Makefile | 23 +- test/ippserver.c | 4299 +++++++++++++++++++++++++++++++++++++++++ test/ipptool.c | 34 +- test/printer.opacity | Bin 0 -> 18493 bytes test/printer.png | Bin 0 -> 5133 bytes 20 files changed, 4735 insertions(+), 56 deletions(-) create mode 100644 test/ippserver.c create mode 100644 test/printer.opacity create mode 100644 test/printer.png diff --git a/CHANGES-1.4.txt b/CHANGES-1.4.txt index 1cd4d6cf5..674530c5b 100644 --- a/CHANGES-1.4.txt +++ b/CHANGES-1.4.txt @@ -3,12 +3,16 @@ CHANGES-1.4.txt CHANGES IN CUPS V1.4.6 + - The cupsAdminSetServerSettings() function disabled sharing when + debug logging was enabled (STR #3712) CHANGES IN CUPS V1.4.5 - Documentation fixes (STR #3542, STR #3650) - Localization fixes (STR #3635, STR #3636, STR #3647, STR #3666) + - Security: Fixed a memory corruption bug reported in CVE-2010-2941 + (STR #3648) - The CUPS API incorrectly mapped the HTTP_UNAUTHORIZED status to the IPP_NOT_AUTHORIZED status code, when IPP_NOT_AUTHENTICATED would be the correct mapping (STR #3684) diff --git a/backend/ipp.c b/backend/ipp.c index 5701ce5d4..810e48b0d 100644 --- a/backend/ipp.c +++ b/backend/ipp.c @@ -1719,7 +1719,8 @@ monitor_printer( * Make a copy of the printer connection... */ - http = _httpCreate(monitor->hostname, monitor->port, monitor->encryption); + http = _httpCreate(monitor->hostname, monitor->port, monitor->encryption, + AF_UNSPEC); cupsSetPasswordCB(password_cb); /* diff --git a/cups/adminutil.c b/cups/adminutil.c index 68efcfd44..b8d28126b 100644 --- a/cups/adminutil.c +++ b/cups/adminutil.c @@ -16,16 +16,14 @@ * * Contents: * - * cupsAdminCreateWindowsPPD() - Create the Windows PPD file for a printer. - * cupsAdminExportSamba() - Export a printer to Samba. - * cupsAdminGetServerSettings() - Get settings from the server. - * _cupsAdminGetServerSettings() - Get settings from the server (private). - * cupsAdminSetServerSettings() - Set settings on the server. - * _cupsAdminSetServerSettings() - Set settings on the server (private). - * do_samba_command() - Do a SAMBA command. - * get_cupsd_conf() - Get the current cupsd.conf file. - * invalidate_cupsd_cache() - Invalidate the cached cupsd.conf settings. - * write_option() - Write a CUPS option to a PPD file. + * cupsAdminCreateWindowsPPD() - Create the Windows PPD file for a printer. + * cupsAdminExportSamba() - Export a printer to Samba. + * cupsAdminGetServerSettings() - Get settings from the server. + * cupsAdminSetServerSettings() - Set settings on the server. + * do_samba_command() - Do a SAMBA command. + * get_cupsd_conf() - Get the current cupsd.conf file. + * invalidate_cupsd_cache() - Invalidate the cached cupsd.conf settings. + * write_option() - Write a CUPS option to a PPD file. */ /* @@ -47,12 +45,6 @@ * Local functions... */ -extern int _cupsAdminGetServerSettings(http_t *http, - int *num_settings, - cups_option_t **settings); -extern int _cupsAdminSetServerSettings(http_t *http, - int num_settings, - cups_option_t *settings); static int do_samba_command(const char *command, const char *address, const char *subcommand, @@ -910,7 +902,7 @@ cupsAdminGetServerSettings( if (!cg->http) { if ((cg->http = _httpCreate(cupsServer(), ippPort(), - cupsEncryption())) == NULL) + cupsEncryption(), AF_UNSPEC)) == NULL) { if (errno) _cupsSetError(IPP_SERVICE_UNAVAILABLE, NULL, 0); @@ -1021,7 +1013,7 @@ cupsAdminGetServerSettings( && *value != '/' #endif /* AF_LOCAL */ #ifdef AF_INET6 - && strcmp(value, "::1") + && strcmp(value, "[::1]") #endif /* AF_INET6 */ ) remote_access = 1; @@ -1281,12 +1273,18 @@ cupsAdminSetServerSettings( else old_debug_logging = 0; + DEBUG_printf(("1cupsAdminSetServerSettings: old debug_logging=%d", + old_debug_logging)); + if ((val = cupsGetOption(CUPS_SERVER_REMOTE_ADMIN, cupsd_num_settings, cupsd_settings)) != NULL) old_remote_admin = atoi(val); else old_remote_admin = 0; + DEBUG_printf(("1cupsAdminSetServerSettings: old remote_admin=%d", + old_remote_admin)); + if ((val = cupsGetOption(CUPS_SERVER_REMOTE_ANY, cupsd_num_settings, cupsd_settings)) != NULL) remote_any = atoi(val); @@ -1302,18 +1300,27 @@ cupsAdminSetServerSettings( else old_remote_printers = 1; + DEBUG_printf(("1cupsAdminSetServerSettings: old remote_printers=%d", + old_remote_printers)); + if ((val = cupsGetOption(CUPS_SERVER_SHARE_PRINTERS, cupsd_num_settings, cupsd_settings)) != NULL) old_share_printers = atoi(val); else old_share_printers = 0; + DEBUG_printf(("1cupsAdminSetServerSettings: old share_printers=%d", + old_share_printers)); + if ((val = cupsGetOption(CUPS_SERVER_USER_CANCEL_ANY, cupsd_num_settings, cupsd_settings)) != NULL) old_user_cancel_any = atoi(val); else old_user_cancel_any = 0; + DEBUG_printf(("1cupsAdminSetServerSettings: old user_cancel_any=%d", + old_user_cancel_any)); + cupsFreeOptions(cupsd_num_settings, cupsd_settings); /* @@ -1337,16 +1344,22 @@ cupsAdminSetServerSettings( else debug_logging = -1; + DEBUG_printf(("1cupsAdminSetServerSettings: debug_logging=%d", + debug_logging)); + if ((val = cupsGetOption(CUPS_SERVER_REMOTE_ANY, num_settings, settings)) != NULL) remote_any = atoi(val); + DEBUG_printf(("1cupsAdminSetServerSettings: remote_any=%d", + remote_any)); + if ((val = cupsGetOption(CUPS_SERVER_REMOTE_ADMIN, num_settings, settings)) != NULL) { remote_admin = atoi(val); - if (remote_admin == old_remote_admin && remote_any < 0) + if (remote_admin == old_remote_admin) { /* * No change to this setting... @@ -1358,6 +1371,9 @@ cupsAdminSetServerSettings( else remote_admin = -1; + DEBUG_printf(("1cupsAdminSetServerSettings: remote_admin=%d", + remote_admin)); + if ((val = cupsGetOption(CUPS_SERVER_REMOTE_PRINTERS, num_settings, settings)) != NULL) { @@ -1375,12 +1391,15 @@ cupsAdminSetServerSettings( else remote_printers = -1; + DEBUG_printf(("1cupsAdminSetServerSettings: remote_printers=%d", + remote_printers)); + if ((val = cupsGetOption(CUPS_SERVER_SHARE_PRINTERS, num_settings, settings)) != NULL) { share_printers = atoi(val); - if (share_printers == old_share_printers && remote_any < 0) + if (share_printers == old_share_printers) { /* * No change to this setting... @@ -1392,6 +1411,9 @@ cupsAdminSetServerSettings( else share_printers = -1; + DEBUG_printf(("1cupsAdminSetServerSettings: share_printers=%d", + share_printers)); + if ((val = cupsGetOption(CUPS_SERVER_USER_CANCEL_ANY, num_settings, settings)) != NULL) { @@ -1409,6 +1431,9 @@ cupsAdminSetServerSettings( else user_cancel_any = -1; + DEBUG_printf(("1cupsAdminSetServerSettings: user_cancel_any=%d", + user_cancel_any)); + /* * Create a temporary file for the new cupsd.conf file... */ @@ -1460,7 +1485,7 @@ cupsAdminSetServerSettings( while (cupsFileGetConf(cupsd, line, sizeof(line), &value, &linenum)) { if ((!strcasecmp(line, "Port") || !strcasecmp(line, "Listen")) && - (remote_admin >= 0 || remote_any >= 0 || share_printers >= 0)) + (remote_admin >= 0 || remote_any > 0 || share_printers >= 0)) { if (!wrote_port_listen) { @@ -1675,7 +1700,7 @@ cupsAdminSetServerSettings( remote_any > 0 ? "all" : "@LOCAL"); } else if (in_root_location && - (remote_admin >= 0 || remote_any >= 0 || share_printers >= 0)) + (remote_admin >= 0 || remote_any > 0 || share_printers >= 0)) { wrote_root_location = 1; @@ -1777,7 +1802,7 @@ cupsAdminSetServerSettings( in_cancel_job = 0; } else if ((((in_admin_location || in_conf_location || in_root_location) && - (remote_admin >= 0 || remote_any >= 0)) || + (remote_admin >= 0 || remote_any > 0)) || (in_root_location && share_printers >= 0)) && (!strcasecmp(line, "Allow") || !strcasecmp(line, "Deny") || !strcasecmp(line, "Order"))) @@ -1823,7 +1848,7 @@ cupsAdminSetServerSettings( /* * Record the non-policy, non-location directives that we find * in the server settings, since we cache this info and record it - * in _cupsAdminGetServerSettings()... + * in cupsAdminGetServerSettings()... */ cupsd_num_settings = cupsAddOption(line, value, cupsd_num_settings, @@ -1882,7 +1907,7 @@ cupsAdminSetServerSettings( } if (!wrote_port_listen && - (remote_admin >= 0 || remote_any >= 0 || share_printers >= 0)) + (remote_admin >= 0 || remote_any > 0 || share_printers >= 0)) { if (remote_admin > 0 || remote_any > 0 || share_printers > 0) { @@ -1903,7 +1928,7 @@ cupsAdminSetServerSettings( } if (!wrote_root_location && - (remote_admin >= 0 || remote_any >= 0 || share_printers >= 0)) + (remote_admin >= 0 || remote_any > 0 || share_printers >= 0)) { if (remote_admin > 0 && share_printers > 0) cupsFilePuts(temp, diff --git a/cups/http-addr.c b/cups/http-addr.c index 7f3897c65..dfd89f957 100644 --- a/cups/http-addr.c +++ b/cups/http-addr.c @@ -33,6 +33,12 @@ #ifdef HAVE_RESOLV_H # include #endif /* HAVE_RESOLV_H */ +#ifdef HAVE_COREFOUNDATION +# include +#endif /* HAVE_COREFOUNDATION */ +#ifdef HAVE_SYSTEMCONFIGURATION +# include +#endif /* HAVE_SYSTEMCONFIGURATION */ /* @@ -579,9 +585,6 @@ httpGetHostname(http_t *http, /* I - HTTP connection or NULL */ char *s, /* I - String buffer for name */ int slen) /* I - Size of buffer */ { - struct hostent *host; /* Host entry to get FQDN */ - - if (!s || slen <= 1) return (NULL); @@ -603,12 +606,50 @@ httpGetHostname(http_t *http, /* I - HTTP connection or NULL */ if (!strchr(s, '.')) { +#ifdef HAVE_SYSTEMCONFIGURATION + /* + * The hostname is not a FQDN, so use the local hostname from the + * SystemConfiguration framework... + */ + + SCDynamicStoreRef sc = SCDynamicStoreCreate(kCFAllocatorDefault, + CFSTR("libcups"), NULL, NULL); + /* System configuration data */ + CFStringRef local = sc ? SCDynamicStoreCopyLocalHostName(sc) : NULL; + /* Local host name */ + char localStr[1024]; /* Local host name C string */ + + if (local && CFStringGetCString(local, localStr, sizeof(localStr), + kCFStringEncodingUTF8)) + { + /* + * Append ".local." to the hostname we get... + */ + + snprintf(s, slen, "%s.local.", localStr); + } + + if (local) + CFRelease(local); + if (sc) + CFRelease(sc); + +#else /* * The hostname is not a FQDN, so look it up... */ + struct hostent *host; /* Host entry to get FQDN */ + if ((host = gethostbyname(s)) != NULL && host->h_name) + { + /* + * Use the resolved hostname... + */ + strlcpy(s, host->h_name, slen); + } +#endif /* HAVE_SYSTEMCONFIGURATION */ } } diff --git a/cups/http-private.h b/cups/http-private.h index 483592f51..51b612987 100644 --- a/cups/http-private.h +++ b/cups/http-private.h @@ -311,7 +311,10 @@ extern int _httpAddrPort(http_addr_t *addr); extern http_tls_credentials_t _httpConvertCredentials(cups_array_t *credentials); extern http_t *_httpCreate(const char *host, int port, - http_encryption_t encryption); + http_encryption_t encryption, + int family); +extern char *_httpDecodeURI(char *dst, const char *src, + size_t dstsize); extern void _httpDisconnect(http_t *http); extern char *_httpEncodeURI(char *dst, const char *src, size_t dstsize); diff --git a/cups/http-support.c b/cups/http-support.c index 452a946f3..443ce6c21 100644 --- a/cups/http-support.c +++ b/cups/http-support.c @@ -36,6 +36,7 @@ * httpStatus() - Return a short string describing a HTTP status code. * _cups_hstrerror() - hstrerror() emulation function for Solaris and * others... + * _httpDecodeURI() - Percent-decode a HTTP request URI. * _httpEncodeURI() - Percent-encode a HTTP request URI. * _httpResolveURI() - Resolve a DNS-SD URI. * http_copy_decode() - Copy and decode a URI. @@ -1288,6 +1289,22 @@ _cups_hstrerror(int error) /* I - Error number */ #endif /* !HAVE_HSTRERROR */ +/* + * '_httpDecodeURI()' - Percent-decode a HTTP request URI. + */ + +char * /* O - Decoded URI or NULL on error */ +_httpDecodeURI(char *dst, /* I - Destination buffer */ + const char *src, /* I - Source URI */ + size_t dstsize) /* I - Size of destination buffer */ +{ + if (http_copy_decode(dst, src, (int)dstsize, NULL, 1)) + return (dst); + else + return (NULL); +} + + /* * '_httpEncodeURI()' - Percent-encode a HTTP request URI. */ diff --git a/cups/http.c b/cups/http.c index 06ceb0212..dc117224c 100644 --- a/cups/http.c +++ b/cups/http.c @@ -475,7 +475,7 @@ httpConnectEncrypt( * Create the HTTP structure... */ - if ((http = _httpCreate(host, port, encryption)) == NULL) + if ((http = _httpCreate(host, port, encryption, AF_UNSPEC)) == NULL) return (NULL); /* @@ -628,7 +628,8 @@ http_t * /* O - HTTP connection */ _httpCreate( const char *host, /* I - Hostname */ int port, /* I - Port number */ - http_encryption_t encryption) /* I - Encryption to use */ + http_encryption_t encryption, /* I - Encryption to use */ + int family) /* I - Address family or AF_UNSPEC */ { http_t *http; /* New HTTP connection */ http_addrlist_t *addrlist; /* Host address data */ @@ -649,7 +650,7 @@ _httpCreate( sprintf(service, "%d", port); - if ((addrlist = httpAddrGetList(host, AF_UNSPEC, service)) == NULL) + if ((addrlist = httpAddrGetList(host, family, service)) == NULL) return (NULL); /* diff --git a/cups/ipp-support.c b/cups/ipp-support.c index c12917f26..42b4d47a4 100644 --- a/cups/ipp-support.c +++ b/cups/ipp-support.c @@ -1,5 +1,5 @@ /* - * "$Id: ipp-support.c 7847 2008-08-19 04:22:14Z mike $" + * "$Id: ipp-support.c 9371 2010-11-17 06:21:32Z mike $" * * Internet Printing Protocol support functions for CUPS. * @@ -343,7 +343,7 @@ _ippAttrString(ipp_attribute_t *attr, /* I - Attribute */ bufptr ++; } - switch (attr->value_tag) + switch (attr->value_tag & ~IPP_TAG_COPY) { case IPP_TAG_ENUM : if (!strcmp(attr->name, "printer-state") && @@ -763,5 +763,51 @@ ippTagValue(const char *name) /* I - Tag name */ /* - * End of "$Id: ipp-support.c 7847 2008-08-19 04:22:14Z mike $". + * 'ipp_col_string()' - Convert a collection to a string. + */ + +static size_t /* O - Number of bytes */ +ipp_col_string(ipp_t *col, /* I - Collection attribute */ + char *buffer, /* I - Buffer or NULL */ + size_t bufsize) /* I - Size of buffer */ +{ + char *bufptr, /* Position in buffer */ + *bufend, /* End of buffer */ + temp[256]; /* Temporary string */ + ipp_attribute_t *attr; /* Current member attribute */ + + + bufptr = buffer; + bufend = buffer + bufsize - 1; + + if (buffer && bufptr < bufend) + *bufptr = '{'; + bufptr ++; + + for (attr = col->attrs; attr; attr = attr->next) + { + if (!attr->name) + continue; + + if (buffer && bufptr < bufend) + bufptr += snprintf(bufptr, bufend - bufptr + 1, "%s=", attr->name); + else + bufptr += strlen(attr->name) + 1; + + if (buffer && bufptr < bufend) + bufptr += _ippAttrString(attr, bufptr, bufend - bufptr + 1); + else + bufptr += _ippAttrString(attr, temp, sizeof(temp)); + } + + if (buffer && bufptr < bufend) + *bufptr = '}'; + bufptr ++; + + return (bufptr - buffer); +} + + +/* + * End of "$Id: ipp-support.c 9371 2010-11-17 06:21:32Z mike $". */ diff --git a/cups/ipp.c b/cups/ipp.c index 3e90b8b4d..8f4360599 100644 --- a/cups/ipp.c +++ b/cups/ipp.c @@ -1271,7 +1271,9 @@ ippReadIO(void *src, /* I - Data source */ attr->value_tag = tag; } - else if ((value_tag >= IPP_TAG_TEXTLANG && + else if (value_tag == IPP_TAG_TEXTLANG || + value_tag == IPP_TAG_NAMELANG || + (value_tag >= IPP_TAG_TEXT && value_tag <= IPP_TAG_MIMETYPE)) { /* @@ -1279,8 +1281,9 @@ ippReadIO(void *src, /* I - Data source */ * forms; accept sets of differing values... */ - if ((tag < IPP_TAG_TEXTLANG || tag > IPP_TAG_MIMETYPE) && - tag != IPP_TAG_NOVALUE) + if (tag != IPP_TAG_TEXTLANG && tag != IPP_TAG_NAMELANG && + (tag < IPP_TAG_TEXT || tag > IPP_TAG_MIMETYPE) && + tag != IPP_TAG_NOVALUE) { DEBUG_printf(("1ippReadIO: 1setOf value tag %x(%s) != %x(%s)", value_tag, ippTagString(value_tag), tag, @@ -2762,6 +2765,7 @@ _ippFreeAttr(ipp_attribute_t *attr) /* I - Attribute to free */ { case IPP_TAG_TEXT : case IPP_TAG_NAME : + case IPP_TAG_RESERVED_STRING : case IPP_TAG_KEYWORD : case IPP_TAG_URI : case IPP_TAG_URISCHEME : diff --git a/cups/ipp.h b/cups/ipp.h index 9eacc3c13..a6d4ed459 100644 --- a/cups/ipp.h +++ b/cups/ipp.h @@ -93,7 +93,8 @@ typedef enum ipp_tag_e /**** Format tags for attributes ****/ IPP_TAG_END_COLLECTION, /* End of collection value */ IPP_TAG_TEXT = 0x41, /* Text value */ IPP_TAG_NAME, /* Name value */ - IPP_TAG_KEYWORD = 0x44, /* Keyword value */ + IPP_TAG_RESERVED_STRING, /* Reserved for future string value @private@ */ + IPP_TAG_KEYWORD, /* Keyword value */ IPP_TAG_URI, /* URI value */ IPP_TAG_URISCHEME, /* URI scheme value */ IPP_TAG_CHARSET, /* Character set value */ diff --git a/cups/thread-private.h b/cups/thread-private.h index 859e6c1a8..48f8a39a6 100644 --- a/cups/thread-private.h +++ b/cups/thread-private.h @@ -35,8 +35,10 @@ extern "C" { # include typedef void *(*_cups_thread_func_t)(void *arg); typedef pthread_mutex_t _cups_mutex_t; +typedef pthread_rwlock_t _cups_rwlock_t; typedef pthread_key_t _cups_threadkey_t; # define _CUPS_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER +# define _CUPS_RWLOCK_INITIALIZER PTHREAD_RWLOCK_INITIALIZER # define _CUPS_THREADKEY_INITIALIZER -1 # define _cupsThreadGetData(k) pthread_getspecific(k) # define _cupsThreadSetData(k,p) pthread_setspecific(k,p) @@ -51,16 +53,20 @@ typedef struct _cups_mutex_s CRITICAL_SECTION m_criticalSection; /* Win32 Critical Section */ } _cups_mutex_t; +typedef _cups_mutex_t _cups_rwlock_t; /* TODO: Implement Win32 reader/writer lock */ typedef DWORD _cups_threadkey_t; # define _CUPS_MUTEX_INITIALIZER { 0, 0 } +# define _CUPS_RWLOCK_INITIALIZER { 0, 0 } # define _CUPS_THREADKEY_INITIALIZER 0 # define _cupsThreadGetData(k) TlsGetValue(k) # define _cupsThreadSetData(k,p) TlsSetValue(k,p) # else typedef char _cups_mutex_t; +typedef char _cups_rwlock_t; typedef void *_cups_threadkey_t; # define _CUPS_MUTEX_INITIALIZER 0 +# define _CUPS_RWLOCK_INITIALIZER 0 # define _CUPS_THREADKEY_INITIALIZER (void *)0 # define _cupsThreadGetData(k) k # define _cupsThreadSetData(k,p) k=p @@ -71,8 +77,13 @@ typedef void *_cups_threadkey_t; * Functions... */ +extern void _cupsMutexInit(_cups_mutex_t *mutex); extern void _cupsMutexLock(_cups_mutex_t *mutex); extern void _cupsMutexUnlock(_cups_mutex_t *mutex); +extern void _cupsRWInit(_cups_rwlock_t *rwlock); +extern void _cupsRWLockRead(_cups_rwlock_t *rwlock); +extern void _cupsRWLockWrite(_cups_rwlock_t *rwlock); +extern void _cupsRWUnlock(_cups_rwlock_t *rwlock); extern int _cupsThreadCreate(_cups_thread_func_t func, void *arg); diff --git a/cups/thread.c b/cups/thread.c index deb3a8ebc..7574298fa 100644 --- a/cups/thread.c +++ b/cups/thread.c @@ -13,8 +13,13 @@ * * Contents: * + * _cupsMutexInit() - Initialize a mutex. * _cupsMutexLock() - Lock a mutex. * _cupsMutexUnlock() - Unlock a mutex. + * _cupsRWInit() - Initialize a reader/writer lock. + * _cupsRWLockRead() - Acquire a reader/writer lock for reading. + * _cupsRWLockWrite() - Acquire a reader/writer lock for writing. + * _cupsRWUnlock() - Release a reader/writer lock. * _cupsThreadCreate() - Create a thread. */ @@ -27,6 +32,17 @@ #if defined(HAVE_PTHREAD_H) +/* + * '_cupsMutexInit()' - Initialize a mutex. + */ + +void +_cupsMutexInit(_cups_mutex_t *mutex) /* I - Mutex */ +{ + pthread_mutex_init(mutex, NULL); +} + + /* * '_cupsMutexLock()' - Lock a mutex. */ @@ -49,6 +65,50 @@ _cupsMutexUnlock(_cups_mutex_t *mutex) /* I - Mutex */ } +/* + * '_cupsRWInit()' - Initialize a reader/writer lock. + */ + +void +_cupsRWInit(_cups_rwlock_t *rwlock) /* I - Reader/writer lock */ +{ + pthread_rwlock_init(rwlock, NULL); +} + + +/* + * '_cupsRWLockRead()' - Acquire a reader/writer lock for reading. + */ + +void +_cupsRWLockRead(_cups_rwlock_t *rwlock) /* I - Reader/writer lock */ +{ + pthread_rwlock_rdlock(rwlock); +} + + +/* + * '_cupsRWLockWrite()' - Acquire a reader/writer lock for writing. + */ + +void +_cupsRWLockWrite(_cups_rwlock_t *rwlock)/* I - Reader/writer lock */ +{ + pthread_rwlock_wrlock(rwlock); +} + + +/* + * '_cupsRWUnlock()' - Release a reader/writer lock. + */ + +void +_cupsRWUnlock(_cups_rwlock_t *rwlock) /* I - Reader/writer lock */ +{ + pthread_rwlock_unlock(rwlock); +} + + /* * '_cupsThreadCreate()' - Create a thread. */ @@ -68,6 +128,18 @@ _cupsThreadCreate( # include +/* + * '_cupsMutexInit()' - Initialize a mutex. + */ + +void +_cupsMutexInit(_cups_mutex_t *mutex) /* I - Mutex */ +{ + InitializeCriticalSection(&mutex->m_criticalSection); + mutex->m_init = 1; +} + + /* * '_cupsMutexLock()' - Lock a mutex. */ @@ -103,6 +175,50 @@ _cupsMutexUnlock(_cups_mutex_t *mutex) /* I - Mutex */ } +/* + * '_cupsRWInit()' - Initialize a reader/writer lock. + */ + +void +_cupsRWInit(_cups_rwlock_t *rwlock) /* I - Reader/writer lock */ +{ + _cupsMutexInit((_cups_mutex_t *)rwlock); +} + + +/* + * '_cupsRWLockRead()' - Acquire a reader/writer lock for reading. + */ + +void +_cupsRWLockRead(_cups_rwlock_t *rwlock) /* I - Reader/writer lock */ +{ + _cupsMutexLock((_cups_mutex_t *)rwlock); +} + + +/* + * '_cupsRWLockWrite()' - Acquire a reader/writer lock for writing. + */ + +void +_cupsRWLockWrite(_cups_rwlock_t *rwlock)/* I - Reader/writer lock */ +{ + _cupsMutexLock((_cups_mutex_t *)rwlock); +} + + +/* + * '_cupsRWUnlock()' - Release a reader/writer lock. + */ + +void +_cupsRWUnlock(_cups_rwlock_t *rwlock) /* I - Reader/writer lock */ +{ + _cupsMutexUnlock((_cups_mutex_t *)rwlock); +} + + /* * '_cupsThreadCreate()' - Create a thread. */ @@ -118,6 +234,17 @@ _cupsThreadCreate( #else +/* + * '_cupsMutexInit()' - Initialize a mutex. + */ + +void +_cupsMutexInit(_cups_mutex_t *mutex) /* I - Mutex */ +{ + (void)mutex; +} + + /* * '_cupsMutexLock()' - Lock a mutex. */ @@ -125,6 +252,7 @@ _cupsThreadCreate( void _cupsMutexLock(_cups_mutex_t *mutex) /* I - Mutex */ { + (void)mutex; } @@ -135,6 +263,51 @@ _cupsMutexLock(_cups_mutex_t *mutex) /* I - Mutex */ void _cupsMutexUnlock(_cups_mutex_t *mutex) /* I - Mutex */ { + (void)mutex; +} + + +/* + * '_cupsRWInit()' - Initialize a reader/writer lock. + */ + +void +_cupsRWInit(_cups_rwlock_t *rwlock) /* I - Reader/writer lock */ +{ + (void)rwlock; +} + + +/* + * '_cupsRWLockRead()' - Acquire a reader/writer lock for reading. + */ + +void +_cupsRWLockRead(_cups_rwlock_t *rwlock) /* I - Reader/writer lock */ +{ + (void)rwlock; +} + + +/* + * '_cupsRWLockWrite()' - Acquire a reader/writer lock for writing. + */ + +void +_cupsRWLockWrite(_cups_rwlock_t *rwlock)/* I - Reader/writer lock */ +{ + (void)rwlock; +} + + +/* + * '_cupsRWUnlock()' - Release a reader/writer lock. + */ + +void +_cupsRWUnlock(_cups_rwlock_t *rwlock) /* I - Reader/writer lock */ +{ + (void)rwlock; } #endif /* HAVE_PTHREAD_H */ diff --git a/man/ipptool.man b/man/ipptool.man index 31bd93de6..7b190031c 100644 --- a/man/ipptool.man +++ b/man/ipptool.man @@ -11,12 +11,12 @@ .\" 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/". .\" -.TH ipptool 1 "CUPS" "17 October 2010" "Apple Inc." +.TH ipptool 1 "CUPS" "9 November 2010" "Apple Inc." .SH NAME ipptool - perform internet printing protocol requests .SH SYNOPSIS .B ipptool -[ -C ] [ -E ] [ -I ] [ -L ] [ -S ] [ -T +[ -4 ] [ -6 ] [ -C ] [ -E ] [ -I ] [ -L ] [ -S ] [ -T .I seconds ] [ -V .I version @@ -39,6 +39,12 @@ ipptool - perform internet printing protocol requests .SH OPTIONS The following options are recognized by \fIipptool\fR: .TP 5 +-4 +Specifies that \fIipptool\fR must connect to the printer or server using IPv4. +.TP 5 +-6 +Specifies that \fIipptool\fR must connect to the printer or server using IPv6. +.TP 5 -C Specifies that requests should be sent using the HTTP/1.1 "Transfer-Encoding: chunked" header, which is required for conformance by all versions of IPP. The default is to use "Transfer-Encoding: chunked" for requests with attached files and "Content-Length:" for requests without attached files. .TP 5 diff --git a/scheduler/dirsvc.c b/scheduler/dirsvc.c index 2f7f0e6c4..300fa6976 100644 --- a/scheduler/dirsvc.c +++ b/scheduler/dirsvc.c @@ -1915,13 +1915,13 @@ cupsdUpdateDNSSDName(void) { DNSServiceErrorType error; /* Error from service creation */ char webif[1024]; /* Web interface share name */ -#ifdef HAVE_COREFOUNDATION_H +# 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_COREFOUNDATION_H */ +# endif /* HAVE_SYSTEMCONFIGURATION */ /* @@ -1937,7 +1937,7 @@ cupsdUpdateDNSSDName(void) * Get the computer name as a c-string... */ -#ifdef HAVE_COREFOUNDATION_H +# ifdef HAVE_SYSTEMCONFIGURATION sc = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("cupsd"), NULL, NULL); if (sc) @@ -2028,7 +2028,7 @@ cupsdUpdateDNSSDName(void) CFRelease(sc); } else -#endif /* HAVE_COREFOUNDATION_H */ +# endif /* HAVE_SYSTEMCONFIGURATION */ { cupsdSetString(&DNSSDComputerName, ServerName); cupsdSetString(&DNSSDHostName, ServerName); diff --git a/test/Dependencies b/test/Dependencies index ac77ec7fc..1c2966362 100644 --- a/test/Dependencies +++ b/test/Dependencies @@ -1,5 +1,13 @@ # DO NOT DELETE THIS LINE -- make depend depends on it. +ippserver.o: ../cups/cups-private.h ../cups/cups.h ../cups/file.h +ippserver.o: ../cups/versioning.h ../cups/ipp.h ../cups/http.h +ippserver.o: ../cups/array.h ../cups/language.h ../cups/string-private.h +ippserver.o: ../config.h ../cups/debug-private.h ../cups/ppd-private.h +ippserver.o: ../cups/ppd.h ../cups/cups.h ../cups/pwg-private.h +ippserver.o: ../cups/http-private.h ../cups/http.h ../cups/md5-private.h +ippserver.o: ../cups/ipp-private.h ../cups/ipp.h ../cups/language-private.h +ippserver.o: ../cups/transcode.h ../cups/thread-private.h ipptool.o: ../cups/cups-private.h ../cups/cups.h ../cups/file.h ipptool.o: ../cups/versioning.h ../cups/ipp.h ../cups/http.h ../cups/array.h ipptool.o: ../cups/language.h ../cups/string-private.h ../config.h diff --git a/test/Makefile b/test/Makefile index f621fa66c..dfbe4245a 100644 --- a/test/Makefile +++ b/test/Makefile @@ -31,13 +31,20 @@ TESTFILES = \ testfile.pdf \ testfile.ps \ testfile.txt +OBJS = \ + ippserver.o \ + ipptool.o +TARGETS = \ + ippserver \ + ipptool \ + ipptool-static # # Make all targets... # -all: ipptool ipptool-static +all: $(TARGETS) # @@ -59,7 +66,7 @@ unittests: # clean: - $(RM) ipptool ipptool.o ipptool-static + $(RM) $(TARGETS) $(OBJS) # @@ -67,7 +74,7 @@ clean: # depend: - makedepend -Y -I.. -fDependencies ipptool.c >/dev/null 2>&1 + makedepend -Y -I.. -fDependencies $(OBJS:.o=.c) >/dev/null 2>&1 # @@ -124,6 +131,16 @@ install-libs: uninstall: +# +# ippserver +# + +ippserver: ippserver.o ../cups/$(LIBCUPSSTATIC) + echo Linking $@... + $(CC) $(LDFLAGS) -o $@ ippserver.o ../cups/$(LIBCUPSSTATIC) \ + $(LIBGSSAPI) $(SSLLIBS) $(DNSSDLIBS) $(COMMONLIBS) $(LIBZ) + + # # ipptool # diff --git a/test/ippserver.c b/test/ippserver.c new file mode 100644 index 000000000..724162d0f --- /dev/null +++ b/test/ippserver.c @@ -0,0 +1,4299 @@ +/* + * "$Id$" + * + * Sample IPP/2.0 server for CUPS. + * + * Copyright 2010 by Apple Inc. + * + * 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: + * + * main() - Main entry to the sample server. + * clean_jobs() - Clean out old (completed) jobs. + * compare_jobs() - Compare two jobs. + * copy_attribute() - Copy a single attribute. + * copy_attributes() - Copy attributes from one request to another. + * copy_job_attrs() - Copy job attributes to the response. + * create_client() - Accept a new network connection and create a + * client object. + * create_job() - Create a new job object from a Print-Job or + * Create-Job request. + * create_listener() - Create a listener socket. + * create_media_col() - Create a media-col value. + * create_printer() - Create, register, and listen for connections + * to a printer object. + * create_requested_array() - Create an array for requested-attributes. + * debug_attributes() - Print attributes in a request or response. + * delete_client() - Close the socket and free all memory used by + * a client object. + * delete_job() - Remove from the printer and free all memory + * used by a job object. + * delete_printer() - Unregister, close listen sockets, and free + * all memory used by a printer object. + * dnssd_callback() - Handle Bonjour registration events. + * find_job() - Find a job specified in a request. + * html_escape() - Write a HTML-safe string. + * html_printf() - Send formatted text to the client, quoting + * as needed. + * ipp_cancel_job() - Cancel a job. + * ipp_create_job() - Create a job object. + * ipp_get_job_attributes() - Get the attributes for a job object. + * ipp_get_jobs() - Get a list of job objects. + * ipp_get_printer_attributes() - Get the attributes for a printer object. + * ipp_print_job() - Create a job object with an attached + * document. + * ipp_send_document() - Add an attached document to a job object + * created with Create-Job. + * ipp_validate_job() - Validate job creation attributes. + * process_client() - Process client requests on a thread. + * process_http() - Process a HTTP request. + * process_ipp() - Process an IPP request. + * process_job() - Process a print job. + * register_printer() - Register a printer object via Bonjour. + * respond_http() - Send a HTTP response. + * respond_ipp() - Send an IPP response. + * run_printer() - Run the printer service. + * usage() - Show program usage. + * valid_job_attributes() - Determine whether the job attributes are + * valid. + */ + +/* + * Include necessary headers... + */ + +#include +#include +#include +#include +#ifdef HAVE_SYS_MOUNT_H +# include +#endif /* HAVE_SYS_MOUNT_H */ +#ifdef HAVE_SYS_STATFS_H +# include +#endif /* HAVE_SYS_STATFS_H */ +#ifdef HAVE_SYS_STATVFS_H +# include +#endif /* HAVE_SYS_STATVFS_H */ +#ifdef HAVE_SYS_VFS_H +# include +#endif /* HAVE_SYS_VFS_H */ + + +/* + * Constants... + */ + +enum _ipp_preasons_e /* printer-state-reasons bit values */ +{ + _IPP_PRINTER_NONE = 0x0000, /* none */ + _IPP_PRINTER_OTHER = 0x0001, /* other */ + _IPP_PRINTER_COVER_OPEN = 0x0002, /* cover-open */ + _IPP_PRINTER_INPUT_TRAY_MISSING = 0x0004, + /* input-tray-missing */ + _IPP_PRINTER_MARKER_SUPPLY_EMPTY = 0x0008, + /* marker-supply-empty */ + _IPP_PRINTER_MARKER_SUPPLY_LOW = 0x0010, + /* marker-suply-low */ + _IPP_PRINTER_MARKER_WASTE_ALMOST_FULL = 0x0020, + /* marker-waste-almost-full */ + _IPP_PRINTER_MARKER_WASTE_FULL = 0x0040, + /* marker-waste-full */ + _IPP_PRINTER_MEDIA_EMPTY = 0x0080, /* media-empty */ + _IPP_PRINTER_MEDIA_JAM = 0x0100, /* media-jam */ + _IPP_PRINTER_MEDIA_LOW = 0x0200, /* media-low */ + _IPP_PRINTER_MEDIA_NEEDED = 0x0400, /* media-needed */ + _IPP_PRINTER_MOVING_TO_PAUSED = 0x0800, + /* moving-to-paused */ + _IPP_PRINTER_PAUSED = 0x1000, /* paused */ + _IPP_PRINTER_SPOOL_AREA_FULL = 0x2000,/* spool-area-full */ + _IPP_PRINTER_TONER_EMPTY = 0x4000, /* toner-empty */ + _IPP_PRINTER_TONER_LOW = 0x8000 /* toner-low */ +}; +typedef unsigned int _ipp_preasons_t; /* Bitfield for printer-state-reasons */ + +typedef enum _ipp_media_class_e +{ + _IPP_GENERAL, /* General-purpose size */ + _IPP_PHOTO_ONLY, /* Photo-only size */ + _IPP_ENV_ONLY /* Envelope-only size */ +} _ipp_media_class_t; + +static const char * const media_supported[] = +{ /* media-supported values */ + "iso_a4_210x297mm", /* A4 */ + "iso_a5_148x210mm", /* A5 */ + "iso_a6_105x148mm", /* A6 */ + "iso_dl_110x220mm", /* DL */ + "na_legal_8.5x14in", /* Legal */ + "na_letter_8.5x11in", /* Letter */ + "na_number-10_4.125x9.5in", /* #10 */ + "na_index-3x5_3x5in", /* 3x5 */ + "oe_photo-l_3.5x5in", /* L */ + "na_index-4x6_4x6in", /* 4x6 */ + "na_5x7_5x7in" /* 5x7 */ +}; +static const int media_col_sizes[][3] = +{ /* media-col-database sizes */ + { 21000, 29700, _IPP_GENERAL }, /* A4 */ + { 14800, 21000, _IPP_PHOTO_ONLY }, /* A5 */ + { 10500, 14800, _IPP_PHOTO_ONLY }, /* A6 */ + { 11000, 22000, _IPP_ENV_ONLY }, /* DL */ + { 21590, 35560, _IPP_GENERAL }, /* Legal */ + { 21590, 27940, _IPP_GENERAL }, /* Letter */ + { 10477, 24130, _IPP_ENV_ONLY }, /* #10 */ + { 7630, 12700, _IPP_PHOTO_ONLY }, /* 3x5 */ + { 8890, 12700, _IPP_PHOTO_ONLY }, /* L */ + { 10160, 15240, _IPP_PHOTO_ONLY }, /* 4x6 */ + { 12700, 17780, _IPP_PHOTO_ONLY } /* 5x7 */ +}; +static const char * const media_type_supported[] = + /* media-type-supported values */ +{ + "auto", + "cardstock", + "envelope", + "labels", + "other", + "photographic-glossy", + "photographic-high-gloss", + "photographic-matte", + "photographic-satin", + "photographic-semi-gloss", + "stationery", + "stationery-letterhead", + "transparency" +}; + + +/* + * Structures... + */ + +typedef struct _ipp_job_s _ipp_job_t; + +typedef struct _ipp_printer_s /**** Printer data ****/ +{ + int ipv4, /* IPv4 listener */ + ipv6; /* IPv6 listener */ + DNSServiceRef common_ref, /* Shared service connection */ + ipp_ref, /* Bonjour IPP service */ + http_ref, /* Bonjour HTTP service */ + printer_ref; /* Bonjour LPD service */ + TXTRecordRef ipp_txt; /* Bonjour IPP TXT record */ + char *name, /* printer-name */ + *dnssd_name, /* printer-dnssd-name */ + *icon, /* Icon filename */ + *directory, /* Spool directory */ + *hostname, /* Hostname */ + *uri; /* printer-uri-supported */ + int port; /* Port */ + size_t urilen; /* Length of printer URI */ + ipp_t *attrs; /* Static attributes */ + ipp_pstate_t state; /* printer-state value */ + _ipp_preasons_t state_reasons; /* printer-state-reasons values */ + cups_array_t *jobs; /* Jobs */ + _ipp_job_t *active_job; /* Current active/pending job */ + int next_job_id; /* Next job-id value */ + _cups_rwlock_t rwlock; /* Printer lock */ +} _ipp_printer_t; + +struct _ipp_job_s /**** Job data ****/ +{ + int id; /* Job ID */ + char *name, /* job-name */ + *username, /* job-originating-user-name */ + *format; /* document-format */ + ipp_jstate_t state; /* job-state value */ + time_t processing, /* time-at-processing value */ + completed; /* time-at-completed value */ + ipp_t *attrs; /* Static attributes */ + int cancel; /* Non-zero when job canceled */ + char *filename; /* Print file name */ + int fd; /* Print file descriptor */ + _ipp_printer_t *printer; /* Printer */ +}; + +typedef struct _ipp_client_s /**** Client data ****/ +{ + http_t http; /* HTTP connection */ + ipp_t *request, /* IPP request */ + *response; /* IPP response */ + time_t start; /* Request start time */ + http_state_t operation; /* Request operation */ + ipp_op_t operation_id; /* IPP operation-id */ + char uri[1024]; /* Request URI */ + http_addr_t addr; /* Client address */ + _ipp_printer_t *printer; /* Printer */ + _ipp_job_t *job; /* Current job, if any */ +} _ipp_client_t; + + +/* + * Local functions... + */ + +static void clean_jobs(_ipp_printer_t *printer); +static int compare_jobs(_ipp_job_t *a, _ipp_job_t *b); +static ipp_attribute_t *copy_attribute(ipp_t *to, ipp_attribute_t *attr, + ipp_tag_t group_tag, int quickcopy); +static void copy_attributes(ipp_t *to, ipp_t *from, cups_array_t *ra, + ipp_tag_t group_tag, int quickcopy); +static void copy_job_attributes(_ipp_client_t *client, + _ipp_job_t *job, cups_array_t *ra); +static _ipp_client_t *create_client(_ipp_printer_t *printer, int sock); +static _ipp_job_t *create_job(_ipp_client_t *client); +static int create_listener(int family, int *port); +static ipp_t *create_media_col(const char *media, const char *type, + int width, int length, int margins); +static _ipp_printer_t *create_printer(const char *servername, + const char *name, const char *location, + const char *make, const char *model, + const char *icon, + const char *docformats, int ppm, + int ppm_color, int duplex, int port, + const char *regtype, + const char *directory); +static cups_array_t *create_requested_array(_ipp_client_t *client); +static void debug_attributes(const char *title, ipp_t *ipp); +static void delete_client(_ipp_client_t *client); +static void delete_job(_ipp_job_t *job); +static void delete_printer(_ipp_printer_t *printer); +static void dnssd_callback(DNSServiceRef sdRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + const char *name, + const char *regtype, + const char *domain, + _ipp_printer_t *printer); +static _ipp_job_t *find_job(_ipp_client_t *client); +static void html_escape(_ipp_client_t *client, const char *s, + size_t slen); +static void html_printf(_ipp_client_t *client, const char *format, + ...) +# ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 2, 3))) +# endif /* __GNUC__ */ +; +static void ipp_cancel_job(_ipp_client_t *client); +#if 0 +static void ipp_create_job(_ipp_client_t *client); +#endif /* 0 */ +static void ipp_get_job_attributes(_ipp_client_t *client); +static void ipp_get_jobs(_ipp_client_t *client); +static void ipp_get_printer_attributes(_ipp_client_t *client); +static void ipp_print_job(_ipp_client_t *client); +#if 0 +static void ipp_send_document(_ipp_client_t *client); +#endif /* 0 */ +static void ipp_validate_job(_ipp_client_t *client); +static void *process_client(_ipp_client_t *client); +static int process_http(_ipp_client_t *client); +static int process_ipp(_ipp_client_t *client); +static void *process_job(_ipp_job_t *job); +static int register_printer(_ipp_printer_t *printer, + const char *location, const char *make, + const char *model, const char *formats, + const char *adminurl, int color, + int duplex, const char *regtype); +static int respond_http(_ipp_client_t *client, http_status_t code, + const char *type, size_t length); +static void respond_ipp(_ipp_client_t *client, ipp_status_t status, + const char *message, ...) +#ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 3, 4))) +#endif /* __GNUC__ */ +; +static void run_printer(_ipp_printer_t *printer); +static void usage(int status); +static int valid_job_attributes(_ipp_client_t *client); + + +/* + * Globals... + */ + +static int KeepFiles = 0, + Verbosity = 0; + + +/* + * 'main()' - Main entry to the sample server. + */ + +int /* O - Exit status */ +main(int argc, /* I - Number of command-line args */ + char *argv[]) /* I - Command-line arguments */ +{ + int i; /* Looping var */ + const char *opt, /* Current option character */ + *servername = NULL, /* Server host name */ + *name = NULL, /* Printer name */ + *location = "", /* Location of printer */ + *make = "Test", /* Manufacturer */ + *model = "Printer", /* Model */ + *icon = "printer.png", /* Icon file */ + *formats = "application/pdf,image/jpeg", + /* Supported formats */ + *regtype = "_ipp._tcp"; /* Bonjour service type */ + int port = 8631, /* Port number (0 = auto) TODO: FIX */ + duplex = 0, /* Duplex mode */ + ppm = 10, /* Pages per minute for mono */ + ppm_color = 0; /* Pages per minute for color */ + char directory[1024] = ""; /* Spool directory */ + _ipp_printer_t *printer; /* Printer object */ + + + /* + * Parse command-line arguments... + */ + + for (i = 1; i < argc; i ++) + if (argv[i][0] == '-') + { + for (opt = argv[i] + 1; *opt; opt ++) + switch (*opt) + { + case '2' : /* -2 (enable 2-sided printing) */ + duplex = 1; + break; + + case 'M' : /* -M manufacturer */ + i ++; + if (i >= argc) + usage(1); + make = argv[i]; + break; + + case 'd' : /* -d spool-directory */ + i ++; + if (i >= argc) + usage(1); + strlcpy(directory, argv[i], sizeof(directory)); + break; + + case 'f' : /* -f type/subtype[,...] */ + i ++; + if (i >= argc) + usage(1); + formats = argv[i]; + break; + + case 'h' : /* -h (show help) */ + usage(0); + break; + + case 'i' : /* -i icon.png */ + i ++; + if (i >= argc) + usage(1); + icon = argv[i]; + break; + + case 'k' : /* -k (keep files) */ + KeepFiles = 1; + break; + + case 'l' : /* -l location */ + i ++; + if (i >= argc) + usage(1); + location = argv[i]; + break; + + case 'm' : /* -m model */ + i ++; + if (i >= argc) + usage(1); + model = argv[i]; + break; + + case 'n' : /* -n hostname */ + i ++; + if (i >= argc) + usage(1); + servername = argv[i]; + break; + + case 'p' : /* -p port */ + i ++; + if (i >= argc || !isdigit(argv[i][0] & 255)) + usage(1); + port = atoi(argv[i]); + break; + + case 'r' : /* -r regtype */ + i ++; + if (i >= argc) + usage(1); + regtype = argv[i]; + break; + + case 's' : /* -s speed[,color-speed] */ + i ++; + if (i >= argc) + usage(1); + if (sscanf(argv[i], "%d,%d", &ppm, &ppm_color) < 1) + usage(1); + break; + + case 'v' : /* -v (be verbose) */ + Verbosity ++; + break; + + default : /* Unknown */ + fprintf(stderr, "Unknown option \"-%c\".\n", *opt); + usage(1); + break; + } + } + else if (!name) + { + name = argv[i]; + } + else + { + fprintf(stderr, "Unexpected command-line argument \"%s\"\n", argv[i]); + usage(1); + } + + if (!name) + usage(1); + + /* + * Apply defaults as needed... + */ + + if (!directory[0]) + { + snprintf(directory, sizeof(directory), "/tmp/ippserver.%d", (int)getpid()); + + if (mkdir(directory, 0777) && errno != EEXIST) + { + fprintf(stderr, "Unable to create spool directory \"%s\": %s\n", + directory, strerror(errno)); + usage(1); + } + + if (Verbosity) + fprintf(stderr, "Using spool directory \"%s\".\n", directory); + } + + /* + * Create the printer... + */ + + if ((printer = create_printer(servername, name, location, make, model, icon, + formats, ppm, ppm_color, duplex, port, regtype, + directory)) == NULL) + return (1); + + /* + * Run the print service... + */ + + run_printer(printer); + + /* + * Destroy the printer and exit... + */ + + delete_printer(printer); + + return (0); +} + + +/* + * 'clean_jobs()' - Clean out old (completed) jobs. + */ + +static void +clean_jobs(_ipp_printer_t *printer) /* I - Printer */ +{ + _ipp_job_t *job; /* Current job */ + time_t cleantime; /* Clean time */ + + + if (cupsArrayCount(printer->jobs) == 0) + return; + + cleantime = time(NULL) - 60; + + _cupsRWLockWrite(&(printer->rwlock)); + for (job = (_ipp_job_t *)cupsArrayFirst(printer->jobs); + job; + job = (_ipp_job_t *)cupsArrayNext(printer->jobs)) + if (job->completed && job->completed < cleantime) + { + cupsArrayRemove(printer->jobs, job); + delete_job(job); + } + else + break; + _cupsRWUnlock(&(printer->rwlock)); +} + + +/* + * 'compare_jobs()' - Compare two jobs. + */ + +static int /* O - Result of comparison */ +compare_jobs(_ipp_job_t *a, /* I - First job */ + _ipp_job_t *b) /* I - Second job */ +{ + return (b->id - a->id); +} + + +/* + * 'copy_attribute()' - Copy a single attribute. + */ + +static ipp_attribute_t * /* O - New attribute */ +copy_attribute( + ipp_t *to, /* O - Destination request/response */ + ipp_attribute_t *attr, /* I - Attribute to copy */ + ipp_tag_t group_tag, /* I - Group to put the copy in */ + int quickcopy) /* I - Do a quick copy? */ +{ + int i; /* Looping var */ + ipp_attribute_t *toattr; /* Destination attribute */ + + + if (Verbosity && attr->name) + { + char buffer[2048]; /* Attribute value */ + + _ippAttrString(attr, buffer, sizeof(buffer)); + + fprintf(stderr, "Copying %s (%s%s) %s\n", attr->name, + attr->num_values > 1 ? "1setOf " : "", + ippTagString(attr->value_tag & ~IPP_TAG_COPY), buffer); + } + + switch (attr->value_tag & ~IPP_TAG_COPY) + { + case IPP_TAG_ZERO : + toattr = ippAddSeparator(to); + break; + + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + toattr = ippAddIntegers(to, group_tag, attr->value_tag, + attr->name, attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].integer = attr->values[i].integer; + break; + + case IPP_TAG_BOOLEAN : + toattr = ippAddBooleans(to, group_tag, attr->name, + attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].boolean = attr->values[i].boolean; + break; + + case IPP_TAG_TEXT : + case IPP_TAG_NAME : + case IPP_TAG_KEYWORD : + case IPP_TAG_URI : + case IPP_TAG_URISCHEME : + case IPP_TAG_CHARSET : + case IPP_TAG_LANGUAGE : + case IPP_TAG_MIMETYPE : + toattr = ippAddStrings(to, group_tag, + (ipp_tag_t)(attr->value_tag | quickcopy), + attr->name, attr->num_values, NULL, NULL); + + if (quickcopy) + { + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].string.text = attr->values[i].string.text; + } + else + { + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].string.text = + _cupsStrAlloc(attr->values[i].string.text); + } + break; + + case IPP_TAG_DATE : + toattr = ippAddDate(to, group_tag, attr->name, + attr->values[0].date); + break; + + case IPP_TAG_RESOLUTION : + toattr = ippAddResolutions(to, group_tag, attr->name, + attr->num_values, IPP_RES_PER_INCH, + NULL, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].resolution.xres = attr->values[i].resolution.xres; + toattr->values[i].resolution.yres = attr->values[i].resolution.yres; + toattr->values[i].resolution.units = attr->values[i].resolution.units; + } + break; + + case IPP_TAG_RANGE : + toattr = ippAddRanges(to, group_tag, attr->name, + attr->num_values, NULL, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].range.lower = attr->values[i].range.lower; + toattr->values[i].range.upper = attr->values[i].range.upper; + } + break; + + case IPP_TAG_TEXTLANG : + case IPP_TAG_NAMELANG : + toattr = ippAddStrings(to, group_tag, + (ipp_tag_t)(attr->value_tag | quickcopy), + attr->name, attr->num_values, NULL, NULL); + + if (quickcopy) + { + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].string.charset = attr->values[i].string.charset; + toattr->values[i].string.text = attr->values[i].string.text; + } + } + else + { + for (i = 0; i < attr->num_values; i ++) + { + if (!i) + toattr->values[i].string.charset = + _cupsStrAlloc(attr->values[i].string.charset); + else + toattr->values[i].string.charset = + toattr->values[0].string.charset; + + toattr->values[i].string.text = + _cupsStrAlloc(attr->values[i].string.text); + } + } + break; + + case IPP_TAG_BEGIN_COLLECTION : + toattr = ippAddCollections(to, group_tag, attr->name, + attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].collection = attr->values[i].collection; + attr->values[i].collection->use ++; + } + break; + + case IPP_TAG_STRING : + if (quickcopy) + { + toattr = ippAddOctetString(to, group_tag, attr->name, NULL, 0); + toattr->value_tag |= quickcopy; + toattr->values[0].unknown.data = attr->values[0].unknown.data; + toattr->values[0].unknown.length = attr->values[0].unknown.length; + } + else + toattr = ippAddOctetString(to, attr->group_tag, attr->name, + attr->values[0].unknown.data, + attr->values[0].unknown.length); + break; + + default : + toattr = ippAddIntegers(to, group_tag, attr->value_tag, + attr->name, attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].unknown.length = attr->values[i].unknown.length; + + if (toattr->values[i].unknown.length > 0) + { + if ((toattr->values[i].unknown.data = + malloc(toattr->values[i].unknown.length)) == NULL) + toattr->values[i].unknown.length = 0; + else + memcpy(toattr->values[i].unknown.data, + attr->values[i].unknown.data, + toattr->values[i].unknown.length); + } + } + break; /* anti-compiler-warning-code */ + } + + return (toattr); +} + + +/* + * 'copy_attributes()' - Copy attributes from one request to another. + */ + +static void +copy_attributes(ipp_t *to, /* I - Destination request */ + ipp_t *from, /* I - Source request */ + cups_array_t *ra, /* I - Requested attributes */ + ipp_tag_t group_tag, /* I - Group to copy */ + int quickcopy) /* I - Do a quick copy? */ +{ + ipp_attribute_t *fromattr; /* Source attribute */ + + + if (!to || !from) + return; + + for (fromattr = from->attrs; fromattr; fromattr = fromattr->next) + { + /* + * Filter attributes as needed... + */ + + if ((group_tag != IPP_TAG_ZERO && fromattr->group_tag != group_tag && + fromattr->group_tag != IPP_TAG_ZERO) || !fromattr->name) + continue; + + if (!ra || cupsArrayFind(ra, fromattr->name)) + copy_attribute(to, fromattr, fromattr->group_tag, quickcopy); + } +} + + +/* + * 'copy_job_attrs()' - Copy job attributes to the response. + */ + +static void +copy_job_attributes( + _ipp_client_t *client, /* I - Client */ + _ipp_job_t *job, /* I - Job */ + cups_array_t *ra) /* I - requested-attributes */ +{ + copy_attributes(client->response, job->attrs, ra, 0, IPP_TAG_ZERO); + + if (!ra || cupsArrayFind(ra, "job-printer-up-time")) + ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, + "job-printer-up-time", (int)time(NULL)); + + if (!ra || cupsArrayFind(ra, "job-state")) + ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_ENUM, + "job-state", job->state); + + if (!ra || cupsArrayFind(ra, "job-state-reasons")) + { + switch (job->state) + { + case IPP_JOB_PENDING : + ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "none"); + break; + + case IPP_JOB_HELD : + if (job->fd >= 0) + ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "job-incoming"); + else if (ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_ZERO)) + ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "job-hold-until-specified"); + break; + + case IPP_JOB_PROCESSING : + if (job->cancel) + ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "processing-to-stop-point"); + else + ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "job-printing"); + break; + + case IPP_JOB_STOPPED : + ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "job-stopped"); + break; + + case IPP_JOB_CANCELED : + ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "job-canceled-by-user"); + break; + + case IPP_JOB_ABORTED : + ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "aborted-by-system"); + break; + + case IPP_JOB_COMPLETED : + ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "job-completed-successfully"); + break; + } + } + + if (!ra || cupsArrayFind(ra, "time-at-completed")) + ippAddInteger(client->response, IPP_TAG_JOB, + job->completed ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE, + "time-at-completed", job->completed); + + if (!ra || cupsArrayFind(ra, "time-at-processing")) + ippAddInteger(client->response, IPP_TAG_JOB, + job->processing ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE, + "time-at-processing", job->processing); +} + + +/* + * 'create_client()' - Accept a new network connection and create a client + * object. + */ + +static _ipp_client_t * /* O - Client */ +create_client(_ipp_printer_t *printer, /* I - Printer */ + int sock) /* I - Listen socket */ +{ + _ipp_client_t *client; /* Client */ + int val; /* Parameter value */ + socklen_t addrlen; /* Length of address */ + + + if ((client = calloc(1, sizeof(_ipp_client_t))) == NULL) + { + perror("Unable to allocate memory for client"); + return (NULL); + } + + client->printer = printer; + client->http.activity = time(NULL); + client->http.hostaddr = &(client->addr); + client->http.blocking = 1; + + /* + * Accept the client and get the remote address... + */ + + addrlen = sizeof(http_addr_t); + + if ((client->http.fd = accept(sock, (struct sockaddr *)&(client->addr), + &addrlen)) < 0) + { + perror("Unable to accept client connection"); + + free(client); + + return (NULL); + } + + httpAddrString(&(client->addr), client->http.hostname, + sizeof(client->http.hostname)); + + if (Verbosity) + fprintf(stderr, "Accepted connection from %s (%s)\n", client->http.hostname, + client->http.hostaddr->addr.sa_family == AF_INET ? "IPv4" : "IPv6"); + + /* + * Using TCP_NODELAY improves responsiveness, especially on systems + * with a slow loopback interface. Since we write large buffers + * when sending print files and requests, there shouldn't be any + * performance penalty for this... + */ + + val = 1; + setsockopt(client->http.fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, + sizeof(val)); + + return (client); +} + + +/* + * 'create_job()' - Create a new job object from a Print-Job or Create-Job + * request. + */ + +static _ipp_job_t * /* O - Job */ +create_job(_ipp_client_t *client) /* I - Client */ +{ + _ipp_job_t *job; /* Job */ + ipp_attribute_t *attr; /* Job attribute */ + char uri[1024]; /* job-uri value */ + + + _cupsRWLockWrite(&(client->printer->rwlock)); + if (client->printer->active_job && + client->printer->active_job->state < IPP_JOB_CANCELED) + { + /* + * Only accept a single job at a time... + */ + + _cupsRWLockWrite(&(client->printer->rwlock)); + return (NULL); + } + + /* + * Allocate and initialize the job object... + */ + + if ((job = calloc(1, sizeof(_ipp_job_t))) == NULL) + { + perror("Unable to allocate memory for job"); + return (NULL); + } + + job->printer = client->printer; + job->attrs = client->request; + job->state = IPP_JOB_HELD; + job->fd = -1; + client->request = NULL; + + /* + * Set all but the first two attributes to the job attributes group... + */ + + for (attr = job->attrs->attrs->next->next; attr; attr = attr->next) + attr->group_tag = IPP_TAG_JOB; + + /* + * Get the requesting-user-name, document format, and priority... + */ + + if ((attr = ippFindAttribute(job->attrs, "requesting-user-name", + IPP_TAG_NAME)) != NULL) + { + _cupsStrFree(attr->name); + attr->name = _cupsStrAlloc("job-originating-user-name"); + } + else + attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, + "job-originating-user-name", NULL, "anonymous"); + + if (attr) + job->username = attr->values[0].string.text; + else + job->username = "anonymous"; + + if ((attr = ippFindAttribute(job->attrs, "document-format", + IPP_TAG_MIMETYPE)) != NULL) + job->format = attr->values[0].string.text; + else + job->format = "application/octet-stream"; + + /* + * Add job description attributes and add to the jobs array... + */ + + job->id = client->printer->next_job_id ++; + + snprintf(uri, sizeof(uri), "%s/%d", client->printer->uri, job->id); + + ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id); + ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, uri); + ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, + client->printer->uri); + ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation", + (int)time(NULL)); + + cupsArrayAdd(client->printer->jobs, job); + client->printer->active_job = job; + + _cupsRWUnlock(&(client->printer->rwlock)); + + return (job); +} + + +/* + * 'create_listener()' - Create a listener socket. + */ + +static int /* O - Listener socket or -1 on error */ +create_listener(int family, /* I - Address family */ + int *port) /* IO - Port number */ +{ + int sock, /* Listener socket */ + val; /* Socket value */ + http_addr_t address; /* Listen address */ + socklen_t addrlen; /* Length of listen address */ + + + if ((sock = socket(family, SOCK_STREAM, 0)) < 0) + return (-1); + + val = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + +#ifdef IPV6_V6ONLY + if (family == AF_INET6) + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); +#endif /* IPV6_V6ONLY */ + + if (!*port) + { + /* + * Get the auto-assigned port number for the IPv4 socket... + */ + + /* TODO: This code does not appear to work - port is always 0... */ + addrlen = sizeof(address); + if (getsockname(sock, (struct sockaddr *)&address, &addrlen)) + { + perror("getsockname() failed"); + *port = 8631; + } + else + *port = _httpAddrPort(&address); + + fprintf(stderr, "Listening on port %d.\n", *port); + } + + memset(&address, 0, sizeof(address)); + if (family == AF_INET) + { + address.ipv4.sin_family = family; + address.ipv4.sin_port = htons(*port); + } + else + { + address.ipv6.sin6_family = family; + address.ipv6.sin6_port = htons(*port); + } + + if (bind(sock, (struct sockaddr *)&address, httpAddrLength(&address))) + { + close(sock); + return (-1); + } + + if (listen(sock, 5)) + { + close(sock); + return (-1); + } + + return (sock); +} + + +/* + * 'create_media_col()' - Create a media-col value. + */ + +static ipp_t * /* O - media-col collection */ +create_media_col(const char *media, /* I - Media name */ + const char *type, /* I - Nedua type */ + int width, /* I - x-dimension in 2540ths */ + int length, /* I - y-dimension in 2540ths */ + int margins) /* I - Value for margins */ +{ + ipp_t *media_col = ippNew(), /* media-col value */ + *media_size = ippNew(); /* media-size value */ + char media_key[256]; /* media-key value */ + + + ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "x-dimension", + width); + ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "y-dimension", + length); + + snprintf(media_key, sizeof(media_key), "%s_%s%s", media, type, + margins == 0 ? "_borderless" : ""); + + ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-key", NULL, + media_key); + ippAddCollection(media_col, IPP_TAG_PRINTER, "media-size", media_size); + ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-bottom-margin", margins); + ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-left-margin", margins); + ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-right-margin", margins); + ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-top-margin", margins); + ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type", + NULL, type); + + ippDelete(media_size); + + return (media_col); +} + + +/* + * 'create_printer()' - Create, register, and listen for connections to a + * printer object. + */ + +static _ipp_printer_t * /* O - Printer */ +create_printer(const char *servername, /* I - Server hostname (NULL for default) */ + const char *name, /* I - printer-name */ + const char *location, /* I - printer-location */ + const char *make, /* I - printer-make-and-model */ + const char *model, /* I - printer-make-and-model */ + const char *icon, /* I - printer-icons */ + const char *docformats, /* I - document-format-supported */ + int ppm, /* I - Pages per minute in grayscale */ + int ppm_color, /* I - Pages per minute in color (0 for gray) */ + int duplex, /* I - 1 = duplex, 0 = simplex */ + int port, /* I - Port for listeners or 0 for auto */ + const char *regtype, /* I - Bonjour service type */ + const char *directory) /* I - Spool directory */ +{ + int i, j; /* Looping vars */ + _ipp_printer_t *printer; /* Printer */ + char hostname[256], /* Hostname */ + uri[1024], /* Printer URI */ + icons[1024], /* printer-icons URI */ + adminurl[1024], /* printer-more-info URI */ + device_id[1024],/* printer-device-id */ + make_model[128];/* printer-make-and-model */ + int num_formats; /* Number of document-format-supported values */ + char *defformat, /* document-format-default value */ + *formats[100], /* document-format-supported values */ + *ptr; /* Pointer into string */ + const char *prefix; /* Prefix string */ + int num_database; /* Number of database values */ + ipp_attribute_t *media_col_database; + /* media-col-database value */ + ipp_t *media_col_default; + /* media-col-default value */ + ipp_value_t *media_col_value; + /* Current media-col-database value */ + int k_supported; /* Maximum file size supported */ +#ifdef HAVE_STATFS + struct statfs spoolinfo; /* FS info for spool directory */ + double spoolsize; /* FS size */ +#elif defined(HAVE_STATVFS) + struct statvfs spoolinfo; /* FS info for spool directory */ + double spoolsize; /* FS size */ +#endif /* HAVE_STATFS */ + static const int orients[4] = /* orientation-requested-supported values */ + { + IPP_PORTRAIT, + IPP_LANDSCAPE, + IPP_REVERSE_LANDSCAPE, + IPP_REVERSE_PORTRAIT + }; + static const char * const versions[] =/* ipp-versions-supported values */ + { + "1.0", + "1.1", + "2.0" + }; + static const int ops[] = /* operations-supported values */ + { + IPP_PRINT_JOB, + IPP_VALIDATE_JOB, + IPP_CREATE_JOB, + IPP_SEND_DOCUMENT, + IPP_CANCEL_JOB, + IPP_GET_JOB_ATTRIBUTES, + IPP_GET_JOBS, + IPP_GET_PRINTER_ATTRIBUTES + }; + static const char * const charsets[] =/* charset-supported values */ + { + "us-ascii", + "utf-8" + }; + static const char * const job_creation[] = + { /* job-creation-attributes-supported values */ + "copies", + "ipp-attribute-fidelity", + "job-name", + "job-priority", + "media", + "media-col", + "multiple-document-handling", + "orientation-requested", + "print-quality", + "sides" + }; + static const char * const media_col_supported[] = + { /* media-col-supported values */ + "media-bottom-margin", + "media-left-margin", + "media-right-margin", + "media-size", + "media-top-margin", + "media-type" + }; + static const int media_xxx_margin_supported[] = + { /* media-xxx-margin-supported values */ + 0, + 635 + }; + static const char * const multiple_document_handling[] = + { /* multiple-document-handling-supported values */ + "separate-documents-uncollated-copies", + "separate-documents-collated-copies" + }; + static const int print_quality_supported[] = + { /* print-quality-supported values */ + IPP_QUALITY_DRAFT, + IPP_QUALITY_NORMAL, + IPP_QUALITY_HIGH + }; + static const char * const sides_supported[] = + { /* sides-supported values */ + "one-sided", + "two-sided-long-edge", + "two-sided-short-edge" + }; + static const char * const which_jobs[] = + { /* which-jobs-supported values */ + "completed", + "not-completed", + "aborted", + "all", + "canceled", + "pending", + "pending-held", + "processing", + "processing-stopped" + }; + + + /* + * Allocate memory for the printer... + */ + + if ((printer = calloc(1, sizeof(_ipp_printer_t))) == NULL) + { + perror("Unable to allocate memory for printer"); + return (NULL); + } + + printer->ipv4 = -1; + printer->ipv6 = -1; + printer->name = _cupsStrAlloc(name); + printer->dnssd_name = _cupsStrRetain(printer->name); + printer->directory = _cupsStrAlloc(directory); + printer->hostname = _cupsStrAlloc(servername ? servername : + httpGetHostname(NULL, hostname, + sizeof(hostname))); + printer->port = port; + printer->state = IPP_PRINTER_IDLE; + printer->state_reasons = _IPP_PRINTER_NONE; + printer->jobs = cupsArrayNew((cups_array_func_t)compare_jobs, NULL); + printer->next_job_id = 1; + + httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, + printer->hostname, printer->port, "/ipp"); + printer->uri = _cupsStrAlloc(uri); + printer->urilen = strlen(uri); + + _cupsRWInit(&(printer->rwlock)); + + /* + * Create the listener sockets... + */ + + if ((printer->ipv4 = create_listener(AF_INET, &(printer->port))) < 0) + { + perror("Unable to create IPv4 listener"); + goto bad_printer; + } + + if ((printer->ipv6 = create_listener(AF_INET6, &(printer->port))) < 0) + { + perror("Unable to create IPv6 listener"); + goto bad_printer; + } + + /* + * Prepare values for the printer attributes... + */ + + httpAssembleURI(HTTP_URI_CODING_ALL, icons, sizeof(icons), "http", NULL, + printer->hostname, printer->port, "/icon.png"); + httpAssembleURI(HTTP_URI_CODING_ALL, adminurl, sizeof(adminurl), "http", NULL, + printer->hostname, printer->port, "/"); + + if (Verbosity) + { + fprintf(stderr, "printer-more-info=\"%s\"\n", adminurl); + fprintf(stderr, "printer-uri=\"%s\"\n", uri); + } + + snprintf(make_model, sizeof(make_model), "%s %s", make, model); + + num_formats = 1; + formats[0] = strdup(docformats); + defformat = formats[0]; + for (ptr = strchr(formats[0], ','); ptr; ptr = strchr(ptr, ',')) + { + *ptr++ = '\0'; + formats[num_formats++] = ptr; + + if (!strcasecmp(ptr, "application/octet-stream")) + defformat = ptr; + } + + snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;", make, model); + ptr = device_id + strlen(device_id); + prefix = "CMD:"; + for (i = 0; i < num_formats; i ++) + { + if (!strcasecmp(formats[i], "application/pdf")) + snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPDF", prefix); + else if (!strcasecmp(formats[i], "application/postscript")) + snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPS", prefix); + else if (!strcasecmp(formats[i], "application/vnd.hp-PCL")) + snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPCL", prefix); + else if (!strcasecmp(formats[i], "image/jpeg")) + snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sJPEG", prefix); + else if (!strcasecmp(formats[i], "image/png")) + snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPNG", prefix); + else if (strcasecmp(formats[i], "application/octet-stream")) + snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%s%s", prefix, + formats[i]); + + ptr += strlen(ptr); + prefix = ","; + } + strlcat(device_id, ";", sizeof(device_id)); + + /* + * Get the maximum spool size based on the size of the filesystem used for + * the spool directory. If the host OS doesn't support the statfs call + * or the filesystem is larger than 2TiB, always report INT_MAX. + */ + +#ifdef HAVE_STATFS + if (statfs(printer->directory, &spoolinfo)) + k_supported = INT_MAX; + else if ((spoolsize = (double)spoolinfo.f_bsize * + spoolinfo.f_blocks / 1024) > INT_MAX) + k_supported = INT_MAX; + else + k_supported = (int)spoolsize; + +#elif defined(HAVE_STATVFS) + if (statvfs(printer->directory, &spoolinfo)) + k_supported = INT_MAX; + else if ((spoolsize = (double)spoolinfo.f_frsize * + spoolinfo.f_blocks / 1024) > INT_MAX) + k_supported = INT_MAX; + else + k_supported = (int)spoolsize; + +#else + k_supported = INT_MAX; +#endif /* HAVE_STATFS */ + + /* + * Create the printer attributes. This list of attributes is sorted to improve + * performance when the client provides a requested-attributes attribute... + */ + + printer->attrs = ippNew(); + + /* charset-configured */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY, + "charset-configured", NULL, "utf-8"); + + /* charset-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY, + "charset-supported", sizeof(charsets) / sizeof(charsets[0]), + NULL, charsets); + + /* color-supported */ + ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "color-supported", + ppm_color > 0); + + /* compression-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "compression-supported", NULL, "none"); + + /* copies-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "copies-default", 1); + + /* copies-supported */ + ippAddRange(printer->attrs, IPP_TAG_PRINTER, "copies-supported", 1, 999); + + /* document-format-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE | IPP_TAG_COPY, + "document-format-default", NULL, "application/octet-stream"); + + /* document-format-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE, + "document-format-supported", num_formats, NULL, + (const char * const *)formats); + + /* finishings-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "finishings-default", IPP_FINISHINGS_NONE); + + /* finishings-supported */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "finishings-supported", IPP_FINISHINGS_NONE); + + /* generated-natural-language-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE | IPP_TAG_COPY, + "generated-natural-language-supported", NULL, "en"); + + /* ipp-versions-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "ipp-versions-supported", + sizeof(versions) / sizeof(versions[0]), NULL, versions); + + /* job-creation-attributes-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "job-creation-attributes-supported", + sizeof(job_creation) / sizeof(job_creation[0]), + NULL, job_creation); + + /* job-k-octets-supported */ + ippAddRange(printer->attrs, IPP_TAG_PRINTER, "job-k-octets-supported", 0, + k_supported); + + /* job-priority-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "job-priority-default", 50); + + /* job-priority-supported */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "job-priority-supported", 100); + + /* job-sheets-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY, + "job-sheets-default", NULL, "none"); + + /* job-sheets-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY, + "job-sheets-supported", NULL, "none"); + + /* media-bottom-margin-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-bottom-margin-supported", + (int)(sizeof(media_xxx_margin_supported) / + sizeof(media_xxx_margin_supported[0])), + media_xxx_margin_supported); + + /* media-col-database */ + for (num_database = 0, i = 0; + i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0])); + i ++) + { + if (media_col_sizes[i][2] == _IPP_ENV_ONLY) + num_database += 2; /* auto + envelope */ + else if (media_col_sizes[i][2] == _IPP_PHOTO_ONLY) + num_database += 12; /* auto + photographic-* + borderless */ + else + num_database += (int)(sizeof(media_type_supported) / + sizeof(media_type_supported[0])) + 6; + /* All types + borderless */ + } + + media_col_database = ippAddCollections(printer->attrs, IPP_TAG_PRINTER, + "media-col-database", num_database, + NULL); + for (media_col_value = media_col_database->values, i = 0; + i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0])); + i ++) + { + for (j = 0; + j < (int)(sizeof(media_type_supported) / + sizeof(media_type_supported[0])); + j ++) + { + if (media_col_sizes[i][2] == _IPP_ENV_ONLY && + strcmp(media_type_supported[j], "auto") && + strcmp(media_type_supported[j], "envelope")) + continue; + else if (media_col_sizes[i][2] == _IPP_PHOTO_ONLY && + strcmp(media_type_supported[j], "auto") && + strncmp(media_type_supported[j], "photographic-", 13)) + continue; + + media_col_value->collection = + create_media_col(media_supported[i], media_type_supported[j], + media_col_sizes[i][0], media_col_sizes[i][1], + media_xxx_margin_supported[1]); + media_col_value ++; + + if (media_col_sizes[i][2] != _IPP_ENV_ONLY && + (!strcmp(media_type_supported[j], "auto") || + !strncmp(media_type_supported[j], "photographic-", 13))) + { + /* + * Add borderless version for this combination... + */ + + media_col_value->collection = + create_media_col(media_supported[i], media_type_supported[j], + media_col_sizes[i][0], media_col_sizes[i][1], + media_xxx_margin_supported[0]); + media_col_value ++; + } + } + } + + /* media-col-default */ + media_col_default = create_media_col(media_supported[0], + media_type_supported[0], + media_col_sizes[0][0], + media_col_sizes[0][1], + media_xxx_margin_supported[1]); + + ippAddCollection(printer->attrs, IPP_TAG_PRINTER, "media-col-default", + media_col_default); + ippDelete(media_col_default); + + /* media-col-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "media-col-supported", + (int)(sizeof(media_col_supported) / + sizeof(media_col_supported[0])), NULL, + media_col_supported); + + /* media-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "media-default", NULL, media_supported[0]); + + /* media-left-margin-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-left-margin-supported", + (int)(sizeof(media_xxx_margin_supported) / + sizeof(media_xxx_margin_supported[0])), + media_xxx_margin_supported); + + /* media-right-margin-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-right-margin-supported", + (int)(sizeof(media_xxx_margin_supported) / + sizeof(media_xxx_margin_supported[0])), + media_xxx_margin_supported); + + /* media-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "media-supported", + (int)(sizeof(media_supported) / sizeof(media_supported[0])), + NULL, media_supported); + + /* media-top-margin-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-top-margin-supported", + (int)(sizeof(media_xxx_margin_supported) / + sizeof(media_xxx_margin_supported[0])), + media_xxx_margin_supported); + + /* multiple-document-handling-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "multiple-document-handling-supported", + sizeof(multiple_document_handling) / + sizeof(multiple_document_handling[0]), NULL, + multiple_document_handling); + + /* multiple-document-jobs-supported */ + ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, + "multiple-document-jobs-supported", 0); + + /* natural-language-configured */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE | IPP_TAG_COPY, + "natural-language-configured", NULL, "en"); + + /* number-up-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "number-up-default", 1); + + /* number-up-supported */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "number-up-supported", 1); + + /* operations-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "operations-supported", sizeof(ops) / sizeof(ops[0]), ops); + + /* orientation-requested-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, + "orientation-requested-default", 0); + + /* orientation-requested-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "orientation-requested-supported", 4, orients); + + /* output-bin-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, + "output-bin-default", NULL, "face-down"); + + /* output-bin-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, + "output-bin-supported", NULL, "face-down"); + + /* pages-per-minute */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "pages-per-minute", ppm); + + /* pages-per-minute-color */ + if (ppm_color > 0) + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "pages-per-minute-color", ppm_color); + + /* pdl-override-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "pdl-override-supported", NULL, "attempted"); + + /* print-quality-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "print-quality-default", IPP_QUALITY_NORMAL); + + /* print-quality-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "print-quality-supported", + (int)(sizeof(print_quality_supported) / + sizeof(print_quality_supported[0])), + print_quality_supported); + + /* printer-device-id */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, + "printer-device-id", NULL, device_id); + + /* printer-icons */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, + "printer-icons", NULL, icons); + + /* printer-is-accepting-jobs */ + ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "printer-is-accepting-jobs", + 1); + + /* printer-info */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info", + NULL, name); + + /* printer-location */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, + "printer-location", NULL, location); + + /* printer-make-and-model */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, + "printer-make-and-model", NULL, make_model); + + /* printer-more-info */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, + "printer-more-info", NULL, adminurl); + + /* printer-name */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name", + NULL, name); + + /* printer-resolution-default */ + ippAddResolution(printer->attrs, IPP_TAG_PRINTER, + "printer-resolution-default", IPP_RES_PER_INCH, 600, 600); + + /* printer-resolution-supported */ + ippAddResolution(printer->attrs, IPP_TAG_PRINTER, + "printer-resolution-supported", IPP_RES_PER_INCH, 600, 600); + + /* printer-uri-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, + "printer-uri-supported", NULL, uri); + + /* sides-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "sides-default", NULL, "one-sided"); + + /* sides-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, + "sides-supported", duplex ? 3 : 1, NULL, sides_supported); + + /* uri-authentication-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, + "uri-authentication-supported", NULL, "none"); + + /* uri-security-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, + "uri-security-supported", NULL, "none"); + + /* which-jobs-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "which-jobs-supported", + sizeof(which_jobs) / sizeof(which_jobs[0]), NULL, which_jobs); + + free(formats[0]); + + debug_attributes("Printer", printer->attrs); + + /* + * Register the printer with Bonjour... + */ + + if (!register_printer(printer, location, make, model, docformats, adminurl, + ppm_color > 0, duplex, regtype)) + goto bad_printer; + + /* + * Return it! + */ + + return (printer); + + + /* + * If we get here we were unable to create the printer... + */ + + bad_printer: + + delete_printer(printer); + return (NULL); +} + + +/* + * 'create_requested_array()' - Create an array for requested-attributes. + */ + +static cups_array_t * /* O - requested-attributes array */ +create_requested_array( + _ipp_client_t *client) /* I - Client */ +{ + int i; /* Looping var */ + ipp_attribute_t *requested; /* requested-attributes attribute */ + cups_array_t *ra; /* Requested attributes array */ + char *value; /* Current value */ + + + /* + * Get the requested-attributes attribute, and return NULL if we don't + * have one... + */ + + if ((requested = ippFindAttribute(client->request, "requested-attributes", + IPP_TAG_KEYWORD)) == NULL) + return (NULL); + + /* + * If the attribute contains a single "all" keyword, return NULL... + */ + + if (requested->num_values == 1 && + !strcmp(requested->values[0].string.text, "all")) + return (NULL); + + /* + * Create an array using "strcmp" as the comparison function... + */ + + ra = cupsArrayNew((cups_array_func_t)strcmp, NULL); + + for (i = 0; i < requested->num_values; i ++) + { + value = requested->values[i].string.text; + + if (!strcmp(value, "job-template")) + { + cupsArrayAdd(ra, "copies"); + cupsArrayAdd(ra, "copies-default"); + cupsArrayAdd(ra, "copies-supported"); + cupsArrayAdd(ra, "finishings"); + cupsArrayAdd(ra, "finishings-default"); + cupsArrayAdd(ra, "finishings-supported"); + cupsArrayAdd(ra, "job-hold-until"); + cupsArrayAdd(ra, "job-hold-until-default"); + cupsArrayAdd(ra, "job-hold-until-supported"); + cupsArrayAdd(ra, "job-priority"); + cupsArrayAdd(ra, "job-priority-default"); + cupsArrayAdd(ra, "job-priority-supported"); + cupsArrayAdd(ra, "job-sheets"); + cupsArrayAdd(ra, "job-sheets-default"); + cupsArrayAdd(ra, "job-sheets-supported"); + cupsArrayAdd(ra, "media"); + cupsArrayAdd(ra, "media-col"); + cupsArrayAdd(ra, "media-col-default"); + cupsArrayAdd(ra, "media-col-supported"); + cupsArrayAdd(ra, "media-default"); + cupsArrayAdd(ra, "media-source-supported"); + cupsArrayAdd(ra, "media-supported"); + cupsArrayAdd(ra, "media-type-supported"); + cupsArrayAdd(ra, "multiple-document-handling"); + cupsArrayAdd(ra, "multiple-document-handling-default"); + cupsArrayAdd(ra, "multiple-document-handling-supported"); + cupsArrayAdd(ra, "number-up"); + cupsArrayAdd(ra, "number-up-default"); + cupsArrayAdd(ra, "number-up-supported"); + cupsArrayAdd(ra, "orientation-requested"); + cupsArrayAdd(ra, "orientation-requested-default"); + cupsArrayAdd(ra, "orientation-requested-supported"); + cupsArrayAdd(ra, "page-ranges"); + cupsArrayAdd(ra, "page-ranges-supported"); + cupsArrayAdd(ra, "printer-resolution"); + cupsArrayAdd(ra, "printer-resolution-default"); + cupsArrayAdd(ra, "printer-resolution-supported"); + cupsArrayAdd(ra, "print-quality"); + cupsArrayAdd(ra, "print-quality-default"); + cupsArrayAdd(ra, "print-quality-supported"); + cupsArrayAdd(ra, "sides"); + cupsArrayAdd(ra, "sides-default"); + cupsArrayAdd(ra, "sides-supported"); + } + else if (!strcmp(value, "job-description")) + { + cupsArrayAdd(ra, "date-time-at-completed"); + cupsArrayAdd(ra, "date-time-at-creation"); + cupsArrayAdd(ra, "date-time-at-processing"); + cupsArrayAdd(ra, "job-detailed-status-message"); + cupsArrayAdd(ra, "job-document-access-errors"); + cupsArrayAdd(ra, "job-id"); + cupsArrayAdd(ra, "job-impressions"); + cupsArrayAdd(ra, "job-impressions-completed"); + cupsArrayAdd(ra, "job-k-octets"); + cupsArrayAdd(ra, "job-k-octets-processed"); + cupsArrayAdd(ra, "job-media-sheets"); + cupsArrayAdd(ra, "job-media-sheets-completed"); + cupsArrayAdd(ra, "job-message-from-operator"); + cupsArrayAdd(ra, "job-more-info"); + cupsArrayAdd(ra, "job-name"); + cupsArrayAdd(ra, "job-originating-user-name"); + cupsArrayAdd(ra, "job-printer-up-time"); + cupsArrayAdd(ra, "job-printer-uri"); + cupsArrayAdd(ra, "job-state"); + cupsArrayAdd(ra, "job-state-message"); + cupsArrayAdd(ra, "job-state-reasons"); + cupsArrayAdd(ra, "job-uri"); + cupsArrayAdd(ra, "number-of-documents"); + cupsArrayAdd(ra, "number-of-intervening-jobs"); + cupsArrayAdd(ra, "output-device-assigned"); + cupsArrayAdd(ra, "time-at-completed"); + cupsArrayAdd(ra, "time-at-creation"); + cupsArrayAdd(ra, "time-at-processing"); + } + else if (!strcmp(value, "printer-description")) + { + cupsArrayAdd(ra, "charset-configured"); + cupsArrayAdd(ra, "charset-supported"); + cupsArrayAdd(ra, "color-supported"); + cupsArrayAdd(ra, "compression-supported"); + cupsArrayAdd(ra, "document-format-default"); + cupsArrayAdd(ra, "document-format-supported"); + cupsArrayAdd(ra, "generated-natural-language-supported"); + cupsArrayAdd(ra, "ipp-versions-supported"); + cupsArrayAdd(ra, "job-impressions-supported"); + cupsArrayAdd(ra, "job-k-octets-supported"); + cupsArrayAdd(ra, "job-media-sheets-supported"); + cupsArrayAdd(ra, "multiple-document-jobs-supported"); + cupsArrayAdd(ra, "multiple-operation-time-out"); + cupsArrayAdd(ra, "natural-language-configured"); + cupsArrayAdd(ra, "notify-attributes-supported"); + cupsArrayAdd(ra, "notify-lease-duration-default"); + cupsArrayAdd(ra, "notify-lease-duration-supported"); + cupsArrayAdd(ra, "notify-max-events-supported"); + cupsArrayAdd(ra, "notify-events-default"); + cupsArrayAdd(ra, "notify-events-supported"); + cupsArrayAdd(ra, "notify-pull-method-supported"); + cupsArrayAdd(ra, "notify-schemes-supported"); + cupsArrayAdd(ra, "operations-supported"); + cupsArrayAdd(ra, "pages-per-minute"); + cupsArrayAdd(ra, "pages-per-minute-color"); + cupsArrayAdd(ra, "pdl-override-supported"); + cupsArrayAdd(ra, "printer-alert"); + cupsArrayAdd(ra, "printer-alert-description"); + cupsArrayAdd(ra, "printer-current-time"); + cupsArrayAdd(ra, "printer-driver-installer"); + cupsArrayAdd(ra, "printer-info"); + cupsArrayAdd(ra, "printer-is-accepting-jobs"); + cupsArrayAdd(ra, "printer-location"); + cupsArrayAdd(ra, "printer-make-and-model"); + cupsArrayAdd(ra, "printer-message-from-operator"); + cupsArrayAdd(ra, "printer-more-info"); + cupsArrayAdd(ra, "printer-more-info-manufacturer"); + cupsArrayAdd(ra, "printer-name"); + cupsArrayAdd(ra, "printer-state"); + cupsArrayAdd(ra, "printer-state-message"); + cupsArrayAdd(ra, "printer-state-reasons"); + cupsArrayAdd(ra, "printer-up-time"); + cupsArrayAdd(ra, "printer-uri-supported"); + cupsArrayAdd(ra, "queued-job-count"); + cupsArrayAdd(ra, "reference-uri-schemes-supported"); + cupsArrayAdd(ra, "uri-authentication-supported"); + cupsArrayAdd(ra, "uri-security-supported"); + } + else if (!strcmp(value, "printer-defaults")) + { + cupsArrayAdd(ra, "copies-default"); + cupsArrayAdd(ra, "document-format-default"); + cupsArrayAdd(ra, "finishings-default"); + cupsArrayAdd(ra, "job-hold-until-default"); + cupsArrayAdd(ra, "job-priority-default"); + cupsArrayAdd(ra, "job-sheets-default"); + cupsArrayAdd(ra, "media-default"); + cupsArrayAdd(ra, "media-col-default"); + cupsArrayAdd(ra, "number-up-default"); + cupsArrayAdd(ra, "orientation-requested-default"); + cupsArrayAdd(ra, "sides-default"); + } + else if (!strcmp(value, "subscription-template")) + { + cupsArrayAdd(ra, "notify-attributes"); + cupsArrayAdd(ra, "notify-charset"); + cupsArrayAdd(ra, "notify-events"); + cupsArrayAdd(ra, "notify-lease-duration"); + cupsArrayAdd(ra, "notify-natural-language"); + cupsArrayAdd(ra, "notify-pull-method"); + cupsArrayAdd(ra, "notify-recipient-uri"); + cupsArrayAdd(ra, "notify-time-interval"); + cupsArrayAdd(ra, "notify-user-data"); + } + else + cupsArrayAdd(ra, value); + } + + return (ra); +} + + +/* + * 'debug_attributes()' - Print attributes in a request or response. + */ + +static void +debug_attributes(const char *title, /* I - Title */ + ipp_t *ipp) /* I - Request/response */ +{ + ipp_tag_t group_tag; /* Current group */ + ipp_attribute_t *attr; /* Current attribute */ + char buffer[2048]; /* String buffer for value */ + + + if (Verbosity <= 1) + return; + + fprintf(stderr, "%s:\n", title); + for (attr = ipp->attrs, group_tag = IPP_TAG_ZERO; attr; attr = attr->next) + { + if (attr->group_tag != group_tag) + { + group_tag = attr->group_tag; + fprintf(stderr, " %s\n", ippTagString(group_tag)); + } + + if (attr->name) + { + _ippAttrString(attr, buffer, sizeof(buffer)); + fprintf(stderr, " %s (%s%s) %s\n", attr->name, + attr->num_values > 1 ? "1setOf " : "", + ippTagString(attr->value_tag), buffer); + } + } +} + + +/* + * 'delete_client()' - Close the socket and free all memory used by a client + * object. + */ + +static void +delete_client(_ipp_client_t *client) /* I - Client */ +{ + if (Verbosity) + fprintf(stderr, "Closing connection from %s (%s)\n", client->http.hostname, + client->http.hostaddr->addr.sa_family == AF_INET ? "IPv4" : "IPv6"); + + /* + * Flush pending writes before closing... + */ + + httpFlushWrite(&(client->http)); + + if (client->http.fd >= 0) + close(client->http.fd); + + /* + * Free memory... + */ + + httpClearCookie(&(client->http)); + httpClearFields(&(client->http)); + + ippDelete(client->request); + + ippDelete(client->response); + + free(client); +} + + +/* + * 'delete_job()' - Remove from the printer and free all memory used by a job + * object. + */ + +static void +delete_job(_ipp_job_t *job) /* I - Job */ +{ + if (Verbosity) + fprintf(stderr, "Removing job #%d from history.\n", job->id); + + ippDelete(job->attrs); + + if (job->filename) + { + if (!KeepFiles) + unlink(job->filename); + + free(job->filename); + } + + free(job); +} + + +/* + * 'delete_printer()' - Unregister, close listen sockets, and free all memory + * used by a printer object. + */ + +static void +delete_printer(_ipp_printer_t *printer) /* I - Printer */ +{ + if (printer->ipv4 >= 0) + close(printer->ipv4); + + if (printer->ipv6 >= 0) + close(printer->ipv6); + + if (printer->printer_ref) + DNSServiceRefDeallocate(printer->printer_ref); + + if (printer->ipp_ref) + DNSServiceRefDeallocate(printer->ipp_ref); + + if (printer->http_ref) + DNSServiceRefDeallocate(printer->http_ref); + + if (printer->common_ref) + DNSServiceRefDeallocate(printer->common_ref); + + TXTRecordDeallocate(&(printer->ipp_txt)); + + if (printer->name) + _cupsStrFree(printer->name); + if (printer->dnssd_name) + _cupsStrFree(printer->dnssd_name); + if (printer->icon) + _cupsStrFree(printer->icon); + if (printer->directory) + _cupsStrFree(printer->directory); + if (printer->hostname) + _cupsStrFree(printer->hostname); + if (printer->uri) + _cupsStrFree(printer->uri); + + ippDelete(printer->attrs); + cupsArrayDelete(printer->jobs); + + free(printer); +} + + +/* + * 'dnssd_callback()' - Handle Bonjour registration events. + */ + +static void +dnssd_callback( + DNSServiceRef sdRef, /* I - Service reference */ + DNSServiceFlags flags, /* I - Status flags */ + DNSServiceErrorType errorCode, /* I - Error, if any */ + const char *name, /* I - Service name */ + const char *regtype, /* I - Service type */ + const char *domain, /* I - Domain for service */ + _ipp_printer_t *printer) /* I - Printer */ +{ + if (errorCode) + { + fprintf(stderr, "DNSServiceRegister for %s failed with error %d.\n", + regtype, (int)errorCode); + return; + } + else if (strcasecmp(name, printer->dnssd_name)) + { + if (Verbosity) + fprintf(stderr, "Now using DNS-SD service name \"%s\".\n", name); + + /* No lock needed since only the main thread accesses/changes this */ + _cupsStrFree(printer->dnssd_name); + printer->dnssd_name = _cupsStrAlloc(name); + } +} + + +/* + * 'find_job()' - Find a job specified in a request. + */ + +static _ipp_job_t * /* O - Job or NULL */ +find_job(_ipp_client_t *client) /* I - Client */ +{ + ipp_attribute_t *attr; /* job-id or job-uri attribute */ + _ipp_job_t key, /* Job search key */ + *job; /* Matching job, if any */ + + + key.id = 0; + + if ((attr = ippFindAttribute(client->request, "job-uri", + IPP_TAG_URI)) != NULL) + { + if (!strncmp(attr->values[0].string.text, client->printer->uri, + client->printer->urilen) && + attr->values[0].string.text[client->printer->urilen] == '/') + key.id = atoi(attr->values[0].string.text + client->printer->urilen + 1); + } + else if ((attr = ippFindAttribute(client->request, "job-id", + IPP_TAG_INTEGER)) != NULL) + key.id = attr->values[0].integer; + + _cupsRWLockRead(&(client->printer->rwlock)); + job = (_ipp_job_t *)cupsArrayFind(client->printer->jobs, &key); + _cupsRWUnlock(&(client->printer->rwlock)); + + return (job); +} + + +/* + * 'html_escape()' - Write a HTML-safe string. + */ + +static void +html_escape(_ipp_client_t *client, /* I - Client */ + const char *s, /* I - String to write */ + size_t slen) /* I - Number of characters to write */ +{ + const char *start, /* Start of segment */ + *end; /* End of string */ + + + start = s; + end = s + (slen > 0 ? slen : strlen(s)); + + while (*s && s < end) + { + if (*s == '&' || *s == '<') + { + if (s > start) + httpWrite2(&(client->http), start, s - start); + + if (*s == '&') + httpWrite2(&(client->http), "&", 5); + else + httpWrite2(&(client->http), "<", 4); + + start = s + 1; + } + + s ++; + } + + if (s > start) + httpWrite2(&(client->http), start, s - start); +} + + +/* + * 'html_printf()' - Send formatted text to the client, quoting as needed. + */ + +static void +html_printf(_ipp_client_t *client, /* I - Client */ + const char *format, /* I - Printf-style format string */ + ...) /* I - Additional arguments as needed */ +{ + va_list ap; /* Pointer to arguments */ + const char *start; /* Start of string */ + char size, /* Size character (h, l, L) */ + type; /* Format type character */ + int width, /* Width of field */ + prec; /* Number of characters of precision */ + char tformat[100], /* Temporary format string for sprintf() */ + *tptr, /* Pointer into temporary format */ + temp[1024]; /* Buffer for formatted numbers */ + char *s; /* Pointer to string */ + + + /* + * Loop through the format string, formatting as needed... + */ + + va_start(ap, format); + start = format; + + while (*format) + { + if (*format == '%') + { + if (format > start) + httpWrite2(&(client->http), start, format - start); + + tptr = tformat; + *tptr++ = *format++; + + if (*format == '%') + { + httpWrite2(&(client->http), "%", 1); + format ++; + continue; + } + else if (strchr(" -+#\'", *format)) + *tptr++ = *format++; + + if (*format == '*') + { + /* + * Get width from argument... + */ + + format ++; + width = va_arg(ap, int); + + snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", width); + tptr += strlen(tptr); + } + else + { + width = 0; + + while (isdigit(*format & 255)) + { + if (tptr < (tformat + sizeof(tformat) - 1)) + *tptr++ = *format; + + width = width * 10 + *format++ - '0'; + } + } + + if (*format == '.') + { + if (tptr < (tformat + sizeof(tformat) - 1)) + *tptr++ = *format; + + format ++; + + if (*format == '*') + { + /* + * Get precision from argument... + */ + + format ++; + prec = va_arg(ap, int); + + snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", prec); + tptr += strlen(tptr); + } + else + { + prec = 0; + + while (isdigit(*format & 255)) + { + if (tptr < (tformat + sizeof(tformat) - 1)) + *tptr++ = *format; + + prec = prec * 10 + *format++ - '0'; + } + } + } + + if (*format == 'l' && format[1] == 'l') + { + size = 'L'; + + if (tptr < (tformat + sizeof(tformat) - 2)) + { + *tptr++ = 'l'; + *tptr++ = 'l'; + } + + format += 2; + } + else if (*format == 'h' || *format == 'l' || *format == 'L') + { + if (tptr < (tformat + sizeof(tformat) - 1)) + *tptr++ = *format; + + size = *format++; + } + else + size = 0; + + + if (!*format) + { + start = format; + break; + } + + if (tptr < (tformat + sizeof(tformat) - 1)) + *tptr++ = *format; + + type = *format++; + *tptr = '\0'; + start = format; + + switch (type) + { + case 'E' : /* Floating point formats */ + case 'G' : + case 'e' : + case 'f' : + case 'g' : + if ((width + 2) > sizeof(temp)) + break; + + sprintf(temp, tformat, va_arg(ap, double)); + + httpWrite2(&(client->http), temp, strlen(temp)); + break; + + case 'B' : /* Integer formats */ + case 'X' : + case 'b' : + case 'd' : + case 'i' : + case 'o' : + case 'u' : + case 'x' : + if ((width + 2) > sizeof(temp)) + break; + +# ifdef HAVE_LONG_LONG + if (size == 'L') + sprintf(temp, tformat, va_arg(ap, long long)); + else +# endif /* HAVE_LONG_LONG */ + if (size == 'l') + sprintf(temp, tformat, va_arg(ap, long)); + else + sprintf(temp, tformat, va_arg(ap, int)); + + httpWrite2(&(client->http), temp, strlen(temp)); + break; + + case 'p' : /* Pointer value */ + if ((width + 2) > sizeof(temp)) + break; + + sprintf(temp, tformat, va_arg(ap, void *)); + + httpWrite2(&(client->http), temp, strlen(temp)); + break; + + case 'c' : /* Character or character array */ + if (width <= 1) + { + temp[0] = va_arg(ap, int); + temp[1] = '\0'; + html_escape(client, temp, 1); + } + else + html_escape(client, va_arg(ap, char *), (size_t)width); + break; + + case 's' : /* String */ + if ((s = va_arg(ap, char *)) == NULL) + s = "(null)"; + + html_escape(client, s, strlen(s)); + break; + } + } + else + format ++; + } + + if (format > start) + httpWrite2(&(client->http), start, format - start); + + va_end(ap); +} + + +/* + * 'ipp_cancel_job()' - Cancel a job. + */ + +static void +ipp_cancel_job(_ipp_client_t *client) /* I - Client */ +{ + _ipp_job_t *job; /* Job information */ + + + /* + * Get the job... + */ + + if ((job = find_job(client)) == NULL) + return; + + /* + * See if the job is already completed, canceled, or aborted; if so, + * we can't cancel... + */ + + switch (job->state) + { + case IPP_JOB_CANCELED : + respond_ipp(client, IPP_NOT_POSSIBLE, + "Job #%d is already canceled - can\'t cancel.", job->id); + break; + + case IPP_JOB_ABORTED : + respond_ipp(client, IPP_NOT_POSSIBLE, + "Job #%d is already aborted - can\'t cancel.", job->id); + break; + + case IPP_JOB_COMPLETED : + respond_ipp(client, IPP_NOT_POSSIBLE, + "Job #%d is already completed - can\'t cancel.", job->id); + break; + + default : + /* + * Cancel the job... + */ + + _cupsRWLockWrite(&(client->printer->rwlock)); + + if (job->state == IPP_JOB_PROCESSING || + (job->state == IPP_JOB_HELD && job->fd >= 0)) + job->cancel = 1; + else + { + job->state = IPP_JOB_CANCELED; + job->completed = time(NULL); + } + + _cupsRWUnlock(&(client->printer->rwlock)); + + respond_ipp(client, IPP_OK, NULL); + break; + } +} + + +#if 0 +/* + * 'ipp_create_job()' - Create a job object. + */ + +static void +ipp_create_job(_ipp_client_t *client) /* I - Client */ +{ +} +#endif /* 0 */ + + +/* + * 'ipp_get_job_attributes()' - Get the attributes for a job object. + */ + +static void +ipp_get_job_attributes( + _ipp_client_t *client) /* I - Client */ +{ + _ipp_job_t *job; /* Job */ + cups_array_t *ra; /* requested-attributes */ + + + if ((job = find_job(client)) == NULL) + { + respond_ipp(client, IPP_NOT_FOUND, "Job not found."); + return; + } + + respond_ipp(client, IPP_OK, NULL); + + ra = create_requested_array(client); + copy_job_attributes(client, job, ra); + cupsArrayDelete(ra); +} + + +/* + * 'ipp_get_jobs()' - Get a list of job objects. + */ + +static void +ipp_get_jobs(_ipp_client_t *client) /* I - Client */ +{ + ipp_attribute_t *attr; /* Current attribute */ + int job_comparison; /* Job comparison */ + ipp_jstate_t job_state; /* job-state value */ + int first_job_id, /* First job ID */ + limit, /* Maximum number of jobs to return */ + count; /* Number of jobs that match */ + const char *username; /* Username */ + _ipp_job_t *job; /* Current job pointer */ + cups_array_t *ra; /* Requested attributes array */ + + + /* + * See if the "which-jobs" attribute have been specified... + */ + + if ((attr = ippFindAttribute(client->request, "which-jobs", + IPP_TAG_KEYWORD)) != NULL) + fprintf(stderr, "%s Get-Jobs which-jobs=%s", client->http.hostname, + attr->values[0].string.text); + + if (!attr || !strcmp(attr->values[0].string.text, "not-completed")) + { + job_comparison = -1; + job_state = IPP_JOB_STOPPED; + } + else if (!strcmp(attr->values[0].string.text, "completed")) + { + job_comparison = 1; + job_state = IPP_JOB_CANCELED; + } + else if (!strcmp(attr->values[0].string.text, "aborted")) + { + job_comparison = 0; + job_state = IPP_JOB_ABORTED; + } + else if (!strcmp(attr->values[0].string.text, "all")) + { + job_comparison = 1; + job_state = IPP_JOB_PENDING; + } + else if (!strcmp(attr->values[0].string.text, "canceled")) + { + job_comparison = 0; + job_state = IPP_JOB_CANCELED; + } + else if (!strcmp(attr->values[0].string.text, "pending")) + { + job_comparison = 0; + job_state = IPP_JOB_PENDING; + } + else if (!strcmp(attr->values[0].string.text, "pending-held")) + { + job_comparison = 0; + job_state = IPP_JOB_HELD; + } + else if (!strcmp(attr->values[0].string.text, "processing")) + { + job_comparison = 0; + job_state = IPP_JOB_PROCESSING; + } + else if (!strcmp(attr->values[0].string.text, "processing-stopped")) + { + job_comparison = 0; + job_state = IPP_JOB_STOPPED; + } + else + { + respond_ipp(client, IPP_ATTRIBUTES, + "The which-jobs value \"%s\" is not supported.", + attr->values[0].string.text); + ippAddString(client->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD, + "which-jobs", NULL, attr->values[0].string.text); + return; + } + + /* + * See if they want to limit the number of jobs reported... + */ + + if ((attr = ippFindAttribute(client->request, "limit", + IPP_TAG_INTEGER)) != NULL) + { + limit = attr->values[0].integer; + + fprintf(stderr, "%s Get-Jobs limit=%d", client->http.hostname, limit); + } + else + limit = 0; + + if ((attr = ippFindAttribute(client->request, "first-job-id", + IPP_TAG_INTEGER)) != NULL) + { + first_job_id = attr->values[0].integer; + + fprintf(stderr, "%s Get-Jobs first-job-id=%d", client->http.hostname, + first_job_id); + } + else + first_job_id = 1; + + /* + * See if we only want to see jobs for a specific user... + */ + + username = NULL; + + if ((attr = ippFindAttribute(client->request, "my-jobs", + IPP_TAG_BOOLEAN)) != NULL) + { + fprintf(stderr, "%s Get-Jobs my-jobs=%s\n", client->http.hostname, + attr->values[0].boolean ? "true" : "false"); + + if (attr->values[0].boolean) + { + if ((attr = ippFindAttribute(client->request, "requesting-user-name", + IPP_TAG_NAME)) == NULL) + { + respond_ipp(client, IPP_BAD_REQUEST, + "Need requesting-user-name with my-jobs."); + return; + } + + username = attr->values[0].string.text; + + fprintf(stderr, "%s Get-Jobs requesting-user-name=\"%s\"\n", + client->http.hostname, username); + } + } + + /* + * OK, build a list of jobs for this printer... + */ + + if ((ra = create_requested_array(client)) == NULL && + !ippFindAttribute(client->request, "requested-attributes", + IPP_TAG_KEYWORD)) + { + /* + * IPP conformance - Get-Jobs has a default requested-attributes value of + * "job-id" and "job-uri". + */ + + ra = cupsArrayNew((cups_array_func_t)strcmp, NULL); + cupsArrayAdd(ra, "job-id"); + cupsArrayAdd(ra, "job-uri"); + } + + respond_ipp(client, IPP_OK, NULL); + + _cupsRWLockRead(&(client->printer->rwlock)); + + for (count = 0, job = (_ipp_job_t *)cupsArrayFirst(client->printer->jobs); + (limit <= 0 || count < limit) && job; + job = (_ipp_job_t *)cupsArrayNext(client->printer->jobs)) + { + /* + * Filter out jobs that don't match... + */ + + if ((job_comparison < 0 && job->state > job_state) || + (job_comparison == 0 && job->state != job_state) || + (job_comparison > 0 && job->state < job_state) || + job->id < first_job_id || + (username && job->username && strcasecmp(username, job->username))) + continue; + + if (count > 0) + ippAddSeparator(client->response); + + count ++; + copy_job_attributes(client, job, ra); + } + + cupsArrayDelete(ra); + + _cupsRWUnlock(&(client->printer->rwlock)); +} + + +/* + * 'ipp_get_printer_attributes()' - Get the attributes for a printer object. + */ + +static void +ipp_get_printer_attributes( + _ipp_client_t *client) /* I - Client */ +{ + cups_array_t *ra; /* Requested attributes array */ + _ipp_printer_t *printer; /* Printer */ + + + /* + * Send the attributes... + */ + + ra = create_requested_array(client); + printer = client->printer; + + respond_ipp(client, IPP_OK, NULL); + + _cupsRWLockRead(&(printer->rwlock)); + + copy_attributes(client->response, printer->attrs, ra, IPP_TAG_ZERO, + IPP_TAG_COPY); + + if (!ra || cupsArrayFind(ra, "printer-state")) + ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "printer-state", printer->state); + + if (!ra || cupsArrayFind(ra, "printer-state-reasons")) + { + if (printer->state_reasons == _IPP_PRINTER_NONE) + ippAddString(client->response, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, + "printer-state-reasons", NULL, "none"); + else + { + int num_reasons = 0;/* Number of reasons */ + const char *reasons[32]; /* Reason strings */ + + if (printer->state_reasons & _IPP_PRINTER_OTHER) + reasons[num_reasons ++] = "other"; + if (printer->state_reasons & _IPP_PRINTER_COVER_OPEN) + reasons[num_reasons ++] = "cover-open"; + if (printer->state_reasons & _IPP_PRINTER_INPUT_TRAY_MISSING) + reasons[num_reasons ++] = "input-tray-missing"; + if (printer->state_reasons & _IPP_PRINTER_MARKER_SUPPLY_EMPTY) + reasons[num_reasons ++] = "marker-supply-empty-warning"; + if (printer->state_reasons & _IPP_PRINTER_MARKER_SUPPLY_LOW) + reasons[num_reasons ++] = "marker-supply-low-report"; + if (printer->state_reasons & _IPP_PRINTER_MARKER_WASTE_ALMOST_FULL) + reasons[num_reasons ++] = "marker-waste-almost-full-report"; + if (printer->state_reasons & _IPP_PRINTER_MARKER_WASTE_FULL) + reasons[num_reasons ++] = "marker-waste-full-warning"; + if (printer->state_reasons & _IPP_PRINTER_MEDIA_EMPTY) + reasons[num_reasons ++] = "media-empty-warning"; + if (printer->state_reasons & _IPP_PRINTER_MEDIA_JAM) + reasons[num_reasons ++] = "media-jam-warning"; + if (printer->state_reasons & _IPP_PRINTER_MEDIA_LOW) + reasons[num_reasons ++] = "media-low-report"; + if (printer->state_reasons & _IPP_PRINTER_MEDIA_NEEDED) + reasons[num_reasons ++] = "media-needed-report"; + if (printer->state_reasons & _IPP_PRINTER_MOVING_TO_PAUSED) + reasons[num_reasons ++] = "moving-to-paused"; + if (printer->state_reasons & _IPP_PRINTER_PAUSED) + reasons[num_reasons ++] = "paused"; + if (printer->state_reasons & _IPP_PRINTER_SPOOL_AREA_FULL) + reasons[num_reasons ++] = "spool-area-full"; + if (printer->state_reasons & _IPP_PRINTER_TONER_EMPTY) + reasons[num_reasons ++] = "toner-empty-warning"; + if (printer->state_reasons & _IPP_PRINTER_TONER_LOW) + reasons[num_reasons ++] = "toner-low-report"; + + ippAddStrings(client->response, IPP_TAG_PRINTER, + IPP_TAG_KEYWORD | IPP_TAG_COPY, "printer-state-reasons", + num_reasons, NULL, reasons); + } + } + + if (!ra || cupsArrayFind(ra, "printer-up-time")) + ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "printer-up-time", (int)time(NULL)); + + if (!ra || cupsArrayFind(ra, "queued-job-count")) + ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "queued-job-count", + printer->active_job && + printer->active_job->state < IPP_JOB_CANCELED); + + _cupsRWUnlock(&(printer->rwlock)); + + cupsArrayDelete(ra); +} + + +/* + * 'ipp_print_job()' - Create a job object with an attached document. + */ + +static void +ipp_print_job(_ipp_client_t *client) /* I - Client */ +{ + _ipp_job_t *job; /* New job */ + char filename[1024], /* Filename buffer */ + buffer[4096]; /* Copy buffer */ + ssize_t bytes; /* Bytes read */ + cups_array_t *ra; /* Attributes to send in response */ + + + /* + * Validate print job attributes... + */ + + if (!valid_job_attributes(client)) + { + httpFlush(&(client->http)); + return; + } + + /* + * Do we have a file to print? + */ + + if (client->http.state == HTTP_POST_SEND) + { + respond_ipp(client, IPP_BAD_REQUEST, "No file in request."); + return; + } + + /* + * Print the job... + */ + + if ((job = create_job(client)) == NULL) + { + respond_ipp(client, IPP_PRINTER_BUSY, "Currently printing another job."); + return; + } + + /* + * Create a file for the request data... + */ + + if (!strcasecmp(job->format, "image/jpeg")) + snprintf(filename, sizeof(filename), "%s/%d.jpg", + client->printer->directory, job->id); + else if (!strcasecmp(job->format, "image/png")) + snprintf(filename, sizeof(filename), "%s/%d.png", + client->printer->directory, job->id); + else if (!strcasecmp(job->format, "application/pdf")) + snprintf(filename, sizeof(filename), "%s/%d.pdf", + client->printer->directory, job->id); + else if (!strcasecmp(job->format, "application/postscript")) + snprintf(filename, sizeof(filename), "%s/%d.ps", + client->printer->directory, job->id); + else + snprintf(filename, sizeof(filename), "%s/%d.prn", + client->printer->directory, job->id); + + if ((job->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0) + { + job->state = IPP_JOB_ABORTED; + + respond_ipp(client, IPP_INTERNAL_ERROR, + "Unable to create print file: %s", strerror(errno)); + return; + } + + while ((bytes = httpRead2(&(client->http), buffer, sizeof(buffer))) > 0) + { + if (write(job->fd, buffer, bytes) < bytes) + { + int error = errno; /* Write error */ + + job->state = IPP_JOB_ABORTED; + + close(job->fd); + job->fd = -1; + + unlink(filename); + + respond_ipp(client, IPP_INTERNAL_ERROR, + "Unable to write print file: %s", strerror(error)); + return; + } + } + + if (bytes < 0) + { + /* + * Got an error while reading the print data, so abort this job. + */ + + job->state = IPP_JOB_ABORTED; + + close(job->fd); + job->fd = -1; + + unlink(filename); + + respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to read print file."); + return; + } + + if (close(job->fd)) + { + int error = errno; /* Write error */ + + job->state = IPP_JOB_ABORTED; + job->fd = -1; + + unlink(filename); + + respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to write print file: %s", + strerror(error)); + return; + } + + job->fd = -1; + job->filename = strdup(filename); + job->state = IPP_JOB_PENDING; + + /* + * Process the job... + */ + + if (!_cupsThreadCreate((_cups_thread_func_t)process_job, job)) + { + job->state = IPP_JOB_ABORTED; + respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to process job."); + return; + } + + /* + * Return the job info... + */ + + respond_ipp(client, IPP_OK, NULL); + + ra = cupsArrayNew((cups_array_func_t)strcmp, NULL); + cupsArrayAdd(ra, "job-id"); + cupsArrayAdd(ra, "job-state"); + cupsArrayAdd(ra, "job-state-reasons"); + cupsArrayAdd(ra, "job-uri"); + + copy_job_attributes(client, job, ra); +} + + +#if 0 +/* + * 'ipp_send_document()' - Add an attached document to a job object created with + * Create-Job. + */ + +static void +ipp_send_document(_ipp_client_t *client)/* I - Client */ +{ +} +#endif /* 0 */ + + +/* + * 'ipp_validate_job()' - Validate job creation attributes. + */ + +static void +ipp_validate_job(_ipp_client_t *client) /* I - Client */ +{ +} + + +/* + * 'process_client()' - Process client requests on a thread. + */ + +static void * /* O - Exit status */ +process_client(_ipp_client_t *client) /* I - Client */ +{ + /* + * Loop until we are out of requests or timeout (30 seconds)... + */ + + while (httpWait(&(client->http), 30000)) + if (!process_http(client)) + break; + + /* + * Close the conection to the client and return... + */ + + delete_client(client); + + return (NULL); +} + + +/* + * 'process_http()' - Process a HTTP request. + */ + +int /* O - 1 on success, 0 on failure */ +process_http(_ipp_client_t *client) /* I - Client connection */ +{ + char line[4096], /* Line from client... */ + operation[64], /* Operation code from socket */ + uri[1024], /* URI */ + version[64], /* HTTP version number string */ + *ptr; /* Pointer into strings */ + int major, minor; /* HTTP version numbers */ + http_status_t status; /* Transfer status */ + ipp_state_t state; /* State of IPP transfer */ + + + /* + * Abort if we have an error on the connection... + */ + + if (client->http.error) + return (0); + + /* + * Clear state variables... + */ + + httpClearFields(&(client->http)); + ippDelete(client->request); + ippDelete(client->response); + + client->http.activity = time(NULL); + client->http.version = HTTP_1_1; + client->http.keep_alive = HTTP_KEEPALIVE_OFF; + client->http.data_encoding = HTTP_ENCODE_LENGTH; + client->http.data_remaining = 0; + client->request = NULL; + client->response = NULL; + client->operation = HTTP_WAITING; + + /* + * Read a request from the connection... + */ + + while ((ptr = httpGets(line, sizeof(line) - 1, &(client->http))) != NULL) + if (*ptr) + break; + + if (!ptr) + return (0); + + /* + * Parse the request line... + */ + + fprintf(stderr, "%s %s\n", client->http.hostname, line); + + switch (sscanf(line, "%63s%1023s%63s", operation, uri, version)) + { + case 1 : + fprintf(stderr, "%s Bad request line.\n", client->http.hostname); + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + + case 2 : + client->http.version = HTTP_0_9; + break; + + case 3 : + if (sscanf(version, "HTTP/%d.%d", &major, &minor) != 2) + { + fprintf(stderr, "%s Bad HTTP version.\n", client->http.hostname); + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + + if (major < 2) + { + client->http.version = (http_version_t)(major * 100 + minor); + if (client->http.version == HTTP_1_1) + client->http.keep_alive = HTTP_KEEPALIVE_ON; + else + client->http.keep_alive = HTTP_KEEPALIVE_OFF; + } + else + { + respond_http(client, HTTP_NOT_SUPPORTED, NULL, 0); + return (0); + } + break; + } + + /* + * Handle full URLs in the request line... + */ + + if (!strncmp(client->uri, "http:", 5) || !strncmp(client->uri, "ipp:", 4)) + { + char scheme[32], /* Method/scheme */ + userpass[128], /* Username:password */ + hostname[HTTP_MAX_HOST];/* Hostname */ + int port; /* Port number */ + + /* + * Separate the URI into its components... + */ + + if (httpSeparateURI(HTTP_URI_CODING_MOST, uri, scheme, sizeof(scheme), + userpass, sizeof(userpass), + hostname, sizeof(hostname), &port, + client->uri, sizeof(client->uri)) < HTTP_URI_OK) + { + fprintf(stderr, "%s Bad URI \"%s\".\n", client->http.hostname, uri); + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + } + else + { + /* + * Decode URI + */ + + if (!_httpDecodeURI(client->uri, uri, sizeof(client->uri))) + { + fprintf(stderr, "%s Bad URI \"%s\".\n", client->http.hostname, uri); + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + } + + /* + * Process the request... + */ + + if (!strcmp(operation, "GET")) + client->http.state = HTTP_GET; + else if (!strcmp(operation, "POST")) + client->http.state = HTTP_POST; + else if (!strcmp(operation, "OPTIONS")) + client->http.state = HTTP_OPTIONS; + else if (!strcmp(operation, "HEAD")) + client->http.state = HTTP_HEAD; + else + { + fprintf(stderr, "%s Bad operation \"%s\".\n", client->http.hostname, + operation); + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + + client->start = time(NULL); + client->operation = client->http.state; + client->http.status = HTTP_OK; + + /* + * Parse incoming parameters until the status changes... + */ + + while ((status = httpUpdate(&(client->http))) == HTTP_CONTINUE); + + if (status != HTTP_OK) + { + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + + if (!client->http.fields[HTTP_FIELD_HOST][0] && + client->http.version >= HTTP_1_1) + { + /* + * HTTP/1.1 and higher require the "Host:" field... + */ + + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + + /* + * Handle HTTP Upgrade... + */ + + if (!strcasecmp(client->http.fields[HTTP_FIELD_CONNECTION], "Upgrade")) + { + if (!respond_http(client, HTTP_NOT_IMPLEMENTED, NULL, 0)) + return (0); + } + + /* + * Handle new transfers... + */ + + switch (client->operation) + { + case HTTP_OPTIONS : + /* + * Do HEAD/OPTIONS command... + */ + + return (respond_http(client, HTTP_OK, NULL, 0)); + + case HTTP_HEAD : + if (!strcmp(client->uri, "/icon.png")) + return (respond_http(client, HTTP_OK, "image/png", 0)); + else if (!strcmp(client->uri, "/")) + return (respond_http(client, HTTP_OK, "text/html", 0)); + else + return (respond_http(client, HTTP_NOT_FOUND, NULL, 0)); + break; + + case HTTP_GET : + if (!strcmp(client->uri, "/icon.png")) + { + /* + * Send PNG icon file. + */ + + int fd; /* Icon file */ + struct stat fileinfo; /* Icon file information */ + char buffer[4096]; /* Copy buffer */ + ssize_t bytes; /* Bytes */ + + if (!stat(client->printer->icon, &fileinfo) && + (fd = open(client->printer->icon, O_RDONLY)) >= 0) + { + if (!respond_http(client, HTTP_OK, "image/png", fileinfo.st_size)) + { + close(fd); + return (0); + } + + while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) + httpWrite2(&(client->http), buffer, bytes); + + httpFlushWrite(&(client->http)); + + close(fd); + } + else + return (respond_http(client, HTTP_NOT_FOUND, NULL, 0)); + } + else if (!strcmp(client->uri, "/")) + { + /* + * Show web status page... + */ + + if (!respond_http(client, HTTP_OK, "text/html", 0)) + return (0); + + html_printf(client, + "\n" + "\n" + "\n" + "%s\n" + "\n" + "\n" + "\n" + "\n" + "

%s

\n" + "

%s, %d job(s).

\n" + "\n" + "\n", + client->printer->name, client->printer->name, + client->printer->state == IPP_PRINTER_IDLE ? "Idle" : + client->printer->state == IPP_PRINTER_PROCESSING ? + "Printing" : "Stopped", + cupsArrayCount(client->printer->jobs)); + httpWrite2(&(client->http), "", 0); + + return (1); + } + else + return (respond_http(client, HTTP_NOT_FOUND, NULL, 0)); + break; + + case HTTP_POST : + if (client->http.data_remaining < 0 || + (!client->http.fields[HTTP_FIELD_CONTENT_LENGTH][0] && + client->http.data_encoding == HTTP_ENCODE_LENGTH)) + { + /* + * Negative content lengths are invalid... + */ + + return (respond_http(client, HTTP_BAD_REQUEST, NULL, 0)); + } + + if (strcmp(client->http.fields[HTTP_FIELD_CONTENT_TYPE], + "application/ipp")) + { + /* + * Not an IPP request... + */ + + return (respond_http(client, HTTP_BAD_REQUEST, NULL, 0)); + } + + /* + * Read the IPP request... + */ + + client->request = ippNew(); + + while ((state = ippRead(&(client->http), client->request)) != IPP_DATA) + if (state == IPP_ERROR) + { + fprintf(stderr, "%s IPP read error (%s).\n", client->http.hostname, + ippOpString(client->request->request.op.operation_id)); + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + + /* + * Now that we have the IPP request, process the request... + */ + + return (process_ipp(client)); + + default : + break; /* Anti-compiler-warning-code */ + } + + return (1); +} + + +/* + * 'process_ipp()' - Process an IPP request. + */ + +static int /* O - 1 on success, 0 on error */ +process_ipp(_ipp_client_t *client) /* I - Client */ +{ + ipp_tag_t group; /* Current group tag */ + ipp_attribute_t *attr; /* Current attribute */ + ipp_attribute_t *charset; /* Character set attribute */ + ipp_attribute_t *language; /* Language attribute */ + ipp_attribute_t *uri; /* Printer URI attribute */ + + + debug_attributes("Request", client->request); + + /* + * First build an empty response message for this request... + */ + + client->operation_id = client->request->request.op.operation_id; + client->response = ippNew(); + + client->response->request.status.version[0] = + client->request->request.op.version[0]; + client->response->request.status.version[1] = + client->request->request.op.version[1]; + client->response->request.status.request_id = + client->request->request.op.request_id; + + /* + * Then validate the request header and required attributes... + */ + + if (client->request->request.any.version[0] < 1 || + client->request->request.any.version[0] > 2) + { + /* + * Return an error, since we only support IPP 1.x and 2.x. + */ + + respond_ipp(client, IPP_VERSION_NOT_SUPPORTED, + "Bad request version number %d.%d.", + client->request->request.any.version[0], + client->request->request.any.version[1]); + } + else if (client->request->request.any.request_id <= 0) + respond_ipp(client, IPP_BAD_REQUEST, "Bad request-id %d.", + client->request->request.any.request_id); + else if (!client->request->attrs) + respond_ipp(client, IPP_BAD_REQUEST, "No attributes in request."); + else + { + /* + * Make sure that the attributes are provided in the correct order and + * don't repeat groups... + */ + + for (attr = client->request->attrs, group = attr->group_tag; + attr; + attr = attr->next) + if (attr->group_tag < group && attr->group_tag != IPP_TAG_ZERO) + { + /* + * Out of order; return an error... + */ + + respond_ipp(client, IPP_BAD_REQUEST, + "Attribute groups are out of order (%x < %x).", + attr->group_tag, group); + break; + } + else + group = attr->group_tag; + + if (!attr) + { + /* + * Then make sure that the first three attributes are: + * + * attributes-charset + * attributes-natural-language + * printer-uri/job-uri + */ + + attr = client->request->attrs; + if (attr && attr->name && + !strcmp(attr->name, "attributes-charset") && + (attr->value_tag & IPP_TAG_MASK) == IPP_TAG_CHARSET) + charset = attr; + else + charset = NULL; + + if (attr) + attr = attr->next; + + if (attr && attr->name && + !strcmp(attr->name, "attributes-natural-language") && + (attr->value_tag & IPP_TAG_MASK) == IPP_TAG_LANGUAGE) + language = attr; + else + language = NULL; + + if ((attr = ippFindAttribute(client->request, "printer-uri", + IPP_TAG_URI)) != NULL) + uri = attr; + else if ((attr = ippFindAttribute(client->request, "job-uri", + IPP_TAG_URI)) != NULL) + uri = attr; + else + uri = NULL; + + ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, + charset ? charset->values[0].string.text : "utf-8"); + + ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, + "attributes-natural-language", NULL, + language ? language->values[0].string.text : "en"); + + if (charset && + strcasecmp(charset->values[0].string.text, "us-ascii") && + strcasecmp(charset->values[0].string.text, "utf-8")) + { + /* + * Bad character set... + */ + + respond_ipp(client, IPP_BAD_REQUEST, + "Unsupported character set \"%s\".", + charset->values[0].string.text); + } + else if (!charset || !language || !uri) + { + /* + * Return an error, since attributes-charset, + * attributes-natural-language, and printer-uri/job-uri are required + * for all operations. + */ + + respond_ipp(client, IPP_BAD_REQUEST, "Missing required attributes."); + } + else if (strcmp(uri->values[0].string.text, client->printer->uri) && + strncmp(uri->values[0].string.text, client->printer->uri, + client->printer->urilen)) + { + respond_ipp(client, IPP_NOT_FOUND, "%s %s not found.", uri->name, + uri->values[0].string.text); + } + else + { + /* + * Try processing the operation... + */ + + if (client->http.expect == HTTP_CONTINUE) + { + /* + * Send 100-continue header... + */ + + if (!respond_http(client, HTTP_CONTINUE, NULL, 0)) + return (0); + } + + switch (client->request->request.op.operation_id) + { + case IPP_PRINT_JOB : + ipp_print_job(client); + break; + + case IPP_VALIDATE_JOB : + ipp_validate_job(client); + break; + + case IPP_CANCEL_JOB : + ipp_cancel_job(client); + break; + + case IPP_GET_JOB_ATTRIBUTES : + ipp_get_job_attributes(client); + break; + + case IPP_GET_JOBS : + ipp_get_jobs(client); + break; + + case IPP_GET_PRINTER_ATTRIBUTES : + ipp_get_printer_attributes(client); + break; + + default : + respond_ipp(client, IPP_OPERATION_NOT_SUPPORTED, + "Operation not supported."); + break; + } + } + } + } + + /* + * Send the HTTP header and return... + */ + + if (client->http.state != HTTP_POST_SEND) + httpFlush(&(client->http)); /* Flush trailing (junk) data */ + + return (respond_http(client, HTTP_OK, "application/ipp", + ippLength(client->response))); +} + + +/* + * 'process_job()' - Process a print job. + */ + +static void * /* O - Thread exit status */ +process_job(_ipp_job_t *job) /* I - Job */ +{ + job->state = IPP_JOB_PROCESSING; + job->printer->state = IPP_PRINTER_PROCESSING; + + sleep(5); + + if (job->cancel) + job->state = IPP_JOB_CANCELED; + else + job->state = IPP_JOB_COMPLETED; + + job->completed = time(NULL); + job->printer->state = IPP_PRINTER_IDLE; + job->printer->active_job = NULL; + + return (NULL); +} + + +/* + * 'register_printer()' - Register a printer object via Bonjour. + */ + +static int /* O - 1 on success, 0 on error */ +register_printer( + _ipp_printer_t *printer, /* I - Printer */ + const char *location, /* I - Location */ + const char *make, /* I - Manufacturer */ + const char *model, /* I - Model name */ + const char *formats, /* I - Supported formats */ + const char *adminurl, /* I - Web interface URL */ + int color, /* I - 1 = color, 0 = monochrome */ + int duplex, /* I - 1 = duplex, 0 = simplex */ + const char *regtype) /* I - Service type */ +{ + DNSServiceErrorType error; /* Error from Bonjour */ + char make_model[256],/* Make and model together */ + product[256]; /* Product string */ + + + /* + * Build the TXT record for IPP... + */ + + snprintf(make_model, sizeof(make_model), "%s %s", make, model); + snprintf(product, sizeof(product), "(%s)", model); + + TXTRecordCreate(&(printer->ipp_txt), 1024, NULL); + TXTRecordSetValue(&(printer->ipp_txt), "txtvers", 1, "1"); + TXTRecordSetValue(&(printer->ipp_txt), "qtotal", 1, "1"); + TXTRecordSetValue(&(printer->ipp_txt), "rp", 3, "ipp"); + TXTRecordSetValue(&(printer->ipp_txt), "ty", (uint8_t)strlen(make_model), + make_model); + TXTRecordSetValue(&(printer->ipp_txt), "adminurl", (uint8_t)strlen(adminurl), + adminurl); + TXTRecordSetValue(&(printer->ipp_txt), "note", (uint8_t)strlen(location), + location); + TXTRecordSetValue(&(printer->ipp_txt), "priority", 1, "0"); + TXTRecordSetValue(&(printer->ipp_txt), "product", (uint8_t)strlen(product), + product); + TXTRecordSetValue(&(printer->ipp_txt), "pdl", (uint8_t)strlen(formats), + formats); + TXTRecordSetValue(&(printer->ipp_txt), "Color", 1, color ? "T" : "F"); + TXTRecordSetValue(&(printer->ipp_txt), "Duplex", 1, duplex ? "T" : "F"); + TXTRecordSetValue(&(printer->ipp_txt), "usb_MFG", (uint8_t)strlen(make), + make); + TXTRecordSetValue(&(printer->ipp_txt), "usb_MDL", (uint8_t)strlen(model), + model); + TXTRecordSetValue(&(printer->ipp_txt), "air", 4, "none"); + + /* + * Create a shared service reference for Bonjour... + */ + + if ((error = DNSServiceCreateConnection(&(printer->common_ref))) + != kDNSServiceErr_NoError) + { + fprintf(stderr, "Unable to create mDNSResponder connection: %d\n", error); + return (0); + } + + /* + * Register the _printer._tcp (LPD) service type with a port number of 0 to + * defend our service name but not actually support LPD... + */ + + printer->printer_ref = printer->common_ref; + + if ((error = DNSServiceRegister(&(printer->printer_ref), + kDNSServiceFlagsShareConnection, + 0 /* interfaceIndex */, printer->dnssd_name, + "_printer._tcp", NULL /* domain */, + NULL /* host */, 0 /* port */, 0 /* txtLen */, + NULL /* txtRecord */, + (DNSServiceRegisterReply)dnssd_callback, + printer)) != kDNSServiceErr_NoError) + { + fprintf(stderr, "Unable to register \"%s._printer._tcp\": %d\n", + printer->dnssd_name, error); + return (0); + } + + /* + * Then register the _ipp._tcp (IPP) service type with the real port number to + * advertise our IPP printer... + */ + + printer->ipp_ref = printer->common_ref; + + if ((error = DNSServiceRegister(&(printer->ipp_ref), + kDNSServiceFlagsShareConnection, + 0 /* interfaceIndex */, printer->dnssd_name, + regtype, NULL /* domain */, + NULL /* host */, htons(printer->port), + TXTRecordGetLength(&(printer->ipp_txt)), + TXTRecordGetBytesPtr(&(printer->ipp_txt)), + (DNSServiceRegisterReply)dnssd_callback, + printer)) != kDNSServiceErr_NoError) + { + fprintf(stderr, "Unable to register \"%s.%s\": %d\n", + printer->dnssd_name, regtype, error); + return (0); + } + + /* + * Similarly, register the _http._tcp,_printer (HTTP) service type with the + * real port number to advertise our IPP printer... + */ + + printer->http_ref = printer->common_ref; + + if ((error = DNSServiceRegister(&(printer->http_ref), + kDNSServiceFlagsShareConnection, + 0 /* interfaceIndex */, printer->dnssd_name, + "_http._tcp,_printer", NULL /* domain */, + NULL /* host */, htons(printer->port), + 0 /* txtLen */, NULL, /* txtRecord */ + (DNSServiceRegisterReply)dnssd_callback, + printer)) != kDNSServiceErr_NoError) + { + fprintf(stderr, "Unable to register \"%s.%s\": %d\n", + printer->dnssd_name, regtype, error); + return (0); + } + + return (1); +} + + +/* + * 'respond_http()' - Send a HTTP response. + */ + +int /* O - 1 on success, 0 on failure */ +respond_http(_ipp_client_t *client, /* I - Client */ + http_status_t code, /* I - HTTP status of response */ + const char *type, /* I - MIME type of response */ + size_t length) /* I - Length of response */ +{ + char message[1024]; /* Text message */ + + + fprintf(stderr, "%s %s\n", client->http.hostname, httpStatus(code)); + + if (code == HTTP_CONTINUE) + { + /* + * 100-continue doesn't send any headers... + */ + + return (httpPrintf(&(client->http), "HTTP/%d.%d 100 Continue\r\n\r\n", + client->http.version / 100, + client->http.version % 100) > 0); + } + + /* + * Format an error message... + */ + + if (!type && !length && code != HTTP_OK) + { + snprintf(message, sizeof(message), "%d - %s\n", code, httpStatus(code)); + + type = "text/plain"; + length = strlen(message); + } + else + message[0] = '\0'; + + /* + * Send the HTTP status header... + */ + + httpFlushWrite(&(client->http)); + + client->http.data_encoding = HTTP_ENCODE_FIELDS; + + if (httpPrintf(&(client->http), "HTTP/%d.%d %d %s\r\n", client->http.version / 100, + client->http.version % 100, code, httpStatus(code)) < 0) + return (0); + + /* + * Follow the header with the response fields... + */ + + if (httpPrintf(&(client->http), "Date: %s\r\n", httpGetDateString(time(NULL))) < 0) + return (0); + + if (client->http.keep_alive && client->http.version >= HTTP_1_0) + { + if (httpPrintf(&(client->http), + "Connection: Keep-Alive\r\n" + "Keep-Alive: timeout=10\r\n") < 0) + return (0); + } + + if (code == HTTP_METHOD_NOT_ALLOWED || client->operation == HTTP_OPTIONS) + { + if (httpPrintf(&(client->http), "Allow: GET, HEAD, OPTIONS, POST\r\n") < 0) + return (0); + } + + if (type) + { + if (!strcmp(type, "text/html")) + { + if (httpPrintf(&(client->http), + "Content-Type: text/html; charset=utf-8\r\n") < 0) + return (0); + } + else if (httpPrintf(&(client->http), "Content-Type: %s\r\n", type) < 0) + return (0); + } + + if (length == 0 && !message[0]) + { + if (httpPrintf(&(client->http), "Transfer-Encoding: chunked\r\n\r\n") < 0) + return (0); + } + else if (httpPrintf(&(client->http), "Content-Length: " CUPS_LLFMT "\r\n\r\n", + CUPS_LLCAST length) < 0) + return (0); + + if (httpFlushWrite(&(client->http)) < 0) + return (0); + + /* + * Send the response data... + */ + + if (message[0]) + { + /* + * Send a plain text message. + */ + + if (httpPrintf(&(client->http), "%s", message) < 0) + return (0); + } + else if (client->response) + { + /* + * Send an IPP response... + */ + + debug_attributes("Response", client->response); + + client->http.data_encoding = HTTP_ENCODE_LENGTH; + client->http.data_remaining = (off_t)ippLength(client->response); + client->response->state = IPP_IDLE; + + if (ippWrite(&(client->http), client->response) != IPP_DATA) + return (0); + } + else + client->http.data_encoding = HTTP_ENCODE_CHUNKED; + + /* + * Flush the data and return... + */ + + return (httpFlushWrite(&(client->http)) >= 0); +} + + +/* + * 'respond_ipp()' - Send an IPP response. + */ + +static void +respond_ipp(_ipp_client_t *client, /* I - Client */ + ipp_status_t status, /* I - status-code */ + const char *message, /* I - printf-style status-message */ + ...) /* I - Additional args as needed */ +{ + va_list ap; /* Pointer to additional args */ + char formatted[1024]; /* Formatted errror message */ + + + client->response->request.status.status_code = status; + + if (!client->response->attrs) + { + ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, "utf-8"); + ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, + "attributes-natural-language", NULL, "en-US"); + } + + if (message) + { + va_start(ap, message); + vsnprintf(formatted, sizeof(formatted), message, ap); + va_end(ap); + + ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_TEXT, + "status-message", NULL, formatted); + } + else + formatted[0] = '\0'; + + fprintf(stderr, "%s %s %s (%s)\n", client->http.hostname, + ippOpString(client->operation_id), ippErrorString(status), formatted); +} + + +/* + * 'run_printer()' - Run the printer service. + */ + +static void +run_printer(_ipp_printer_t *printer) /* I - Printer */ +{ + struct pollfd polldata[3]; /* poll() data */ + int timeout; /* Timeout for poll() */ + _ipp_client_t *client; /* New client */ + + + /* + * Setup poll() data for the Bonjour service socket and IPv4/6 listeners... + */ + + polldata[0].fd = printer->ipv4; + polldata[0].events = POLLIN; + + polldata[1].fd = printer->ipv6; + polldata[1].events = POLLIN; + + polldata[2].fd = DNSServiceRefSockFD(printer->common_ref); + polldata[2].events = POLLIN; + + /* + * Loop until we are killed or have a hard error... + */ + + for (;;) + { + if (cupsArrayCount(printer->jobs)) + timeout = 10; + else + timeout = -1; + + if (poll(polldata, (int)(sizeof(polldata) / sizeof(polldata[0])), + timeout) < 0 && errno != EINTR) + { + perror("poll() failed"); + break; + } + + if (polldata[0].revents & POLLIN) + { + if ((client = create_client(printer, printer->ipv4)) != NULL) + { + if (!_cupsThreadCreate((_cups_thread_func_t)process_client, client)) + { + perror("Unable to create client thread"); + delete_client(client); + } + } + } + + if (polldata[1].revents & POLLIN) + { + if ((client = create_client(printer, printer->ipv6)) != NULL) + { + if (!_cupsThreadCreate((_cups_thread_func_t)process_client, client)) + { + perror("Unable to create client thread"); + delete_client(client); + } + } + } + + if (polldata[2].revents & POLLIN) + DNSServiceProcessResult(printer->common_ref); + + /* + * Clean out old jobs... + */ + + clean_jobs(printer); + } +} + + +/* + * 'usage()' - Show program usage. + */ + +static void +usage(int status) /* O - Exit status */ +{ + if (!status) + { + puts(CUPS_SVERSION " - Copyright 2010 by Apple Inc. All rights reserved."); + puts(""); + } + + puts("Usage: ippserver [options] \"name\""); + puts(""); + puts("Options:"); + puts("-2 Supports 2-sided printing (default=1-sided)"); + puts("-M manufacturer Manufacturer name (default=Test)"); + printf("-d spool-directory Spool directory " + "(default=/tmp/ippserver.%d)\n", (int)getpid()); + puts("-f type/subtype[,...] List of supported types " + "(default=application/pdf,image/jpeg)"); + puts("-h Show program help"); + puts("-i iconfile.png PNG icon file (default=printer.png)"); + puts("-l location Location of printer (default=empty string)"); + puts("-m model Model name (default=Printer)"); + puts("-n hostname Hostname for printer"); + puts("-p port Port number (default=auto)"); + puts("-r regtype Bonjour service type (default=_ipp._tcp)"); + puts("-s speed[,color-speed] Speed in pages per minute (default=10,0)"); + puts("-v[vvv] Be (very) verbose"); + + exit(status); +} + + +/* + * 'valid_job_attributes()' - Determine whether the job attributes are valid. + * + * When one or more job attributes are invalid, this function adds a suitable + * response and attributes to the unsupported group. + */ + +static int /* O - 1 if valid, 0 if not */ +valid_job_attributes( + _ipp_client_t *client) /* I - Client */ +{ + int i; /* Looping var */ + ipp_attribute_t *attr, /* Current attribute */ + *supported; /* document-format-supported */ + const char *format = NULL; /* document-format value */ + int valid = 1; /* Valid attributes? */ + + + /* + * Check operation attributes... + */ + +#define respond_unsupported(client, attr) \ + if (valid) \ + { \ + respond_ipp(client, IPP_ATTRIBUTES, "Unsupported %s %s%s value.", \ + attr->name, attr->num_values > 1 ? "1setOf " : "", \ + ippTagString(attr->value_tag)); \ + valid = 0; \ + } \ + copy_attribute(client->response, attr, 0, IPP_TAG_UNSUPPORTED_GROUP) + + if ((attr = ippFindAttribute(client->request, "compression", + IPP_TAG_ZERO)) != NULL) + { + /* + * If compression is specified, only accept "none"... + */ + + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD || + strcmp(attr->values[0].string.text, "none")) + { + respond_unsupported(client, attr); + } + else + fprintf(stderr, "%s Print-Job compression=\"%s\"\n", client->http.hostname, + attr->values[0].string.text); + } + + /* + * Is it a format we support? + */ + + if ((attr = ippFindAttribute(client->request, "document-format", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_MIMETYPE) + { + respond_unsupported(client, attr); + } + else + format = attr->values[0].string.text; + } + else + format = "application/octet-stream"; + + if (!strcmp(format, "application/octet-stream") && + client->request->request.op.operation_id != IPP_VALIDATE_JOB) + { + /* + * Auto-type the file using the first 4 bytes of the file... + */ + + unsigned char header[4]; /* First 4 bytes of file */ + + memset(header, 0, sizeof(header)); + _httpPeek(&(client->http), (char *)header, sizeof(header)); + + if (!memcmp(header, "%PDF", 4)) + format = "application/pdf"; + else if (!memcmp(header, "%!", 2)) + format = "application/postscript"; + else if (!memcmp(header, "\377\330\377", 3) && + header[3] >= 0xe0 && header[3] <= 0xef) + format = "image/jpeg"; + else if (!memcmp(header, "\211PNG", 4)) + format = "image/png"; + + if (format) + fprintf(stderr, "%s %s Auto-typed document-format=\"%s\"\n", + client->http.hostname, + ippOpString(client->request->request.op.operation_id), format); + + if (!attr) + ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, + "document-format", NULL, format); + else + { + _cupsStrFree(attr->values[0].string.text); + attr->values[0].string.text = _cupsStrAlloc(format); + } + } + + if ((supported = ippFindAttribute(client->printer->attrs, + "document-format-supported", + IPP_TAG_MIMETYPE)) != NULL) + { + for (i = 0; i < attr->num_values; i ++) + if (!strcasecmp(format, attr->values[i].string.text)) + break; + + if (i >= attr->num_values) + { + respond_unsupported(client, attr); + } + } + + /* + * Check the various job template attributes... + */ + + if ((attr = ippFindAttribute(client->request, "copies", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_INTEGER || + attr->values[0].integer < 1 || attr->values[0].integer > 999) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "ipp-attribute-fidelity", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_BOOLEAN) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "job-hold-until", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || + (attr->value_tag != IPP_TAG_NAME && + attr->value_tag != IPP_TAG_NAMELANG && + attr->value_tag != IPP_TAG_KEYWORD) || + strcmp(attr->values[0].string.text, "no-hold")) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "job-name", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || + (attr->value_tag != IPP_TAG_NAME && + attr->value_tag != IPP_TAG_NAMELANG)) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "job-priority", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_INTEGER || + attr->values[0].integer < 1 || attr->values[0].integer > 100) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "job-sheets", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || + (attr->value_tag != IPP_TAG_NAME && + attr->value_tag != IPP_TAG_NAMELANG && + attr->value_tag != IPP_TAG_KEYWORD) || + strcmp(attr->values[0].string.text, "none")) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "media", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || + (attr->value_tag != IPP_TAG_NAME && + attr->value_tag != IPP_TAG_NAMELANG && + attr->value_tag != IPP_TAG_KEYWORD)) + { + respond_unsupported(client, attr); + } + else + { + for (i = 0; + i < (int)(sizeof(media_supported) / sizeof(media_supported[0])); + i ++) + if (!strcmp(attr->values[0].string.text, media_supported[i])) + break; + + if (i >= (int)(sizeof(media_supported) / sizeof(media_supported[0]))) + { + respond_unsupported(client, attr); + } + } + } + + if ((attr = ippFindAttribute(client->request, "media-col", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_BEGIN_COLLECTION) + { + respond_unsupported(client, attr); + } + /* TODO: check for valid media-col */ + } + + if ((attr = ippFindAttribute(client->request, "multiple-document-handling", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD || + (strcmp(attr->values[0].string.text, + "separate-documents-uncollated-copies") && + strcmp(attr->values[0].string.text, + "separate-documents-collated-copies"))) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "orientation-requested", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_ENUM || + attr->values[0].integer < IPP_PORTRAIT || + attr->values[0].integer > IPP_REVERSE_PORTRAIT) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "page-ranges", + IPP_TAG_ZERO)) != NULL) + { + respond_unsupported(client, attr); + } + + if ((attr = ippFindAttribute(client->request, "print-quality", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_ENUM || + attr->values[0].integer < IPP_QUALITY_DRAFT || + attr->values[0].integer > IPP_QUALITY_HIGH) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "printer-resolution", + IPP_TAG_ZERO)) != NULL) + { + respond_unsupported(client, attr); + } + + if ((attr = ippFindAttribute(client->request, "sides", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD) + { + respond_unsupported(client, attr); + } + + if ((supported = ippFindAttribute(client->printer->attrs, "sides", + IPP_TAG_KEYWORD)) != NULL) + { + for (i = 0; i < supported->num_values; i ++) + if (!strcmp(attr->values[0].string.text, + supported->values[i].string.text)) + break; + + if (i >= supported->num_values) + { + respond_unsupported(client, attr); + } + } + else + { + respond_unsupported(client, attr); + } + } + + return (valid); +} + + +/* + * End of "$Id$". + */ diff --git a/test/ipptool.c b/test/ipptool.c index 6832f9d34..3ea4ec86c 100644 --- a/test/ipptool.c +++ b/test/ipptool.c @@ -117,6 +117,7 @@ typedef struct _cups_vars_s /**** Set of variables ****/ int port; /* Port number from URI */ http_encryption_t encryption; /* Encryption for connection? */ double timeout; /* Timeout for connection */ + int family; /* Address family */ cups_array_t *vars; /* Array of variables */ } _cups_vars_t; @@ -232,7 +233,8 @@ main(int argc, /* I - Number of command-line args */ _cupsSetLocale(argv); memset(&vars, 0, sizeof(vars)); - vars.vars = cupsArrayNew((cups_array_func_t)compare_vars, NULL); + vars.family = AF_UNSPEC; + vars.vars = cupsArrayNew((cups_array_func_t)compare_vars, NULL); /* * We need at least: @@ -253,6 +255,16 @@ main(int argc, /* I - Number of command-line args */ { switch (*opt) { + case '4' : /* Connect using IPv4 only */ + vars.family = AF_INET; + break; + +#ifdef AF_INET6 + case '6' : /* Connect using IPv6 only */ + vars.family = AF_INET6; + break; +#endif /* AF_INET6 */ + case 'C' : /* Enable HTTP chunking */ Transfer = _CUPS_TRANSFER_CHUNKED; break; @@ -644,8 +656,16 @@ do_tests(_cups_vars_t *vars, /* I - Variables */ * Connect to the server... */ - if ((http = httpConnectEncrypt(vars->hostname, vars->port, - vars->encryption)) == NULL) + if ((http = _httpCreate(vars->hostname, vars->port, vars->encryption, + vars->family)) == NULL) + { + print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname, + vars->port, strerror(errno)); + pass = 0; + goto test_exit; + } + + if (httpReconnect(http)) { print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname, vars->port, strerror(errno)); @@ -3743,10 +3763,12 @@ usage(void) "\n" "Options:\n" "\n" - "-C Send requests using chunking (default)\n" + "-4 Connect using IPv4.\n" + "-6 Connect using IPv6.\n" + "-C Send requests using chunking (default).\n" "-E Test with TLS encryption.\n" - "-I Ignore errors\n" - "-L Send requests using content-length\n" + "-I Ignore errors.\n" + "-L Send requests using content-length.\n" "-S Test with SSL encryption.\n" "-T Set the receive/send timeout in seconds.\n" "-V version Set default IPP version.\n" diff --git a/test/printer.opacity b/test/printer.opacity new file mode 100644 index 0000000000000000000000000000000000000000..595854a243b507b86b9cd5e6c3712a19900a106a GIT binary patch literal 18493 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 L)Yqs{vy1vaawq$n literal 0 Hc-jL100001 -- 2.39.2