/* -*- Mode: C; tab-width: 4 -*- * * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ Change History (most recent first): $Log: uds_daemon.c,v $ Revision 1.164 2005/01/28 06:07:55 cheshire Don't use deliver_error() from within handle_regrecord_request() Revision 1.163 2005/01/28 01:39:16 cheshire Include file descriptor number in "broken pipe" message Revision 1.162 2005/01/27 23:59:20 cheshire Remove extraneous LogMsg Revision 1.161 2005/01/27 22:57:56 cheshire Fix compile errors on gcc4 Revision 1.160 2005/01/27 20:52:11 cheshire mDNSResponder leaks sockets for add/update/remove record calls Revision 1.159 2005/01/27 01:45:25 cheshire mDNSResponder should never call exit(1); Revision 1.158 2005/01/25 17:28:07 ksekar Should not return "local" twice for domain enumeration Revision 1.157 2005/01/21 02:20:39 cheshire Fix mistake in LogOperation() format string Revision 1.156 2005/01/19 19:15:36 ksekar Refinement to - Simplify mDNS_PurgeResultsForDomain logic and move into daemon layer Revision 1.155 2005/01/19 03:00:47 cheshire Show Add/Rmv in DNSServiceBrowse LogOperation() message Revision 1.154 2005/01/15 00:56:42 ksekar Unicast services don't disappear when logging out of VPN Revision 1.153 2005/01/14 18:44:28 ksekar mDNSResponder is crashing when changing domains Revision 1.152 2005/01/13 17:16:38 ksekar Back out checkin 1.150 - correct fix is on clientstub side Revision 1.151 2005/01/11 21:06:29 ksekar Changed now-benign LogMsg to debugf Revision 1.150 2005/01/07 23:59:15 ksekar dnd-sd shows the wrong port numbers Revision 1.149 2004/12/20 23:20:35 cheshire mDNSResponder crashes repeatedly when printer sharing is enabled Make sure to call mDNS_SetupResourceRecord() for all newly created AuthRecords Revision 1.148 2004/12/20 20:37:35 cheshire AllowRemoteQuery not set for the extras in a ServiceRecordSet Revision 1.147 2004/12/20 00:15:41 cheshire Include client file descriptor numbers in udsserver_info() output Revision 1.146 2004/12/17 05:25:47 cheshire Shorten DNS-SD queries to avoid NAT bugs Revision 1.145 2004/12/16 21:39:46 cheshire Include CacheGroup objects in CacheUsed count Revision 1.144 2004/12/16 21:27:38 ksekar Fixed build failures when compiled with verbose debugging messages Revision 1.143 2004/12/16 20:13:02 cheshire Cache memory management improvements Revision 1.142 2004/12/16 08:07:33 shersche Fix compiler error (mixed declarations and code) on Windows Revision 1.141 2004/12/16 01:56:21 cheshire Improve DNSServiceEnumerateDomains syslog message Revision 1.140 2004/12/14 03:02:10 ksekar Rare race condition can cause crash Revision 1.139 2004/12/13 21:18:45 ksekar Include uDNS registrations in CountPeerRegistrations Revision 1.138 2004/12/13 18:23:18 ksekar mDNSResponder error when quitting iChat - don't close sockets delivering errors to blocked clients Revision 1.137 2004/12/13 00:09:22 ksekar mDNSResponder error when quitting iChat Revision 1.136 2004/12/11 01:52:10 cheshire Support kDNSServiceFlagsAllowRemoteQuery for registering services too Revision 1.135 2004/12/10 20:46:37 cheshire Change LogOperation message to debugf Revision 1.134 2004/12/10 13:19:37 cheshire Add verbosedebugf() logging message in CountPeerRegistrations() Revision 1.133 2004/12/10 05:27:26 cheshire Guard against multiple autoname services of the same type on the same machine Revision 1.132 2004/12/10 04:28:28 cheshire User not notified of name changes for services using new UDS API Revision 1.131 2004/12/10 02:09:25 cheshire Modify default TTLs Revision 1.130 2004/12/10 00:55:24 cheshire Add full name and type to LogOperation messages for DNSServiceAddRecord/UpdateRecord/RemoveRecord Revision 1.129 2004/12/09 03:17:23 ksekar DomainEnumeration interface index should be zero Revision 1.128 2004/12/07 21:26:05 ksekar DNSServiceRegisterRecord() can crash on deregistration Revision 1.127 2004/12/07 20:42:34 cheshire Add explicit context parameter to mDNS_RemoveRecordFromService() Revision 1.126 2004/12/07 17:23:55 ksekar Fixed LogOperation Revision 1.125 2004/12/06 21:15:23 ksekar mDNSResponder crashed in CheckServiceRegistrations Revision 1.124 2004/11/30 02:19:14 cheshire Raise maxfds.rlim_cur for mDNSResponder Revision 1.123 2004/11/29 23:50:57 cheshire Checkin 1.122 not necessary Revision 1.122 2004/11/24 17:55:01 ksekar Added log message clarifying For unicast operations, verify that service types are legal Revision 1.121 2004/11/24 04:45:52 cheshire Spelling mistake Revision 1.120 2004/11/24 00:10:44 cheshire For unicast operations, verify that service types are legal Revision 1.119 2004/11/23 23:54:17 ksekar Wide-Area DNSServiceRegisterRecord() failures can crash mDNSResponder Revision 1.118 2004/11/23 22:33:01 cheshire Remove temporary workaround code for iChat Revision 1.117 2004/11/23 20:23:10 ksekar Fixed LogOperation that causes crash on connected service deregistrations Revision 1.116 2004/11/23 03:39:47 cheshire Let interface name/index mapping capability live directly in JNISupport.c, instead of having to call through to the daemon via IPC to get this information. Revision 1.115 2004/11/13 00:12:53 ksekar Fixed some LogOperation printf converstions for debug builds. Revision 1.114 2004/11/12 18:25:45 shersche Tidy up cross platform usleep code fragment. Revision 1.113 2004/11/12 03:21:41 rpantos rdar://problem/3809541 Add DNSSDMapIfIndexToName, DNSSDMapNameToIfIndex. Revision 1.112 2004/11/11 16:58:32 ksekar Removed unused code (previously wrapped in #if 0) Revision 1.111 2004/11/05 22:47:37 shersche Conditionally compile usleep(1000) to be Sleep(1) on Windows Submitted by: Pavel Repin Revision 1.110 2004/11/05 19:56:56 ksekar We no longer need to browse .Mac domains by default - changed #if 0 to more descriptive #ifdef _HAVE_SETDOMAIN_SUPPORT_ Revision 1.109 2004/11/04 03:40:45 cheshire More debugging messages Revision 1.108 2004/11/03 02:25:51 cheshire Conflict for Computer Name should update *all* empty string services, not just the one with the conflict Revision 1.107 2004/11/02 19:39:23 ksekar We no longer need to browse .Mac domains by default Revision 1.106 2004/11/02 02:12:21 cheshire Remove unnecessary memory allocations Revision 1.105 2004/10/28 19:07:19 cheshire Add some more debugging checks and improved LogOperation() messages Revision 1.104 2004/10/26 18:53:15 cheshire Avoid unused variable warning Revision 1.103 2004/10/26 07:15:55 cheshire Add file descriptor number to all LogOperation messages Revision 1.102 2004/10/26 06:11:42 cheshire Add improved logging to aid in diagnosis of mDNSResponder crashed Revision 1.101 2004/10/26 04:31:44 cheshire Rename CountSubTypes() as ChopSubTypes() Revision 1.100 2004/10/26 01:17:48 cheshire Use "#if 0" instead of commenting out code Revision 1.99 2004/10/19 21:33:22 cheshire Cannot resolve non-local registrations using the mach API Added flag 'kDNSServiceFlagsForceMulticast'. Passing through an interface id for a unicast name doesn't force multicast unless you set this flag to indicate explicitly that this is what you want Revision 1.98 2004/10/14 01:59:33 cheshire UDS resolves don't work for uDNS services Revision 1.97 2004/10/13 00:58:35 cheshire Registering a proxy doesn't work Revision 1.96 2004/09/30 00:25:00 ksekar Dynamically update default registration domains on config change Revision 1.95 2004/09/26 23:20:36 ksekar Allow default registrations in multiple wide-area domains Revision 1.94 2004/09/22 18:27:06 ksekar allow DNSServiceAddRecord to pass zero to get default record TTL Revision 1.93 2004/09/22 02:39:44 cheshire Allow DNSServiceRegisterRecord to pass zero to get default record TTL Revision 1.92 2004/09/22 02:34:04 cheshire Rename parameter "ttl" to "GetTTL" for clarity Revision 1.91 2004/09/22 02:25:43 cheshire Fix spelling errors Revision 1.90 2004/09/21 23:40:12 ksekar mDNSResponder to return errors on NAT traversal failure Revision 1.89 2004/09/21 23:29:51 cheshire DNSServiceResolve should delay sending packets Revision 1.88 2004/09/21 23:12:46 cheshire Reorder initialization of question fields to match structure order Revision 1.87 2004/09/21 22:18:33 cheshire In SIGINFO output, display a '-' next to records that have the Unique bit set Revision 1.86 2004/09/21 21:05:11 cheshire Move duplicate code out of mDNSMacOSX/daemon.c and mDNSPosix/PosixDaemon.c, into mDNSShared/uds_daemon.c Revision 1.85 2004/09/18 01:11:58 ksekar Add a user's default domain to empty-string browse list Revision 1.84 2004/09/17 01:08:55 cheshire Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces declared in that file are ONLY appropriate to single-address-space embedded applications. For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used. Revision 1.83 2004/09/16 23:26:33 cheshire Move version check inside preceeding "if" that checks we have a complete header Revision 1.82 2004/09/16 23:14:25 cheshire Changes for Windows compatibility Revision 1.81 2004/09/16 21:46:38 ksekar Need SPI for LoginWindow to associate a UID with a Wide Area Rendezvous domain Revision 1.80 2004/09/16 01:58:23 cheshire Fix compiler warnings Revision 1.79 2004/09/16 00:24:49 cheshire Fix unsafe use of mDNSPlatformTimeNow() Revision 1.78 2004/09/15 21:44:20 cheshire Randomize initial timenow_adjust value in mDNS_Init Show time value in log to help diagnose errors Revision 1.77 2004/09/15 00:19:18 cheshire read_rr_from_ipc_msg should use mDNS_SetupResourceRecord() Revision 1.76 2004/09/02 06:39:52 cheshire Minor textual cleanup for clarity Revision 1.75 2004/09/02 03:48:47 cheshire Disable targeted unicast query support by default 1. New flag kDNSServiceFlagsAllowRemoteQuery to indicate we want to allow remote queries for this record 2. New field AllowRemoteQuery in AuthRecord structure 3. uds_daemon.c sets AllowRemoteQuery if kDNSServiceFlagsAllowRemoteQuery is set 4. mDNS.c only answers remote queries if AllowRemoteQuery is set Revision 1.74 2004/08/25 02:32:47 cheshire Minor cleanup: replace "®type[0]" with "regtype" Revision 1.73 2004/08/25 02:30:40 cheshire Current method of doing subtypes causes name collisions Revision 1.72 2004/08/14 03:22:42 cheshire Dynamic DNS UI <-> mDNSResponder glue Add GetUserSpecifiedDDNSName() routine Convert ServiceRegDomain to domainname instead of C string Replace mDNS_GenerateFQDN/mDNS_GenerateGlobalFQDN with mDNS_SetFQDNs Revision 1.71 2004/08/11 04:21:21 rpantos Fix Windows build. Revision 1.70 2004/08/11 02:07:00 cheshire Remove "mDNS *globalInstance" parameter from udsserver_init() Move CheckForDuplicateRegistrations from daemon.c No warning when accidentally registering the same service multiple times using socket API Revision 1.69 2004/08/10 16:14:48 cheshire Fix debug builds (oops) Revision 1.68 2004/08/10 06:24:56 cheshire Use types with precisely defined sizes for 'op' and 'reg_index', for better compatibility if the daemon and the client stub are built using different compilers Revision 1.67 2004/07/27 07:14:16 shersche make error socket non-blocking after call to connect() Revision 1.66 2004/07/13 21:24:25 rpantos Fix for . Revision 1.65 2004/06/26 03:17:14 shersche implement cross-platform strerror function Submitted by: herscher Revision 1.64 2004/06/25 00:26:27 rpantos Changes to fix the Posix build on Solaris. Revision 1.63 2004/06/24 03:43:44 rpantos Fix previous checkin so it builds on Windows. Revision 1.62 2004/06/24 00:57:08 ksekar Replaced code acccidentally removed in checkin 1.59. Revision 1.61 2004/06/19 00:09:39 cheshire Remove unused strsep() implementation Revision 1.60 2004/06/18 19:10:00 cheshire Current method of doing subtypes causes name collisions Revision 1.59 2004/06/18 05:10:31 rpantos Changes to allow code to be used on Windows Revision 1.58 2004/06/15 03:54:08 cheshire Include mDNS_TimeNow(&mDNSStorage) in SIGINFO output Revision 1.57 2004/06/12 01:47:27 ksekar : BBEdit crashes when trying to check for newer version udsserver_idle compared time in ticks to interval in seconds. Revision 1.56 2004/06/12 01:35:47 cheshire Changes for Windows compatibility Revision 1.55 2004/06/05 00:04:27 cheshire : wide-area domains should be returned in reg. domain enumeration Revision 1.54 2004/06/01 22:22:52 ksekar : wide-area default registrations should be in .local too Revision 1.53 2004/05/28 23:42:37 ksekar : Feature: DNS server->client notification on record changes (#7805) Revision 1.52 2004/05/26 00:39:49 ksekar : wide-area DNS-SD servers don't appear in Finder Use local-only InterfaceID for GetDomains calls for sockets-API Revision 1.51 2004/05/18 23:51:27 cheshire Tidy up all checkin comments to use consistent "" format for bug numbers Revision 1.50 2004/05/14 16:39:47 ksekar Browse for iChat locally for now. Revision 1.49 2004/05/13 21:33:52 ksekar Clean up non-local registration control via config file. Force iChat registrations to be local for now. Revision 1.48 2004/05/13 04:13:19 ksekar Updated SIGINFO handler for multi-domain browses Revision 1.47 2004/05/12 22:04:01 ksekar Implemented multi-domain browsing by default for uds_daemon. Revision 1.46 2004/05/06 18:42:58 ksekar General dns_sd.h API cleanup, including the following radars: : Remove flags with zero value : Passing in NULL causes a crash. Revision 1.45 2004/03/12 08:49:28 cheshire #include Revision 1.44 2004/02/25 01:25:27 ksekar : DNSServiceRegisterRecord flags not error-checked Revision 1.43 2004/02/24 01:46:40 cheshire Manually reinstate lost checkin 1.36 Revision 1.42 2004/02/05 19:39:29 cheshire Move creation of /var/run/mDNSResponder.pid to uds_daemon.c, so that all platforms get this functionality Revision 1.41 2004/02/03 18:59:02 cheshire Change "char *domain" parameter for format_enumeration_reply to "const char *domain" Revision 1.40 2004/01/28 03:41:00 cheshire : Need ability to do targeted queries as well as multicast queries Revision 1.39 2004/01/25 00:03:21 cheshire Change to use mDNSVal16() instead of private PORT_AS_NUM() macro Revision 1.38 2004/01/19 19:51:46 cheshire Fix compiler error (mixed declarations and code) on some versions of Linux Revision 1.37 2003/12/08 21:11:42 rpantos Changes necessary to support mDNSResponder on Linux. Revision 1.36 2003/12/04 23:40:57 cheshire : Security: Crashing bug in mDNSResponder Fix some more code that should use buffer size MAX_ESCAPED_DOMAIN_NAME (1005) instead of 256-byte buffers. Revision 1.35 2003/12/03 19:10:22 ksekar : malloc'd data not zero'd Revision 1.34 2003/12/03 02:00:01 ksekar : malloc'd data not zero'd Revision 1.33 2003/11/22 01:18:46 ksekar : config change handler not called for dns-sd services Revision 1.32 2003/11/20 21:46:12 ksekar : leak: DNSServiceRegisterRecord Revision 1.31 2003/11/20 20:33:05 ksekar : leak: DNSServiceRegisterRecord Revision 1.30 2003/11/20 02:10:55 ksekar : cleanup DNSServiceAdd/RemoveRecord Revision 1.29 2003/11/14 21:18:32 cheshire : Security: Crashing bug in mDNSResponder Fix code that should use buffer size MAX_ESCAPED_DOMAIN_NAME (1005) instead of 256-byte buffers. Revision 1.28 2003/11/08 22:18:29 cheshire : Don't need to show process ID in *every* mDNSResponder syslog message Revision 1.27 2003/11/05 22:44:57 ksekar : No bounds checking when reading data from client Reviewed by: Stuart Cheshire Revision 1.26 2003/10/23 17:51:04 ksekar : handle blocked clients more efficiently Changed gettimeofday() to mDNS_TimeNow(&mDNSStorage); Revision 1.25 2003/10/22 23:37:49 ksekar : crash/hang in abort_client Revision 1.24 2003/10/21 20:59:40 ksekar : handle blocked clients moreefficiently Revision 1.23 2003/09/23 02:12:43 cheshire Also include port number in list of services registered via new UDS API Revision 1.22 2003/08/19 16:03:55 ksekar : ER: SIGINFO dump should include resolves started by DNSServiceQueryRecord Check termination_context for NULL before dereferencing. Revision 1.21 2003/08/19 05:39:43 cheshire SIGINFO dump should include resolves started by DNSServiceQueryRecord Revision 1.20 2003/08/16 03:39:01 cheshire InterfaceID -1 indicates "local only" Revision 1.19 2003/08/15 20:16:03 cheshire mDNSResponder takes too much RPRVT We want to avoid touching the rdata pages, so we don't page them in. 1. RDLength was stored with the rdata, which meant touching the page just to find the length. Moved this from the RData to the ResourceRecord object. 2. To avoid unnecessarily touching the rdata just to compare it, compute a hash of the rdata and store the hash in the ResourceRecord object. Revision 1.18 2003/08/15 00:38:00 ksekar : Bug: buffer overrun when reading long rdata from client Revision 1.17 2003/08/14 02:18:21 cheshire Split generic ResourceRecord type into two separate types: AuthRecord and CacheRecord Revision 1.16 2003/08/13 23:58:52 ksekar : Bug: UDS Sub-type browsing works, but not sub-type registration Fixed pointer increment error, moved subtype reading for-loop for easier error bailout. Revision 1.15 2003/08/13 17:30:33 ksekar : DNSServiceAddRecord doesn't work Fixed various problems with handling the AddRecord request and freeing the ExtraResourceRecords. Revision 1.14 2003/08/12 19:56:25 cheshire Update to APSL 2.0 */ #if defined(_WIN32) #include #define dnssd_strerror(X) win32_strerror(X) #define usleep(X) Sleep(((X)+999)/1000) static char * win32_strerror(int inErrorCode); #else #include #include #include #include #include #include #define dnssd_strerror(X) strerror(X) #endif #include #include #include "mDNSEmbeddedAPI.h" #include "DNSCommon.h" #include "uds_daemon.h" #include "dns_sd.h" #include "dnssd_ipc.h" // Apple specific configuration functionality, not required for other platforms #ifdef __MACOSX__ #include #ifndef LOCAL_PEERCRED #define LOCAL_PEERCRED 0x001 /* retrieve peer credentials */ #endif // LOCAL_PEERCRED #endif //__MACOSX__ // Types and Data Structures // ---------------------------------------------------------------------- typedef enum { t_uninitialized, t_morecoming, t_complete, t_error, t_terminated } transfer_state; typedef void (*req_termination_fn)(void *); typedef struct registered_record_entry { uint32_t key; AuthRecord *rr; struct registered_record_entry *next; client_context_t client_context; struct request_state *rstate; } registered_record_entry; // A single registered service: ServiceRecordSet + bookkeeping // Note that we duplicate some fields from parent service_info object // to facilitate cleanup, when instances and parent may be deallocated at different times. typedef struct service_instance { struct service_instance *next; mDNSBool autoname; // Set if this name is tied to the Computer Name mDNSBool autorename; // Set if this client wants us to automatically rename on conflict mDNSBool allowremotequery; // Respond to unicast queries from outside the local link? mDNSBool rename_on_memfree; // Set on config change when we deregister original name domainlabel name; domainname domain; mDNSBool default_local; // is this the "local." from an empty-string registration? struct request_state *request; int sd; AuthRecord *subtypes; ServiceRecordSet srs; // note - must be last field in struct } service_instance; // A client-created service. May reference several service_info objects if default // settings cause registration in multiple domains. typedef struct { uint16_t txtlen; void *txtdata; mDNSIPPort port; domainlabel name; char type_as_string[MAX_ESCAPED_DOMAIN_NAME]; domainname type; mDNSBool default_domain; domainname host; mDNSBool autoname; // Set if this name is tied to the Computer Name mDNSBool autorename; // Set if this client wants us to automatically rename on conflict mDNSBool allowremotequery; // Respond to unicast queries from outside the local link? int num_subtypes; mDNSInterfaceID InterfaceID; service_instance *instances; struct request_state *request; } service_info; // for multi-domain default browsing typedef struct browser_t { DNSQuestion q; domainname domain; struct browser_t *next; } browser_t; // parent struct for browser instances: list pointer plus metadata typedef struct { mDNSBool default_domain; mDNSBool ForceMCast; domainname regtype; mDNSInterfaceID interface_id; struct request_state *rstate; browser_t *browsers; } browser_info_t; typedef struct { mStatus err; // Note: This field is in NETWORK byte order int nwritten; dnssd_sock_t sd; } undelivered_error_t; typedef struct request_state { // connection structures dnssd_sock_t sd; // state of read (in case message is read over several recv() calls) transfer_state ts; uint32_t hdr_bytes; // bytes of header already read ipc_msg_hdr hdr; uint32_t data_bytes; // bytes of message data already read char *msgbuf; // pointer to data storage to pass to free() char *msgdata; // pointer to data to be read from (may be modified) int bufsize; // size of data storage // reply, termination, error, and client context info int no_reply; // don't send asynchronous replies to client int time_blocked; // record time of a blocked client void *client_context; // don't touch this - pointer only valid in client's addr space struct reply_state *replies; // corresponding (active) reply list undelivered_error_t *u_err; void *termination_context; req_termination_fn terminate; //!!!KRS toss these pointers in a union // registration context associated with this request (null if not applicable) registered_record_entry *reg_recs; // muliple registrations for a connection-oriented request service_info *service_registration; browser_info_t *browser_info; struct request_state *next; } request_state; // struct physically sits between ipc message header and call-specific fields in the message buffer typedef struct { DNSServiceFlags flags; // Note: This field is in NETWORK byte order uint32_t ifi; // Note: This field is in NETWORK byte order DNSServiceErrorType error; // Note: This field is in NETWORK byte order } reply_hdr; typedef struct reply_state { // state of the transmission dnssd_sock_t sd; transfer_state ts; uint32_t nwriten; uint32_t len; // context of the reply struct request_state *request; // the request that this answers struct reply_state *next; // if there are multiple unsent replies // pointer into message buffer - allows fields to be changed after message is formatted ipc_msg_hdr *mhdr; reply_hdr *rhdr; char *sdata; // pointer to start of call-specific data // pointer to malloc'd buffer char *msgbuf; } reply_state; // domain enumeration and resolv calls require 2 mDNSCore calls, so we need separate interconnected // structures to handle callbacks typedef struct { DNSQuestion question; mDNS_DomainType type; request_state *rstate; } domain_enum_t; typedef struct { domain_enum_t *all; domain_enum_t *def; request_state *rstate; } enum_termination_t; typedef struct { request_state *rstate; DNSQuestion qtxt; DNSQuestion qsrv; // const ResourceRecord *txt; // const ResourceRecord *srv; mDNSBool srv; mDNSBool txt; domainname target; mDNSIPPort port; mDNSu16 txtlen; mDNSu8 txtdata[AbsoluteMaxDNSMessageData]; } resolve_termination_t; #ifdef _HAVE_SETDOMAIN_SUPPORT_ typedef struct default_browse_list_t { struct default_browse_list_t *next; uid_t uid; AuthRecord ptr_rec; } default_browse_list_t; static default_browse_list_t *default_browse_list = NULL; #endif // _HAVE_SETDOMAIN_SUPPORT_ // globals mDNSexport mDNS mDNSStorage; #define gmDNS (&mDNSStorage) static dnssd_sock_t listenfd = dnssd_InvalidSocket; static request_state * all_requests = NULL; #define MAX_TIME_BLOCKED 60 * mDNSPlatformOneSecond // try to send data to a blocked client for 60 seconds before // terminating connection #define MSG_PAD_BYTES 5 // pad message buffer (read from client) with n zero'd bytes to guarantee // n get_string() calls w/o buffer overrun // private function prototypes static void connect_callback(void *info); static int read_msg(request_state *rs); static int send_msg(reply_state *rs); static void abort_request(request_state *rs); static void request_callback(void *info); static void handle_resolve_request(request_state *rstate); static void question_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord); static void question_termination_callback(void *context); static void handle_browse_request(request_state *request); static void browse_termination_callback(void *context); static void browse_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord); static void handle_regservice_request(request_state *request); static void regservice_termination_callback(void *context); static void process_service_registration(ServiceRecordSet *const srs); static void regservice_callback(mDNS *const m, ServiceRecordSet *const srs, mStatus result); static mStatus handle_add_request(request_state *rstate); static mStatus handle_update_request(request_state *rstate); static mStatus gen_rr_response(domainname *servicename, mDNSInterfaceID id, request_state *request, reply_state **rep); static void append_reply(request_state *req, reply_state *rep); static int build_domainname_from_strings(domainname *srv, char *name, char *regtype, char *domain); static void enum_termination_callback(void *context); static void enum_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord); static void handle_query_request(request_state *rstate); static reply_state *format_enumeration_reply(request_state *rstate, const char *domain, DNSServiceFlags flags, uint32_t ifi, DNSServiceErrorType err); static void handle_enum_request(request_state *rstate); static mStatus handle_regrecord_request(request_state *rstate); static void regrecord_callback(mDNS *const m, AuthRecord *const rr, mStatus result); static void connected_registration_termination(void *context); static void handle_reconfirm_request(request_state *rstate); static AuthRecord *read_rr_from_ipc_msg(char *msgbuf, int ttl, int validate_flags); static mStatus handle_removerecord_request(request_state *rstate); static void reset_connected_rstate(request_state *rstate); static int deliver_error(request_state *rstate, mStatus err); static int deliver_async_error(request_state *rs, reply_op_t op, mStatus err); static transfer_state send_undelivered_error(request_state *rs); static reply_state *create_reply(reply_op_t op, size_t datalen, request_state *request); static void update_callback(mDNS *const m, AuthRecord *const rr, RData *oldrd); static void my_perror(char *errmsg); static void unlink_request(request_state *rs); static void resolve_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord); static void resolve_termination_callback(void *context); static int validate_message(request_state *rstate); static mStatus remove_extra(request_state *rstate, service_instance *serv); static mStatus remove_record(request_state *rstate); static void free_service_instance(service_instance *srv); static uint32_t dnssd_htonl(uint32_t l); static void handle_setdomain_request(request_state *rstate); // initialization, setup/teardown functions // If a platform specifies its own PID file name, we use that #ifndef PID_FILE #define PID_FILE "/var/run/mDNSResponder.pid" #endif static void FatalError(char *errmsg) { LogMsg("%s: %s", errmsg, dnssd_strerror(dnssd_errno())); *(long*)0 = 0; // On OS X abort() doesn't generate a crash log, but writing to zero does abort(); // On platforms where writing to zero doesn't generate an exception, abort instead } int udsserver_init(void) { dnssd_sockaddr_t laddr; int ret; #if defined(_WIN32) u_long opt = 1; #endif // If a particular platform wants to opt out of having a PID file, define PID_FILE to be "" if (PID_FILE[0]) { FILE *fp = fopen(PID_FILE, "w"); if (fp != NULL) { fprintf(fp, "%d\n", getpid()); fclose(fp); } } if ((listenfd = socket(AF_DNSSD, SOCK_STREAM, 0)) == dnssd_InvalidSocket) goto error; bzero(&laddr, sizeof(laddr)); #if defined(USE_TCP_LOOPBACK) { laddr.sin_family = AF_INET; laddr.sin_port = htons(MDNS_TCP_SERVERPORT); laddr.sin_addr.s_addr = inet_addr(MDNS_TCP_SERVERADDR); ret = bind(listenfd, (struct sockaddr *) &laddr, sizeof(laddr)); if (ret < 0) goto error; } #else { mode_t mask = umask(0); unlink(MDNS_UDS_SERVERPATH); //OK if this fails laddr.sun_family = AF_LOCAL; #ifndef NOT_HAVE_SA_LEN // According to Stevens (section 3.2), there is no portable way to // determine whether sa_len is defined on a particular platform. laddr.sun_len = sizeof(struct sockaddr_un); #endif strcpy(laddr.sun_path, MDNS_UDS_SERVERPATH); ret = bind(listenfd, (struct sockaddr *) &laddr, sizeof(laddr)); umask(mask); if (ret < 0) goto error; } #endif #if defined(_WIN32) // // SEH: do we even need to do this on windows? this socket // will be given to WSAEventSelect which will automatically // set it to non-blocking // if (ioctlsocket(listenfd, FIONBIO, &opt) != 0) #else if (fcntl(listenfd, F_SETFL, O_NONBLOCK) != 0) #endif { my_perror("ERROR: could not set listen socket to non-blocking mode"); goto error; } if (listen(listenfd, LISTENQ) != 0) { my_perror("ERROR: could not listen on listen socket"); goto error; } if (mStatus_NoError != udsSupportAddFDToEventLoop(listenfd, connect_callback, (void *) NULL)) { my_perror("ERROR: could not add listen socket to event loop"); goto error; } #if !defined(PLATFORM_NO_RLIMIT) { // Set maximum number of open file descriptors #define MIN_OPENFILES 10240 struct rlimit maxfds, newfds; // Due to bugs in OS X (, , ) // you have to get and set rlimits once before getrlimit will return sensible values if (getrlimit(RLIMIT_NOFILE, &maxfds) < 0) { my_perror("ERROR: Unable to get file descriptor limit"); return 0; } if (setrlimit(RLIMIT_NOFILE, &maxfds) < 0) my_perror("ERROR: Unable to set maximum file descriptor limit"); if (getrlimit(RLIMIT_NOFILE, &maxfds) < 0) { my_perror("ERROR: Unable to get file descriptor limit"); return 0; } newfds.rlim_max = (maxfds.rlim_max > MIN_OPENFILES) ? maxfds.rlim_max : MIN_OPENFILES; newfds.rlim_cur = (maxfds.rlim_cur > MIN_OPENFILES) ? maxfds.rlim_cur : MIN_OPENFILES; if (newfds.rlim_max != maxfds.rlim_max || newfds.rlim_cur != maxfds.rlim_cur) if (setrlimit(RLIMIT_NOFILE, &newfds) < 0) my_perror("ERROR: Unable to set maximum file descriptor limit"); if (getrlimit(RLIMIT_NOFILE, &maxfds) < 0) { my_perror("ERROR: Unable to get file descriptor limit"); return 0; } debugf("maxfds.rlim_max %d", (long)maxfds.rlim_max); debugf("maxfds.rlim_cur %d", (long)maxfds.rlim_cur); } #endif return 0; error: my_perror("ERROR: udsserver_init"); return -1; } int udsserver_exit(void) { dnssd_close(listenfd); #if !defined(USE_TCP_LOOPBACK) unlink(MDNS_UDS_SERVERPATH); #endif return 0; } mDNSs32 udsserver_idle(mDNSs32 nextevent) { request_state *req = all_requests, *tmp, *prev = NULL; reply_state *fptr; transfer_state result; mDNSs32 now = mDNS_TimeNow(&mDNSStorage); while(req) { result = t_uninitialized; if (req->u_err) result = send_undelivered_error(req); if (result != t_error && result != t_morecoming && // don't try to send msg if send_error failed (req->ts == t_complete || req->ts == t_morecoming)) { while(req->replies) { if (req->replies->next) req->replies->rhdr->flags |= dnssd_htonl(kDNSServiceFlagsMoreComing); result = send_msg(req->replies); if (result == t_complete) { fptr = req->replies; req->replies = req->replies->next; freeL("udsserver_idle", fptr); req->time_blocked = 0; // reset failure counter after successful send } else if (result == t_terminated || result == t_error) { abort_request(req); break; } else if (result == t_morecoming) break; // client's queues are full, move to next } } if (result == t_morecoming) { if (!req->time_blocked) req->time_blocked = now; debugf("udsserver_idle: client has been blocked for %ld seconds", (now - req->time_blocked) / mDNSPlatformOneSecond); if (now - req->time_blocked >= MAX_TIME_BLOCKED) { LogMsg("Could not write data to client after %ld seconds - aborting connection", MAX_TIME_BLOCKED / mDNSPlatformOneSecond); abort_request(req); result = t_terminated; } else if (nextevent - now > mDNSPlatformOneSecond) nextevent = now + mDNSPlatformOneSecond; // try again in a second } if (result == t_terminated || result == t_error) //since we're already doing a list traversal, we unlink the request manunally instead of calling unlink_request() { tmp = req; if (prev) prev->next = req->next; if (req == all_requests) all_requests = all_requests->next; req = req->next; freeL("udsserver_idle", tmp); } else { prev = req; req = req->next; } } return nextevent; } void udsserver_info(mDNS *const m) { mDNSs32 now = mDNS_TimeNow(m); mDNSu32 CacheUsed = 0, CacheActive = 0; mDNSu32 slot; CacheGroup *cg; CacheRecord *rr; request_state *req; LogMsgNoIdent("Timenow 0x%08lX (%ld)", (mDNSu32)now, now); for (slot = 0; slot < CACHE_HASH_SLOTS; slot++) for(cg = m->rrcache_hash[slot]; cg; cg=cg->next) { CacheUsed++; // Count one cache entity for the CacheGroup object for (rr = cg->members; rr; rr=rr->next) { mDNSs32 remain = rr->resrec.rroriginalttl - (now - rr->TimeRcvd) / mDNSPlatformOneSecond; CacheUsed++; if (rr->CRActiveQuestion) CacheActive++; LogMsgNoIdent("%s%6ld %s%-6s%-6s%s", rr->CRActiveQuestion ? "*" : " ", remain, (rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask) ? "-" : " ", DNSTypeName(rr->resrec.rrtype), ((NetworkInterfaceInfo *)rr->resrec.InterfaceID)->ifname, CRDisplayString(m, rr)); usleep(1000); // Limit rate a little so we don't flood syslog too fast } } if (m->rrcache_totalused != CacheUsed) LogMsgNoIdent("Cache use mismatch: rrcache_totalused is %lu, true count %lu", m->rrcache_totalused, CacheUsed); if (m->rrcache_active != CacheActive) LogMsgNoIdent("Cache use mismatch: rrcache_active is %lu, true count %lu", m->rrcache_active, CacheActive); LogMsgNoIdent("Cache currently contains %lu records; %lu referenced by active questions", CacheUsed, CacheActive); for (req = all_requests; req; req=req->next) { void *t = req->termination_context; if (!t) continue; if (req->terminate == regservice_termination_callback) { service_instance *ptr; for (ptr = ((service_info *)t)->instances; ptr; ptr = ptr->next) LogMsgNoIdent("%3d: DNSServiceRegister %##s %u", req->sd, ptr->srs.RR_SRV.resrec.name->c, SRS_PORT(&ptr->srs)); } else if (req->terminate == browse_termination_callback) { browser_t *blist; for (blist = req->browser_info->browsers; blist; blist = blist->next) LogMsgNoIdent("%3d: DNSServiceBrowse %##s", req->sd, blist->q.qname.c); } else if (req->terminate == resolve_termination_callback) LogMsgNoIdent("%3d: DNSServiceResolve %##s", req->sd, ((resolve_termination_t *)t)->qsrv.qname.c); else if (req->terminate == question_termination_callback) LogMsgNoIdent("%3d: DNSServiceQueryRecord %##s", req->sd, ((DNSQuestion *) t)->qname.c); else if (req->terminate == enum_termination_callback) LogMsgNoIdent("%3d: DNSServiceEnumerateDomains %##s", req->sd, ((enum_termination_t *) t)->all->question.qname.c); } now = mDNS_TimeNow(m); LogMsgNoIdent("Timenow 0x%08lX (%ld)", (mDNSu32)now, now); } static void rename_service(service_instance *srv) { if (srv->autoname && !SameDomainLabel(srv->name.c, gmDNS->nicelabel.c)) { srv->rename_on_memfree = 1; if (mDNS_DeregisterService(gmDNS, &srv->srs)) // If service deregistered already, we can re-register immediately regservice_callback(gmDNS, &srv->srs, mStatus_MemFree); } } void udsserver_handle_configchange(void) { request_state *req; for (req = all_requests; req; req = req->next) { if (req->service_registration) { service_instance *ptr; for (ptr = req->service_registration->instances; ptr; ptr = ptr->next) rename_service(ptr); } } } static void connect_callback(void *info) { dnssd_sock_t sd; unsigned int len; unsigned long optval; dnssd_sockaddr_t cliaddr; request_state *rstate; (void)info; // Unused len = (int) sizeof(cliaddr); sd = accept(listenfd, (struct sockaddr*) &cliaddr, &len); if (sd == dnssd_InvalidSocket) { if (dnssd_errno() == dnssd_EWOULDBLOCK) return; my_perror("ERROR: accept"); return; } optval = 1; #ifdef SO_NOSIGPIPE // Some environments (e.g. OS X) support turning off SIGPIPE for a socket if (setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)) < 0) { my_perror("ERROR: setsockopt - SO_NOSIGPIPE - aborting client"); dnssd_close(sd); return; } #endif #if defined(_WIN32) if (ioctlsocket(sd, FIONBIO, &optval) != 0) #else if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0) #endif { my_perror("ERROR: fcntl(sd, F_SETFL, O_NONBLOCK) - aborting client"); dnssd_close(sd); return; } // allocate a request_state struct that will live with the socket rstate = mallocL("connect_callback", sizeof(request_state)); if (!rstate) FatalError("ERROR: malloc"); bzero(rstate, sizeof(request_state)); rstate->ts = t_morecoming; rstate->sd = sd; LogOperation("%3d: Adding FD", rstate->sd); if ( mStatus_NoError != udsSupportAddFDToEventLoop( sd, request_callback, rstate)) return; rstate->next = all_requests; all_requests = rstate; } // handler static void request_callback(void *info) { request_state *rstate = info; transfer_state result; dnssd_sockaddr_t cliaddr; int dedicated_error_socket; #if defined(_WIN32) u_long opt = 1; #endif result = read_msg(rstate); if (result == t_morecoming) { return; } if (result == t_terminated) { abort_request(rstate); unlink_request(rstate); return; } if (result == t_error) { abort_request(rstate); unlink_request(rstate); return; } if (rstate->hdr.version != VERSION) { LogMsg("ERROR: client incompatible with daemon (client version = %d, " "daemon version = %d)\n", rstate->hdr.version, VERSION); abort_request(rstate); unlink_request(rstate); return; } if (validate_message(rstate) < 0) { // note that we cannot deliver an error message if validation fails, since the path to the error socket // may be contained in the (invalid) message body for some message types abort_request(rstate); unlink_request(rstate); LogMsg("Invalid message sent by client - may indicate a malicious program running on this machine!"); return; } // check if client wants silent operation if (rstate->hdr.flags & IPC_FLAGS_NOREPLY) rstate->no_reply = 1; dedicated_error_socket = (rstate->hdr.op == reg_record_request || rstate->hdr.op == add_record_request || rstate->hdr.op == update_record_request || rstate->hdr.op == remove_record_request); if (((rstate->hdr.flags & IPC_FLAGS_REUSE_SOCKET) == 0) != dedicated_error_socket) LogMsg("WARNING: client request %d with incorrect flags setting 0x%X", rstate->hdr.op, rstate->hdr.flags); // check if primary socket is to be used for synchronous errors, else open new socket if (dedicated_error_socket) { mStatus err = 0; int nwritten; int errfd = socket(AF_DNSSD, SOCK_STREAM, 0); if (errfd == dnssd_InvalidSocket) { my_perror("ERROR: socket"); abort_request(rstate); unlink_request(rstate); return; } #if defined(USE_TCP_LOOPBACK) { mDNSOpaque16 port; port.b[0] = rstate->msgdata[0]; port.b[1] = rstate->msgdata[1]; rstate->msgdata += 2; cliaddr.sin_family = AF_INET; cliaddr.sin_port = port.NotAnInteger; cliaddr.sin_addr.s_addr = inet_addr(MDNS_TCP_SERVERADDR); } #else { char ctrl_path[MAX_CTLPATH]; get_string(&rstate->msgdata, ctrl_path, 256); // path is first element in message buffer bzero(&cliaddr, sizeof(cliaddr)); cliaddr.sun_family = AF_LOCAL; strcpy(cliaddr.sun_path, ctrl_path); } #endif if (connect(errfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr)) < 0) { my_perror("ERROR: connect"); abort_request(rstate); unlink_request(rstate); return; } #if defined(_WIN32) if (ioctlsocket(errfd, FIONBIO, &opt) != 0) #else if (fcntl(errfd, F_SETFL, O_NONBLOCK) != 0) #endif { my_perror("ERROR: could not set control socket to non-blocking mode"); abort_request(rstate); unlink_request(rstate); return; } switch(rstate->hdr.op) { case reg_record_request: err = handle_regrecord_request (rstate); break; case add_record_request: err = handle_add_request (rstate); break; case update_record_request: err = handle_update_request (rstate); break; case remove_record_request: err = handle_removerecord_request(rstate); break; default: LogMsg("%3d: ERROR: udsserver_recv_request - unsupported request type: %d", rstate->sd, rstate->hdr.op); } err = dnssd_htonl(err); nwritten = send(errfd, &err, sizeof(err), 0); // On a freshly-created Unix Domain Socket, the kernel should *never* fail to buffer a four-byte write for us. // If not, we don't attempt to handle this failure, but we do log it. if (nwritten < (int)sizeof(err)) LogMsg("ERROR: failed to write error response back to caller: %d %d %s", nwritten, dnssd_errno(), dnssd_strerror(dnssd_errno())); dnssd_close(errfd); reset_connected_rstate(rstate); // Reset ready to accept the next request on this pipe } else { switch(rstate->hdr.op) { case resolve_request: handle_resolve_request (rstate); break; case query_request: handle_query_request (rstate); break; case browse_request: handle_browse_request (rstate); break; case reg_service_request: handle_regservice_request(rstate); break; case enumeration_request: handle_enum_request (rstate); break; case reconfirm_record_request: handle_reconfirm_request (rstate); break; case setdomain_request: handle_setdomain_request (rstate); break; default: LogMsg("%3d: ERROR: udsserver_recv_request - unsupported request type: %d", rstate->sd, rstate->hdr.op); } } } // mDNS operation functions. Each operation has 3 associated functions - a request handler that parses // the client's request and makes the appropriate mDNSCore call, a result handler (passed as a callback // to the mDNSCore routine) that sends results back to the client, and a termination routine that aborts // the mDNSCore operation if the client dies or closes its socket. // query and resolve calls have separate request handlers that parse the arguments from the client and // massage the name parameters appropriately, but the rest of the operations (making the query call, // delivering the result to the client, and termination) are identical. static void handle_query_request(request_state *rstate) { DNSServiceFlags flags; uint32_t ifi; char name[256]; uint16_t rrtype, rrclass; char *ptr; mStatus result; mDNSInterfaceID InterfaceID; DNSQuestion *q; if (rstate->ts != t_complete) { LogMsg("ERROR: handle_query_request - transfer state != t_complete"); goto error; } ptr = rstate->msgdata; if (!ptr) { LogMsg("ERROR: handle_query_request - NULL msgdata"); goto error; } flags = get_flags(&ptr); ifi = get_long(&ptr); if (get_string(&ptr, name, 256) < 0) goto bad_param; rrtype = get_short(&ptr); rrclass = get_short(&ptr); InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(gmDNS, ifi); if (ifi && !InterfaceID) goto bad_param; q = mallocL("DNSQuestion", sizeof(DNSQuestion)); if (!q) FatalError("ERROR: handle_query - malloc"); bzero(q, sizeof(DNSQuestion)); q->InterfaceID = InterfaceID; q->Target = zeroAddr; if (!MakeDomainNameFromDNSNameString(&q->qname, name)) { freeL("DNSQuestion", q); goto bad_param; } q->qtype = rrtype; q->qclass = rrclass; q->LongLived = (flags & kDNSServiceFlagsLongLivedQuery) != 0; q->ExpectUnique = mDNSfalse; q->ForceMCast = (flags & kDNSServiceFlagsForceMulticast) != 0; q->QuestionCallback = question_result_callback; q->QuestionContext = rstate; rstate->termination_context = q; rstate->terminate = question_termination_callback; LogOperation("%3d: DNSServiceQueryRecord(%##s, %s) START", rstate->sd, q->qname.c, DNSTypeName(q->qtype)); result = mDNS_StartQuery(gmDNS, q); if (result != mStatus_NoError) LogMsg("ERROR: mDNS_StartQuery: %d", (int)result); if (result) rstate->terminate = NULL; if (deliver_error(rstate, result) < 0) goto error; return; bad_param: deliver_error(rstate, mStatus_BadParamErr); rstate->terminate = NULL; // don't try to terminate insuccessful Core calls error: abort_request(rstate); unlink_request(rstate); return; } static void handle_resolve_request(request_state *rstate) { DNSServiceFlags flags; uint32_t interfaceIndex; mDNSInterfaceID InterfaceID; char name[256], regtype[MAX_ESCAPED_DOMAIN_NAME], domain[MAX_ESCAPED_DOMAIN_NAME]; char *ptr; // message data pointer domainname fqdn; resolve_termination_t *term; mStatus err; if (rstate->ts != t_complete) { LogMsg("ERROR: handle_resolve_request - transfer state != t_complete"); abort_request(rstate); unlink_request(rstate); return; } // extract the data from the message ptr = rstate->msgdata; if (!ptr) { LogMsg("ERROR: handle_resolve_request - NULL msgdata"); abort_request(rstate); unlink_request(rstate); return; } flags = get_flags(&ptr); interfaceIndex = get_long(&ptr); InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(gmDNS, interfaceIndex); if (interfaceIndex && !InterfaceID) { LogMsg("ERROR: handle_resolve_request - Couldn't find InterfaceID for interfaceIndex %d", interfaceIndex); goto bad_param; } if (get_string(&ptr, name, 256) < 0 || get_string(&ptr, regtype, MAX_ESCAPED_DOMAIN_NAME) < 0 || get_string(&ptr, domain, MAX_ESCAPED_DOMAIN_NAME) < 0) { LogMsg("ERROR: handle_resolve_request - Couldn't read name/regtype/domain"); goto bad_param; } // free memory in rstate since we don't need it anymore freeL("handle_resolve_request", rstate->msgbuf); rstate->msgbuf = NULL; if (build_domainname_from_strings(&fqdn, name, regtype, domain) < 0) { LogMsg("ERROR: handle_resolve_request - Couldn't build_domainname_from_strings “%s” “%s” “%s”", name, regtype, domain); goto bad_param; } // set up termination info term = mallocL("handle_resolve_request", sizeof(resolve_termination_t)); bzero(term, sizeof(*term)); if (!term) FatalError("ERROR: malloc"); // format questions term->qsrv.InterfaceID = InterfaceID; term->qsrv.Target = zeroAddr; memcpy(&term->qsrv.qname, &fqdn, MAX_DOMAIN_NAME); term->qsrv.qtype = kDNSType_SRV; term->qsrv.qclass = kDNSClass_IN; term->qsrv.LongLived = mDNSfalse; term->qsrv.ExpectUnique = mDNStrue; term->qsrv.ForceMCast = mDNSfalse; term->qsrv.QuestionCallback = resolve_result_callback; term->qsrv.QuestionContext = rstate; term->qtxt.InterfaceID = InterfaceID; term->qtxt.Target = zeroAddr; memcpy(&term->qtxt.qname, &fqdn, MAX_DOMAIN_NAME); term->qtxt.qtype = kDNSType_TXT; term->qtxt.qclass = kDNSClass_IN; term->qtxt.LongLived = mDNSfalse; term->qtxt.ExpectUnique = mDNStrue; term->qtxt.ForceMCast = mDNSfalse; term->qtxt.QuestionCallback = resolve_result_callback; term->qtxt.QuestionContext = rstate; term->rstate = rstate; rstate->termination_context = term; rstate->terminate = resolve_termination_callback; // ask the questions LogOperation("%3d: DNSServiceResolve(%##s) START", rstate->sd, term->qsrv.qname.c); err = mDNS_StartQuery(gmDNS, &term->qsrv); if (!err) err = mDNS_StartQuery(gmDNS, &term->qtxt); if (err) { freeL("handle_resolve_request", term); rstate->terminate = NULL; // prevent abort_request() from invoking termination callback } if (deliver_error(rstate, err) < 0 || err) { abort_request(rstate); unlink_request(rstate); } return; bad_param: deliver_error(rstate, mStatus_BadParamErr); abort_request(rstate); unlink_request(rstate); } static void resolve_termination_callback(void *context) { resolve_termination_t *term = context; request_state *rs; if (!term) { LogMsg("ERROR: resolve_termination_callback: double termination"); return; } rs = term->rstate; LogOperation("%3d: DNSServiceResolve(%##s) STOP", rs->sd, term->qtxt.qname.c); mDNS_StopQuery(gmDNS, &term->qtxt); mDNS_StopQuery(gmDNS, &term->qsrv); freeL("resolve_termination_callback", term); rs->termination_context = NULL; } static void resolve_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord) { size_t len = 0; char fullname[MAX_ESCAPED_DOMAIN_NAME], target[MAX_ESCAPED_DOMAIN_NAME]; char *data; transfer_state result; reply_state *rep; request_state *rs = question->QuestionContext; resolve_termination_t *res = rs->termination_context; (void)m; // Unused LogOperation("%3d: DNSServiceResolve(%##s, %s) RESULT %s", rs->sd, question->qname.c, DNSTypeName(question->qtype), RRDisplayString(m, answer)); // This code used to do this trick of just keeping a copy of the pointer to // the answer record in the cache, but the unicast query code doesn't currently // put its answer records in the cache, so for now we can't do this. if (!AddRecord) { // if (answer->rrtype == kDNSType_TXT && res->txt == answer) res->txt = mDNSNULL; // if (answer->rrtype == kDNSType_SRV && res->srv == answer) res->srv = mDNSNULL; return; } // if (answer->rrtype == kDNSType_TXT) res->txt = answer; // if (answer->rrtype == kDNSType_SRV) res->srv = answer; if (answer->rrtype == kDNSType_SRV) { AssignDomainName(&res->target, &answer->rdata->u.srv.target); res->port = answer->rdata->u.srv.port; res->srv = mDNStrue; } if (answer->rrtype == kDNSType_TXT) { if (answer->rdlength > AbsoluteMaxDNSMessageData) return; res->txtlen = answer->rdlength; mDNSPlatformMemCopy(answer->rdata->u.data, res->txtdata, res->txtlen); res->txt = mDNStrue; } if (!res->txt || !res->srv) return; // only deliver result to client if we have both answers ConvertDomainNameToCString(answer->name, fullname); ConvertDomainNameToCString(&res->target, target); // calculate reply length len += sizeof(DNSServiceFlags); len += sizeof(uint32_t); // interface index len += sizeof(DNSServiceErrorType); len += strlen(fullname) + 1; len += strlen(target) + 1; len += 2 * sizeof(uint16_t); // port, txtLen len += res->txtlen; // allocate/init reply header rep = create_reply(resolve_reply, len, rs); rep->rhdr->flags = dnssd_htonl(0); rep->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(gmDNS, answer->InterfaceID)); rep->rhdr->error = dnssd_htonl(kDNSServiceErr_NoError); data = rep->sdata; // write reply data to message put_string(fullname, &data); put_string(target, &data); *data++ = res->port.b[0]; *data++ = res->port.b[1]; put_short(res->txtlen, &data); put_rdata(res->txtlen, res->txtdata, &data); result = send_msg(rep); if (result == t_error || result == t_terminated) { abort_request(rs); unlink_request(rs); freeL("resolve_result_callback", rep); } else if (result == t_complete) freeL("resolve_result_callback", rep); else append_reply(rs, rep); } // what gets called when a resolve is completed and we need to send the data back to the client static void question_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord) { char *data; char name[MAX_ESCAPED_DOMAIN_NAME]; request_state *req = question->QuestionContext; reply_state *rep; size_t len; (void)m; // Unused LogOperation("%3d: DNSServiceQueryRecord(%##s, %s) RESULT %s", req->sd, question->qname.c, DNSTypeName(question->qtype), RRDisplayString(m, answer)); //mDNS_StopQuery(m, question); // calculate reply data length len = sizeof(DNSServiceFlags); len += 2 * sizeof(uint32_t); // if index + ttl len += sizeof(DNSServiceErrorType); len += 3 * sizeof(uint16_t); // type, class, rdlen len += answer->rdlength; ConvertDomainNameToCString(answer->name, name); len += strlen(name) + 1; rep = create_reply(query_reply, len, req); rep->rhdr->flags = dnssd_htonl(AddRecord ? kDNSServiceFlagsAdd : 0); rep->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(gmDNS, answer->InterfaceID)); rep->rhdr->error = dnssd_htonl(kDNSServiceErr_NoError); data = rep->sdata; put_string(name, &data); put_short(answer->rrtype, &data); put_short(answer->rrclass, &data); put_short(answer->rdlength, &data); put_rdata(answer->rdlength, answer->rdata->u.data, &data); put_long(AddRecord ? answer->rroriginalttl : 0, &data); append_reply(req, rep); return; } static void question_termination_callback(void *context) { DNSQuestion *q = context; LogOperation("%3d: DNSServiceQueryRecord(%##s, %s) STOP", ((request_state *)q->QuestionContext)->sd, q->qname.c, DNSTypeName(q->qtype)); mDNS_StopQuery(gmDNS, q); // no need to error check freeL("question_termination_callback", q); } // If there's a comma followed by another character, // FindFirstSubType overwrites the comma with a nul and returns the pointer to the next character. // Otherwise, it returns a pointer to the final nul at the end of the string static char *FindFirstSubType(char *p) { while (*p) { if (p[0] == '\\' && p[1]) p += 2; else if (p[0] == ',' && p[1]) { *p++ = 0; return(p); } else p++; } return(p); } // If there's a comma followed by another character, // FindNextSubType overwrites the comma with a nul and returns the pointer to the next character. // If it finds an illegal unescaped dot in the subtype name, it returns mDNSNULL // Otherwise, it returns a pointer to the final nul at the end of the string static char *FindNextSubType(char *p) { while (*p) { if (p[0] == '\\' && p[1]) // If escape character p += 2; // ignore following character else if (p[0] == ',') // If we found a comma { if (p[1]) *p++ = 0; return(p); } else if (p[0] == '.') return(mDNSNULL); else p++; } return(p); } // Returns -1 if illegal subtype found mDNSexport mDNSs32 ChopSubTypes(char *regtype) { mDNSs32 NumSubTypes = 0; char *stp = FindFirstSubType(regtype); while (stp && *stp) // If we found a comma... { if (*stp == ',') return(-1); NumSubTypes++; stp = FindNextSubType(stp); } if (!stp) return(-1); return(NumSubTypes); } mDNSexport AuthRecord *AllocateSubTypes(mDNSs32 NumSubTypes, char *p) { AuthRecord *st = mDNSNULL; if (NumSubTypes) { mDNSs32 i; st = mallocL("ServiceSubTypes", NumSubTypes * sizeof(AuthRecord)); if (!st) return(mDNSNULL); for (i = 0; i < NumSubTypes; i++) { mDNS_SetupResourceRecord(&st[i], mDNSNULL, mDNSInterface_Any, kDNSQType_ANY, kStandardTTL, 0, mDNSNULL, mDNSNULL); while (*p) p++; p++; if (!MakeDomainNameFromDNSNameString(st[i].resrec.name, p)) { freeL("ServiceSubTypes", st); return(mDNSNULL); } } } return(st); } #ifdef _HAVE_SETDOMAIN_SUPPORT_ static void free_defdomain(mDNS *const m, AuthRecord *const rr, mStatus result) { (void)m; // unused if (result == mStatus_MemFree) free(rr->RecordContext); // context is the enclosing list structure } #endif static void handle_setdomain_request(request_state *request) { mStatus err = mStatus_NoError; char *ptr; char domainstr[MAX_ESCAPED_DOMAIN_NAME]; domainname domain; DNSServiceFlags flags; #ifdef _HAVE_SETDOMAIN_SUPPORT_ struct xucred xuc; socklen_t xuclen; #endif if (request->ts != t_complete) { LogMsg("ERROR: handle_setdomain_request - transfer state != t_complete"); abort_request(request); unlink_request(request); return; } // extract flags/domain from message ptr = request->msgdata; flags = get_flags(&ptr); if (get_string(&ptr, domainstr, MAX_ESCAPED_DOMAIN_NAME) < 0 || !MakeDomainNameFromDNSNameString(&domain, domainstr)) { err = mStatus_BadParamErr; goto end; } freeL("handle_setdomain_request", request->msgbuf); request->msgbuf = NULL; debugf("%3d: DNSServiceSetDefaultDomainForUser(%##s)", request->sd, domain.c); #ifdef _HAVE_SETDOMAIN_SUPPORT_ // this functionality currently only used for Apple-specific configuration, so we don't burned other platforms by mandating // the existence of this socket option xuclen = sizeof(xuc); if (getsockopt(request->sd, 0, LOCAL_PEERCRED, &xuc, &xuclen)) { my_perror("ERROR: getsockopt, LOCAL_PEERCRED"); err = mStatus_UnknownErr; goto end; } if (xuc.cr_version != XUCRED_VERSION) { LogMsg("getsockopt, LOCAL_PEERCRED - bad version"); err = mStatus_UnknownErr; goto end; } LogMsg("Default domain %s %s for UID %d", domainstr, flags & kDNSServiceFlagsAdd ? "set" : "removed", xuc.cr_uid); if (flags & kDNSServiceFlagsAdd) { // register a local-only PRT record default_browse_list_t *newelem = malloc(sizeof(default_browse_list_t)); if (!newelem) { LogMsg("ERROR: malloc"); err = mStatus_NoMemoryErr; goto end; } mDNS_SetupResourceRecord(&newelem->ptr_rec, mDNSNULL, mDNSInterface_LocalOnly, kDNSType_PTR, 7200, kDNSRecordTypeShared, free_defdomain, newelem); MakeDomainNameFromDNSNameString(&newelem->ptr_rec.resrec.name, mDNS_DomainTypeNames[mDNS_DomainTypeBrowseDefault]); AppendDNSNameString (&newelem->ptr_rec.resrec.name, "local"); AssignDomainName(&newelem->ptr_rec.resrec.rdata->u.name, &domain); newelem->uid = xuc.cr_uid; err = mDNS_Register(gmDNS, &newelem->ptr_rec); if (err) free(newelem); else { // link into list newelem->next = default_browse_list; default_browse_list = newelem; } } else { // remove - find in list, deregister default_browse_list_t *ptr = default_browse_list, *prev = NULL; while (ptr) { if (SameDomainName(&ptr->ptr_rec.resrec.rdata->u.name, &domain)) { if (prev) prev->next = ptr->next; else default_browse_list = ptr->next; err = mDNS_Deregister(gmDNS, &ptr->ptr_rec); break; } prev = ptr; ptr = ptr->next; } if (!ptr) { LogMsg("Attempt to remove nonexistent domain %s for UID %d", domainstr, xuc.cr_uid); err = mStatus_Invalid; } } #else err = mStatus_NoError; #endif // _HAVE_SETDOMAIN_SUPPORT_ end: deliver_error(request, err); abort_request(request); unlink_request(request); } static mStatus add_domain_to_browser(browser_info_t *info, const domainname *d) { browser_t *b, *p; mStatus err; for (p = info->browsers; p; p = p->next) { if (SameDomainName(&p->domain, d)) { debugf("add_domain_to_browser - attempt to add domain %##d already in list", d->c); return mStatus_AlreadyRegistered; } } b = mallocL("browser_t", sizeof(*b)); if (!b) return mStatus_NoMemoryErr; AssignDomainName(&b->domain, d); err = mDNS_StartBrowse(gmDNS, &b->q, &info->regtype, d, info->interface_id, info->ForceMCast, browse_result_callback, info->rstate); if (err) { LogMsg("mDNS_StartBrowse returned %d for type %##s domain %##s", err, info->regtype.c, d->c); freeL("browser_t", b); } else { b->next = info->browsers; info->browsers = b; } return err; } static void handle_browse_request(request_state *request) { DNSServiceFlags flags; uint32_t interfaceIndex; mDNSInterfaceID InterfaceID; char regtype[MAX_ESCAPED_DOMAIN_NAME], domain[MAX_ESCAPED_DOMAIN_NAME]; domainname typedn, d, temp; mDNSs32 NumSubTypes; char *ptr; mStatus err; DNameListElem *search_domain_list, *sdom; browser_info_t *info = NULL; if (request->ts != t_complete) { LogMsg("ERROR: handle_browse_request - transfer state != t_complete"); abort_request(request); unlink_request(request); return; } // extract data from message ptr = request->msgdata; flags = get_flags(&ptr); interfaceIndex = get_long(&ptr); if (get_string(&ptr, regtype, MAX_ESCAPED_DOMAIN_NAME) < 0 || get_string(&ptr, domain, MAX_ESCAPED_DOMAIN_NAME) < 0) { err = mStatus_BadParamErr; goto error; } freeL("handle_browse_request", request->msgbuf); request->msgbuf = NULL; InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(gmDNS, interfaceIndex); if (interfaceIndex && !InterfaceID) { err = mStatus_BadParamErr; goto error; } typedn.c[0] = 0; NumSubTypes = ChopSubTypes(regtype); // Note: Modifies regtype string to remove trailing subtypes if (NumSubTypes < 0 || NumSubTypes > 1) { err = mStatus_BadParamErr; goto error; } if (NumSubTypes == 1 && !AppendDNSNameString(&typedn, regtype + strlen(regtype) + 1)) { err = mStatus_BadParamErr; goto error; } if (!regtype[0] || !AppendDNSNameString(&typedn, regtype)) { err = mStatus_BadParamErr; goto error; } if (!MakeDomainNameFromDNSNameString(&temp, regtype)) { err = mStatus_BadParamErr; goto error; } if (temp.c[0] > 15 && domain[0] == 0) strcpy(domain, "local."); // For over-long service types, we only allow domain "local" // allocate and set up browser info info = mallocL("browser_info_t", sizeof(*info)); if (!info) { err = mStatus_NoMemoryErr; goto error; } request->browser_info = info; info->ForceMCast = (flags & kDNSServiceFlagsForceMulticast) != 0; info->interface_id = InterfaceID; AssignDomainName(&info->regtype, &typedn); info->rstate = request; info->default_domain = !domain[0]; info->browsers = NULL; // setup termination context request->termination_context = info; request->terminate = browse_termination_callback; LogOperation("%3d: DNSServiceBrowse(%##s%s) START", request->sd, info->regtype.c, domain); if (domain[0]) { if (!MakeDomainNameFromDNSNameString(&d, domain)) { err = mStatus_BadParamErr; goto error; } err = add_domain_to_browser(info, &d); } else { search_domain_list = mDNSPlatformGetSearchDomainList(); for (sdom = search_domain_list; sdom; sdom = sdom->next) { err = add_domain_to_browser(info, &sdom->name); if (err) { if (SameDomainName(&sdom->name, &localdomain)) break; else err = mStatus_NoError; // suppress errors for non-local "default" domains } } mDNS_FreeDNameList(search_domain_list); } deliver_error(request, mStatus_NoError); return; error: if (info) freeL("browser_info_t", info); if (request->termination_context) request->termination_context = NULL; deliver_error(request, err); abort_request(request); unlink_request(request); } static void browse_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord) { request_state *req = question->QuestionContext; reply_state *rep; mStatus err; (void)m; // Unused LogOperation("%3d: DNSServiceBrowse(%##s, %s) RESULT %s %s", req->sd, question->qname.c, DNSTypeName(question->qtype), AddRecord ? "Add" : "Rmv", RRDisplayString(m, answer)); err = gen_rr_response(&answer->rdata->u.name, answer->InterfaceID, req, &rep); if (err) { if (deliver_async_error(req, browse_reply, err) < 0) { abort_request(req); unlink_request(req); } return; } if (AddRecord) rep->rhdr->flags |= dnssd_htonl(kDNSServiceFlagsAdd); // non-zero TTL indicates add append_reply(req, rep); return; } static void browse_termination_callback(void *context) { browser_info_t *info = context; browser_t *ptr; if (!info) return; while(info->browsers) { ptr = info->browsers; info->browsers = ptr->next; LogOperation("%3d: DNSServiceBrowse(%##s) STOP", info->rstate->sd, ptr->q.qname.c); mDNS_StopBrowse(gmDNS, &ptr->q); // no need to error-check result freeL("browse_termination_callback", ptr); } info->rstate->termination_context = NULL; freeL("browser_info", info); } mDNSexport void udsserver_default_browse_domain_changed(const domainname *d, mDNSBool add) { request_state *r; for (r = all_requests; r; r = r->next) { browser_info_t *info = r->browser_info; if (!info || !info->default_domain) continue; if (add) add_domain_to_browser(info, d); else { browser_t **ptr = &info->browsers; while (*ptr) { if (SameDomainName(&(*ptr)->domain, d)) { browser_t *remove = *ptr; *ptr = (*ptr)->next; if (remove->q.LongLived) { // give goodbyes for known answers. // note that since events are sent to client via udsserver_idle(), we don't need to worry about the question being cancelled mid-loop CacheRecord *ka = remove->q.uDNS_info.knownAnswers; while (ka) { remove->q.QuestionCallback(gmDNS, &remove->q, &ka->resrec, mDNSfalse); ka = ka->next; } } mDNS_StopBrowse(gmDNS, &remove->q); freeL("browser_t", remove); return; } ptr = &(*ptr)->next; } LogMsg("Requested removal of default domain %##s not in list for sd %d", d->c, r->sd); } } } // Count how many other service records we have locally with the same name, but different rdata. // For auto-named services, we can have at most one per machine -- if we allowed two auto-named services of // the same type on the same machine, we'd get into an infinite autoimmune-response loop of continuous renaming. mDNSexport int CountPeerRegistrations(mDNS *const m, ServiceRecordSet *const srs) { int count = 0; ResourceRecord *r = &srs->RR_SRV.resrec; AuthRecord *rr; ServiceRecordSet *s; for (rr = m->ResourceRecords; rr; rr=rr->next) if (rr->resrec.rrtype == kDNSType_SRV && SameDomainName(rr->resrec.name, r->name) && !SameRData(&rr->resrec, r)) count++; for (rr = m->uDNS_info.RecordRegistrations; rr; rr=rr->next) if (rr->uDNS_info.state != regState_Unregistered && rr->resrec.rrtype == kDNSType_SRV && SameDomainName(rr->resrec.name, r->name) && !SameRData(&rr->resrec, r)) count++; for (s = m->uDNS_info.ServiceRegistrations; s; s = s->next) if (s->uDNS_info.state != regState_Unregistered && SameDomainName(s->RR_SRV.resrec.name, r->name) && !SameRData(&s->RR_SRV.resrec, r)) count++; verbosedebugf("%d peer registrations for %##s", count, r->name->c); return(count); } mDNSexport int CountExistingRegistrations(domainname *srv, mDNSIPPort port) { int count = 0; AuthRecord *rr; for (rr = gmDNS->ResourceRecords; rr; rr=rr->next) if (rr->resrec.rrtype == kDNSType_SRV && rr->resrec.rdata->u.srv.port.NotAnInteger == port.NotAnInteger && SameDomainName(rr->resrec.name, srv)) count++; return(count); } static mStatus register_service_instance(request_state *request, const domainname *domain) { service_info *info = request->service_registration; service_instance *ptr, *instance; int instance_size; mStatus result; for (ptr = info->instances; ptr; ptr = ptr->next) { if (SameDomainName(&ptr->domain, domain)) { LogMsg("register_service_instance: domain %##s already registered", domain->c); return mStatus_AlreadyRegistered; } } instance_size = sizeof(*instance); if (info->txtlen > sizeof(RDataBody)) instance_size += (info->txtlen - sizeof(RDataBody)); instance = mallocL("service_instance", instance_size); if (!instance) { my_perror("ERROR: malloc"); return mStatus_NoMemoryErr; } instance->subtypes = AllocateSubTypes(info->num_subtypes, info->type_as_string); if (info->num_subtypes && !instance->subtypes) { free_service_instance(instance); instance = NULL; FatalError("ERROR: malloc"); } instance->request = request; instance->sd = request->sd; instance->autoname = info->autoname; instance->autorename = info->autorename; instance->allowremotequery = info->allowremotequery; instance->rename_on_memfree = 0; instance->name = info->name; AssignDomainName(&instance->domain, domain); instance->default_local = (info->default_domain && SameDomainName(domain, &localdomain)); result = mDNS_RegisterService(gmDNS, &instance->srs, &instance->name, &info->type, domain, info->host.c[0] ? &info->host : NULL, info->port, info->txtdata, info->txtlen, instance->subtypes, info->num_subtypes, info->InterfaceID, regservice_callback, instance); if (result) free_service_instance(instance); else { instance->next = info->instances; info->instances = instance; } return result; } mDNSexport void udsserver_default_reg_domain_changed(const domainname *d, mDNSBool add) { request_state *rstate; service_info *info; for (rstate = all_requests; rstate; rstate = rstate->next) { if (rstate->terminate != regservice_termination_callback) continue; info = rstate->service_registration; if (!info) { LogMsg("udsserver_default_reg_domain_changed - NULL service info"); continue; } // this should never happen if (!info->default_domain) continue; // valid default registration if (add) register_service_instance(rstate, d); else { // find the instance to remove service_instance *si = rstate->service_registration->instances, *prev = NULL; while (si) { if (SameDomainName(&si->domain, d)) { mStatus err; if (prev) prev->next = si->next; else info->instances = si->next; err = mDNS_DeregisterService(gmDNS, &si->srs); if (err) { LogMsg("udsserver_default_reg_domain_changed - mDNS_DeregisterService err %d", err); free_service_instance(si); } break; } prev = si; si = si->next; } if (!si) LogMsg("udsserver_default_reg_domain_changed - domain %##s not registered", d->c); } } } // service registration static void handle_regservice_request(request_state *request) { DNSServiceFlags flags; uint32_t ifi; char name[256], domain[MAX_ESCAPED_DOMAIN_NAME], host[MAX_ESCAPED_DOMAIN_NAME]; char *ptr; domainname d, srv; mStatus result; service_info *service = NULL; if (request->ts != t_complete) { LogMsg("ERROR: handle_regservice_request - transfer state != t_complete"); abort_request(request); unlink_request(request); return; } service = mallocL("service_info", sizeof(*service)); if (!service) { my_perror("ERROR: malloc"); result = mStatus_NoMemoryErr; goto finish; } service->instances = NULL; service->request = request; request->service_registration = service; request->termination_context = request->service_registration; request->terminate = regservice_termination_callback; // extract data from message ptr = request->msgdata; flags = get_flags(&ptr); ifi = get_long(&ptr); service->InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(gmDNS, ifi); if (ifi && !service->InterfaceID) { LogMsg("ERROR: handle_regservice_request - Couldn't find InterfaceID for interfaceIndex %d", ifi); goto bad_param; } if (get_string(&ptr, name, 256) < 0 || get_string(&ptr, service->type_as_string, MAX_ESCAPED_DOMAIN_NAME) < 0 || get_string(&ptr, domain, MAX_ESCAPED_DOMAIN_NAME) < 0 || get_string(&ptr, host, MAX_ESCAPED_DOMAIN_NAME) < 0) { LogMsg("ERROR: handle_regservice_request - Couldn't read name/regtype/domain"); goto bad_param; } service->port.b[0] = *ptr++; service->port.b[1] = *ptr++; service->txtlen = get_short(&ptr); service->txtdata = get_rdata(&ptr, service->txtlen); // Check for sub-types after the service type service->num_subtypes = ChopSubTypes(service->type_as_string); // Note: Modifies regtype string to remove trailing subtypes if (service->num_subtypes < 0) { LogMsg("ERROR: handle_regservice_request - ChopSubTypes failed %s", service->type_as_string); goto bad_param; } // Don't try to construct "domainname t" until *after* ChopSubTypes has worked its magic if (!*service->type_as_string || !MakeDomainNameFromDNSNameString(&service->type, service->type_as_string)) { LogMsg("ERROR: handle_regservice_request - service->type_as_string bad %s", service->type_as_string); goto bad_param; } if (!name[0]) { service->name = (gmDNS)->nicelabel; service->autoname = mDNStrue; } else { if (!MakeDomainLabelFromLiteralString(&service->name, name)) { LogMsg("ERROR: handle_regservice_request - name bad %s", name); goto bad_param; } service->autoname = mDNSfalse; } if (*domain) { service->default_domain = mDNSfalse; if (!MakeDomainNameFromDNSNameString(&d, domain)) { LogMsg("ERROR: handle_regservice_request - domain bad %s", domain); goto bad_param; } } else { service->default_domain = mDNStrue; MakeDomainNameFromDNSNameString(&d, "local."); } if (!ConstructServiceName(&srv, &service->name, &service->type, &d)) { LogMsg("ERROR: handle_regservice_request - Couldn't ConstructServiceName from, “%#s” “%##s” “%##s”", service->name.c, service->type.c, d.c); goto bad_param; } if (!MakeDomainNameFromDNSNameString(&service->host, host)) { LogMsg("ERROR: handle_regservice_request - host bad %s", host); goto bad_param; } service->autorename = (flags & kDNSServiceFlagsNoAutoRename ) == 0; service->allowremotequery = (flags & kDNSServiceFlagsAllowRemoteQuery) != 0; // Some clients use mDNS for lightweight copy protection, registering a pseudo-service with // a port number of zero. When two instances of the protected client are allowed to run on one // machine, we don't want to see misleading "Bogus client" messages in syslog and the console. if (service->port.NotAnInteger) { int count = CountExistingRegistrations(&srv, service->port); if (count) LogMsg("Client application registered %d identical instances of service %##s port %u.", count+1, srv.c, mDNSVal16(service->port)); } LogOperation("%3d: DNSServiceRegister(%##s, %u) START", request->sd, srv.c, mDNSVal16(service->port)); result = register_service_instance(request, &d); if (!result && !*domain) { DNameListElem *ptr, *def_domains = mDNSPlatformGetRegDomainList(); for (ptr = def_domains; ptr; ptr = ptr->next) register_service_instance(request, &ptr->name); // note that we don't report errors for non-local, non-explicit domains mDNS_FreeDNameList(def_domains); } finish: deliver_error(request, result); if (result != mStatus_NoError) { abort_request(request); unlink_request(request); } else reset_connected_rstate(request); // prepare to receive add/remove messages return; bad_param: //if (service) freeL("service_info", service); Don't think we should do this -- abort_request will free it a second time and crash deliver_error(request, mStatus_BadParamErr); abort_request(request); unlink_request(request); } // service registration callback performs three duties - frees memory for deregistered services, // handles name conflicts, and delivers completed registration information to the client (via // process_service_registraion()) static void regservice_callback(mDNS *const m, ServiceRecordSet *const srs, mStatus result) { mStatus err; service_instance *instance = srs->ServiceContext; (void)m; // Unused if (!srs) { LogMsg("regservice_callback: srs is NULL %d", result); return; } if (!instance) { LogMsg("regservice_callback: srs->ServiceContext is NULL %d", result); return; } if (result == mStatus_NoError) LogOperation("%3d: DNSServiceRegister(%##s, %u) REGISTERED ", instance->sd, srs->RR_SRV.resrec.name->c, mDNSVal16(srs->RR_SRV.resrec.rdata->u.srv.port)); else if (result == mStatus_MemFree) LogOperation("%3d: DNSServiceRegister(%##s, %u) DEREGISTERED", instance->sd, srs->RR_SRV.resrec.name->c, mDNSVal16(srs->RR_SRV.resrec.rdata->u.srv.port)); else if (result == mStatus_NameConflict) LogOperation("%3d: DNSServiceRegister(%##s, %u) NAME CONFLICT", instance->sd, srs->RR_SRV.resrec.name->c, mDNSVal16(srs->RR_SRV.resrec.rdata->u.srv.port)); else LogOperation("%3d: DNSServiceRegister(%##s, %u) CALLBACK %d", instance->sd, srs->RR_SRV.resrec.name->c, mDNSVal16(srs->RR_SRV.resrec.rdata->u.srv.port), result); if (result == mStatus_NoError) { if (instance->allowremotequery) { ExtraResourceRecord *e; srs->RR_ADV.AllowRemoteQuery = mDNStrue; srs->RR_PTR.AllowRemoteQuery = mDNStrue; srs->RR_SRV.AllowRemoteQuery = mDNStrue; srs->RR_TXT.AllowRemoteQuery = mDNStrue; for (e = instance->srs.Extras; e; e = e->next) e->r.AllowRemoteQuery = mDNStrue; } process_service_registration(srs); if (instance->autoname && CountPeerRegistrations(m, srs) == 0) RecordUpdatedNiceLabel(m, 0); // Successfully got new name, tell user immediately return; } else if (result == mStatus_MemFree) { if (instance->rename_on_memfree) { instance->rename_on_memfree = 0; instance->name = gmDNS->nicelabel; err = mDNS_RenameAndReregisterService(gmDNS, srs, &instance->name); if (err) LogMsg("ERROR: regservice_callback - RenameAndReregisterService returned %ld", err); // error should never happen - safest to log and continue } else { free_service_instance(instance); return; } } else if (result == mStatus_NameConflict) { if (instance->autoname && CountPeerRegistrations(m, srs) == 0) { // On conflict for an autoname service, rename and reregister *all* autoname services IncrementLabelSuffix(&m->nicelabel, mDNStrue); m->MainCallback(m, mStatus_ConfigChanged); } else if (instance->autoname || instance->autorename) { mDNS_RenameAndReregisterService(gmDNS, srs, mDNSNULL); return; } else { request_state *rs = instance->request; if (!rs) { LogMsg("ERROR: regservice_callback: received result %ld with a NULL request pointer", result); return; } free_service_instance(instance); if (deliver_async_error(rs, reg_service_reply, result) < 0) { abort_request(rs); unlink_request(rs); } return; } } else { request_state *rs = instance->request; if (!rs) { LogMsg("ERROR: regservice_callback: received result %ld with a NULL request pointer", result); return; } if (result != mStatus_NATTraversal) LogMsg("ERROR: unknown result in regservice_callback: %ld", result); free_service_instance(instance); if (deliver_async_error(rs, reg_service_reply, result) < 0) { abort_request(rs); unlink_request(rs); } return; } } mDNSexport void FreeExtraRR(mDNS *const m, AuthRecord *const rr, mStatus result) { ExtraResourceRecord *extra = (ExtraResourceRecord *)rr->RecordContext; (void)m; //unused if (result != mStatus_MemFree) { LogMsg("Error: FreeExtraRR invoked with unexpected error %d", result); return; } debugf("%##s: MemFree", rr->resrec.name->c); if (rr->resrec.rdata != &rr->rdatastorage) freeL("Extra RData", rr->resrec.rdata); freeL("ExtraResourceRecord", extra); } static mStatus add_record_to_service(request_state *rstate, service_instance *instance, uint16_t rrtype, uint16_t rdlen, char *rdata, uint32_t ttl) { ServiceRecordSet *srs = &instance->srs; ExtraResourceRecord *extra; mStatus result; int size; if (rdlen > sizeof(RDataBody)) size = rdlen; else size = sizeof(RDataBody); extra = mallocL("ExtraResourceRecord", sizeof(*extra) - sizeof(RDataBody) + size); if (!extra) { my_perror("ERROR: malloc"); return mStatus_NoMemoryErr; } bzero(extra, sizeof(ExtraResourceRecord)); // OK if oversized rdata not zero'd extra->r.resrec.rrtype = rrtype; extra->r.rdatastorage.MaxRDLength = (mDNSu16) size; extra->r.resrec.rdlength = rdlen; memcpy(&extra->r.rdatastorage.u.data, rdata, rdlen); result = mDNS_AddRecordToService(gmDNS, srs , extra, &extra->r.rdatastorage, ttl); if (result) { freeL("ExtraResourceRecord", extra); return result; } extra->ClientID = rstate->hdr.reg_index; return result; } static mStatus handle_add_request(request_state *rstate) { uint32_t ttl; uint16_t rrtype, rdlen; char *ptr, *rdata; mStatus result = mStatus_UnknownErr; DNSServiceFlags flags; service_info *srvinfo = rstate->service_registration; service_instance *i; if (!srvinfo) { LogMsg("handle_add_request called with NULL service_registration"); return(-1); } ptr = rstate->msgdata; flags = get_flags(&ptr); rrtype = get_short(&ptr); rdlen = get_short(&ptr); rdata = get_rdata(&ptr, rdlen); ttl = get_long(&ptr); if (!ttl) ttl = DefaultTTLforRRType(rrtype); LogOperation("%3d: DNSServiceAddRecord(%##s, %s)", rstate->sd, (srvinfo->instances) ? srvinfo->instances->srs.RR_SRV.resrec.name->c : NULL, DNSTypeName(rrtype)); for (i = srvinfo->instances; i; i = i->next) { result = add_record_to_service(rstate, i, rrtype, rdlen, rdata, ttl); if (result && i->default_local) break; else result = mStatus_NoError; // suppress non-local default errors } return(result); } static mStatus update_record(AuthRecord *rr, uint16_t rdlen, char *rdata, uint32_t ttl) { int rdsize; RData *newrd; mStatus result; if (rdlen > sizeof(RDataBody)) rdsize = rdlen; else rdsize = sizeof(RDataBody); newrd = mallocL("handle_update_request", sizeof(RData) - sizeof(RDataBody) + rdsize); if (!newrd) FatalError("ERROR: malloc"); newrd->MaxRDLength = (mDNSu16) rdsize; memcpy(&newrd->u, rdata, rdlen); result = mDNS_Update(gmDNS, rr, ttl, rdlen, newrd, update_callback); if (result) { LogMsg("ERROR: mDNS_Update - %ld", result); freeL("handle_update_request", newrd); } return result; } static mStatus handle_update_request(request_state *rstate) { uint16_t rdlen; char *ptr, *rdata; uint32_t ttl; mStatus result = mStatus_BadReferenceErr; service_info *srvinfo = rstate->service_registration; service_instance *i; AuthRecord *rr = NULL; // get the message data ptr = rstate->msgdata; get_flags(&ptr); // flags unused rdlen = get_short(&ptr); rdata = get_rdata(&ptr, rdlen); ttl = get_long(&ptr); if (rstate->reg_recs) { // update an individually registered record registered_record_entry *reptr; for (reptr = rstate->reg_recs; reptr; reptr = reptr->next) { if (reptr->key == rstate->hdr.reg_index) { result = update_record(reptr->rr, rdlen, rdata, ttl); goto end; } } result = mStatus_BadReferenceErr; goto end; } // update a record from a service record set if (!srvinfo) { result = mStatus_BadReferenceErr; goto end; } for (i = srvinfo->instances; i; i = i->next) { if (rstate->hdr.reg_index == TXT_RECORD_INDEX) rr = &i->srs.RR_TXT; else { ExtraResourceRecord *e; for (e = i->srs.Extras; e; e = e->next) if (e->ClientID == rstate->hdr.reg_index) { rr = &e->r; break; } } if (!rr) { result = mStatus_BadReferenceErr; goto end; } result = update_record(rr, rdlen, rdata, ttl); if (result && i->default_local) goto end; else result = mStatus_NoError; // suppress non-local default errors } end: LogOperation("%3d: DNSServiceUpdateRecord(%##s, %s)", rstate->sd, (srvinfo->instances) ? srvinfo->instances->srs.RR_SRV.resrec.name->c : NULL, rr ? DNSTypeName(rr->resrec.rrtype) : ""); return(result); } static void update_callback(mDNS *const m, AuthRecord *const rr, RData *oldrd) { (void)m; // Unused if (oldrd != &rr->rdatastorage) freeL("update_callback", oldrd); } static void process_service_registration(ServiceRecordSet *const srs) { reply_state *rep; transfer_state send_result; mStatus err; service_instance *instance = srs->ServiceContext; request_state *req = instance->request; if (!req) { LogMsg("ERROR: process_service_registration - null request object"); return; } err = gen_rr_response(srs->RR_SRV.resrec.name, srs->RR_SRV.resrec.InterfaceID, req, &rep); if (err) { if (deliver_async_error(req, reg_service_reply, err) < 0) { abort_request(req); unlink_request(req); } return; } send_result = send_msg(rep); if (send_result == t_error || send_result == t_terminated) { abort_request(req); unlink_request(req); freeL("process_service_registration", rep); } else if (send_result == t_complete) freeL("process_service_registration", rep); else append_reply(req, rep); } static void free_service_instance(service_instance *srv) { request_state *rstate = srv->request; ExtraResourceRecord *e = srv->srs.Extras, *tmp; // clear pointers from parent struct if (rstate) { service_instance *ptr = rstate->service_registration->instances, *prev = NULL; while (ptr) { if (ptr == srv) { if (prev) prev->next = ptr->next; else rstate->service_registration->instances = ptr->next; break; } prev = ptr; ptr = ptr->next; } } while(e) { e->r.RecordContext = e; tmp = e; e = e->next; FreeExtraRR(gmDNS, &tmp->r, mStatus_MemFree); } if (srv->subtypes) { freeL("regservice_callback", srv->subtypes); srv->subtypes = NULL; } freeL("regservice_callback", srv); } static void regservice_termination_callback(void *context) { service_info *info = context; service_instance *i, *p; if (!info) { LogMsg("regservice_termination_callback context is NULL"); return; } if (!info->request) { LogMsg("regservice_termination_callback info->request is NULL"); return; } i = info->instances; while (i) { p = i; i = i->next; p->request = NULL; // clear back pointer // only safe to free memory if registration is not valid, i.e. deregister fails (which invalidates p) LogOperation("%3d: DNSServiceRegister(%##s, %u) STOP", info->request->sd, p->srs.RR_SRV.resrec.name->c, mDNSVal16(p->srs.RR_SRV.resrec.rdata->u.srv.port)); if (mDNS_DeregisterService(gmDNS, &p->srs)) free_service_instance(p); } info->request->service_registration = NULL; // clear pointer from request back to info freeL("service_info", info); } static mStatus handle_regrecord_request(request_state *rstate) { AuthRecord *rr; registered_record_entry *re; mStatus result; if (rstate->ts != t_complete) { LogMsg("ERROR: handle_regrecord_request - transfer state != t_complete"); abort_request(rstate); unlink_request(rstate); return(-1); } rr = read_rr_from_ipc_msg(rstate->msgdata, 1, 1); if (!rr) return(mStatus_BadParamErr); // allocate registration entry, link into list re = mallocL("handle_regrecord_request", sizeof(registered_record_entry)); if (!re) FatalError("ERROR: malloc"); re->key = rstate->hdr.reg_index; re->rr = rr; re->rstate = rstate; re->client_context = rstate->hdr.client_context; rr->RecordContext = re; rr->RecordCallback = regrecord_callback; re->next = rstate->reg_recs; rstate->reg_recs = re; if (!rstate->terminate) { rstate->terminate = connected_registration_termination; rstate->termination_context = rstate; } if (rr->resrec.rroriginalttl == 0) rr->resrec.rroriginalttl = DefaultTTLforRRType(rr->resrec.rrtype); LogOperation("%3d: DNSServiceRegisterRecord %s", rstate->sd, RRDisplayString(gmDNS, &rr->resrec)); result = mDNS_Register(gmDNS, rr); return(result); } static void regrecord_callback(mDNS *const m, AuthRecord * rr, mStatus result) { registered_record_entry *re = rr->RecordContext; request_state *rstate = re ? re->rstate : NULL; int len; reply_state *reply; transfer_state ts; (void)m; // Unused if (!re) { // parent struct alreadt freed by termination callback if (!result) LogMsg("Error: regrecord_callback: successful registration of orphaned record"); else { if (result != mStatus_MemFree) LogMsg("regrecord_callback: error %d received after parent termination", result); freeL("regrecord_callback", rr); } return; } // format result, add to the list for the request, including the client context in the header len = sizeof(DNSServiceFlags); len += sizeof(uint32_t); //interfaceIndex len += sizeof(DNSServiceErrorType); reply = create_reply(reg_record_reply, len, rstate); reply->mhdr->client_context = re->client_context; reply->rhdr->flags = dnssd_htonl(0); reply->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(gmDNS, rr->resrec.InterfaceID)); reply->rhdr->error = dnssd_htonl(result); if (result) { // unlink from list, free memory registered_record_entry **ptr = &re->rstate->reg_recs; while (*ptr && (*ptr) != re) ptr = &(*ptr)->next; if (!*ptr) { LogMsg("regrecord_callback - record not in list!"); return; } *ptr = (*ptr)->next; freeL("regrecord_callback", re->rr); re->rr = rr = NULL; freeL("regrecord_callback", re); re = NULL; } ts = send_msg(reply); if (ts == t_error || ts == t_terminated) { abort_request(rstate); unlink_request(rstate); } else if (ts == t_complete) freeL("regrecord_callback", reply); else if (ts == t_morecoming) append_reply(rstate, reply); // client is blocked, link reply into list } static void connected_registration_termination(void *context) { int shared; registered_record_entry *fptr, *ptr = ((request_state *)context)->reg_recs; while(ptr) { fptr = ptr; ptr = ptr->next; shared = fptr->rr->resrec.RecordType == kDNSRecordTypeShared; fptr->rr->RecordContext = NULL; mDNS_Deregister(gmDNS, fptr->rr); freeL("connected_registration_termination", fptr); } } static mStatus handle_removerecord_request(request_state *rstate) { mStatus err = mStatus_BadReferenceErr; char *ptr; service_info *srvinfo = rstate->service_registration; ptr = rstate->msgdata; get_flags(&ptr); // flags unused if (rstate->reg_recs) err = remove_record(rstate); // remove individually registered record else if (!srvinfo) LogOperation("%3d: DNSServiceRemoveRecord (bad ref)", rstate->sd); else { service_instance *i; LogOperation("%3d: DNSServiceRemoveRecord(%##s)", rstate->sd, (srvinfo->instances) ? srvinfo->instances->srs.RR_SRV.resrec.name->c : NULL); for (i = srvinfo->instances; i; i = i->next) { err = remove_extra(rstate, i); if (err && i->default_local) break; else err = mStatus_NoError; // suppress non-local default errors } } return(err); } // remove a resource record registered via DNSServiceRegisterRecord() static mStatus remove_record(request_state *rstate) { int shared; mStatus err = mStatus_UnknownErr; registered_record_entry *e, **ptr = &rstate->reg_recs; while(*ptr && (*ptr)->key != rstate->hdr.reg_index) ptr = &(*ptr)->next; if (!*ptr) { LogMsg("DNSServiceRemoveRecord - bad reference"); return mStatus_BadReferenceErr; } e = *ptr; *ptr = e->next; // unlink LogOperation("%3d: DNSServiceRemoveRecord(%#s)", rstate->sd, e->rr->resrec.name->c); shared = e->rr->resrec.RecordType == kDNSRecordTypeShared; e->rr->RecordContext = NULL; err = mDNS_Deregister(gmDNS, e->rr); if (err) { LogMsg("ERROR: remove_record, mDNS_Deregister: %ld", err); freeL("remove_record", e->rr); freeL("remove_record", e); } return err; } static mStatus remove_extra(request_state *rstate, service_instance *serv) { mStatus err = mStatus_BadReferenceErr; ExtraResourceRecord *ptr; for (ptr = serv->srs.Extras; ptr; ptr = ptr->next) { if (ptr->ClientID == rstate->hdr.reg_index) // found match return mDNS_RemoveRecordFromService(gmDNS, &serv->srs, ptr, FreeExtraRR, ptr); } return err; } // domain enumeration static void handle_enum_request(request_state *rstate) { DNSServiceFlags flags; uint32_t ifi; mDNSInterfaceID InterfaceID; char *ptr = rstate->msgdata; domain_enum_t *def, *all; enum_termination_t *term; mStatus err; int result; if (rstate->ts != t_complete) { LogMsg("ERROR: handle_enum_request - transfer state != t_complete"); abort_request(rstate); unlink_request(rstate); return; } flags = get_flags(&ptr); ifi = get_long(&ptr); InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(gmDNS, ifi); if (ifi && !InterfaceID) { deliver_error(rstate, mStatus_BadParamErr); abort_request(rstate); unlink_request(rstate); } // allocate context structures def = mallocL("handle_enum_request", sizeof(domain_enum_t)); all = mallocL("handle_enum_request", sizeof(domain_enum_t)); term = mallocL("handle_enum_request", sizeof(enum_termination_t)); if (!def || !all || !term) FatalError("ERROR: malloc"); // enumeration requires multiple questions, so we must link all the context pointers so that // necessary context can be reached from the callbacks def->rstate = rstate; all->rstate = rstate; term->def = def; term->all = all; term->rstate = rstate; rstate->termination_context = term; rstate->terminate = enum_termination_callback; def->question.QuestionContext = def; def->type = (flags & kDNSServiceFlagsRegistrationDomains) ? mDNS_DomainTypeRegistrationDefault: mDNS_DomainTypeBrowseDefault; all->question.QuestionContext = all; all->type = (flags & kDNSServiceFlagsRegistrationDomains) ? mDNS_DomainTypeRegistration : mDNS_DomainTypeBrowse; // if the caller hasn't specified an explicit interface, we use local-only to get the system-wide list. if (!InterfaceID) InterfaceID = mDNSInterface_LocalOnly; // make the calls LogOperation("%3d: DNSServiceEnumerateDomains(%X=%s)", rstate->sd, flags, (flags & kDNSServiceFlagsBrowseDomains ) ? "kDNSServiceFlagsBrowseDomains" : (flags & kDNSServiceFlagsRegistrationDomains) ? "kDNSServiceFlagsRegistrationDomains" : "<>"); err = mDNS_GetDomains(gmDNS, &all->question, all->type, NULL, InterfaceID, enum_result_callback, all); if (err == mStatus_NoError) err = mDNS_GetDomains(gmDNS, &def->question, def->type, NULL, InterfaceID, enum_result_callback, def); result = deliver_error(rstate, err); // send error *before* returning local domain if (result < 0 || err) { abort_request(rstate); unlink_request(rstate); return; } } static void enum_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord) { char domain[MAX_ESCAPED_DOMAIN_NAME]; domain_enum_t *de = question->QuestionContext; DNSServiceFlags flags = 0; reply_state *reply; (void)m; // Unused if (answer->rrtype != kDNSType_PTR) return; if (AddRecord) { flags |= kDNSServiceFlagsAdd; if (de->type == mDNS_DomainTypeRegistrationDefault || de->type == mDNS_DomainTypeBrowseDefault) flags |= kDNSServiceFlagsDefault; } ConvertDomainNameToCString(&answer->rdata->u.name, domain); // note that we do NOT propagate specific interface indexes to the client - for example, a domain we learn from // a machine's system preferences may be discovered on the LocalOnly interface, but should be browsed on the // network, so we just pass kDNSServiceInterfaceIndexAny reply = format_enumeration_reply(de->rstate, domain, flags, kDNSServiceInterfaceIndexAny, kDNSServiceErr_NoError); if (!reply) { LogMsg("ERROR: enum_result_callback, format_enumeration_reply"); return; } reply->next = NULL; append_reply(de->rstate, reply); return; } static reply_state *format_enumeration_reply(request_state *rstate, const char *domain, DNSServiceFlags flags, uint32_t ifi, DNSServiceErrorType err) { size_t len; reply_state *reply; char *data; len = sizeof(DNSServiceFlags); len += sizeof(uint32_t); len += sizeof(DNSServiceErrorType); len += strlen(domain) + 1; reply = create_reply(enumeration_reply, len, rstate); reply->rhdr->flags = dnssd_htonl(flags); reply->rhdr->ifi = dnssd_htonl(ifi); reply->rhdr->error = dnssd_htonl(err); data = reply->sdata; put_string(domain, &data); return reply; } static void enum_termination_callback(void *context) { enum_termination_t *t = context; mDNS *coredata = gmDNS; mDNS_StopGetDomains(coredata, &t->all->question); mDNS_StopGetDomains(coredata, &t->def->question); freeL("enum_termination_callback", t->all); freeL("enum_termination_callback", t->def); t->rstate->termination_context = NULL; freeL("enum_termination_callback", t); } static void handle_reconfirm_request(request_state *rstate) { AuthRecord *rr; rr = read_rr_from_ipc_msg(rstate->msgdata, 0, 1); if (!rr) return; LogOperation("%3d: DNSServiceReconfirmRecord(%##s) %s", rstate->sd, RRDisplayString(gmDNS, &rr->resrec)); mDNS_ReconfirmByValue(gmDNS, &rr->resrec); abort_request(rstate); unlink_request(rstate); freeL("handle_reconfirm_request", rr); } // setup rstate to accept new reg/dereg requests static void reset_connected_rstate(request_state *rstate) { rstate->ts = t_morecoming; rstate->hdr_bytes = 0; rstate->data_bytes = 0; if (rstate->msgbuf) freeL("reset_connected_rstate", rstate->msgbuf); rstate->msgbuf = NULL; rstate->bufsize = 0; } // returns a resource record (allocated w/ malloc) containing the data found in an IPC message // data must be in format flags, interfaceIndex, name, rrtype, rrclass, rdlen, rdata, (optional)ttl // (ttl only extracted/set if ttl argument is non-zero). returns NULL for a bad-parameter error static AuthRecord *read_rr_from_ipc_msg(char *msgbuf, int GetTTL, int validate_flags) { char *rdata, name[256]; AuthRecord *rr; DNSServiceFlags flags; uint32_t interfaceIndex; uint16_t type, class, rdlen; int storage_size; flags = get_flags(&msgbuf); if (validate_flags && !((flags & kDNSServiceFlagsShared) == kDNSServiceFlagsShared) && !((flags & kDNSServiceFlagsUnique) == kDNSServiceFlagsUnique)) { LogMsg("ERROR: Bad resource record flags (must be kDNSServiceFlagsShared or kDNSServiceFlagsUnique)"); return NULL; } interfaceIndex = get_long(&msgbuf); if (get_string(&msgbuf, name, 256) < 0) { LogMsg("ERROR: read_rr_from_ipc_msg - get_string"); return NULL; } type = get_short(&msgbuf); class = get_short(&msgbuf); rdlen = get_short(&msgbuf); if (rdlen > sizeof(RDataBody)) storage_size = rdlen; else storage_size = sizeof(RDataBody); rr = mallocL("read_rr_from_ipc_msg", sizeof(AuthRecord) - sizeof(RDataBody) + storage_size); if (!rr) FatalError("ERROR: malloc"); bzero(rr, sizeof(AuthRecord)); // ok if oversized rdata not zero'd mDNS_SetupResourceRecord(rr, mDNSNULL, mDNSPlatformInterfaceIDfromInterfaceIndex(gmDNS, interfaceIndex), type, 0, (flags & kDNSServiceFlagsShared) ? kDNSRecordTypeShared : kDNSRecordTypeUnique, mDNSNULL, mDNSNULL); if (!MakeDomainNameFromDNSNameString(rr->resrec.name, name)) { LogMsg("ERROR: bad name: %s", name); freeL("read_rr_from_ipc_msg", rr); return NULL; } if (flags & kDNSServiceFlagsAllowRemoteQuery) rr->AllowRemoteQuery = mDNStrue; rr->resrec.rrclass = class; rr->resrec.rdlength = rdlen; rr->resrec.rdata->MaxRDLength = rdlen; rdata = get_rdata(&msgbuf, rdlen); memcpy(rr->resrec.rdata->u.data, rdata, rdlen); if (GetTTL) { rr->resrec.rroriginalttl = get_long(&msgbuf); } return rr; } // generate a response message for a browse result, service registration result, or any other call with the // identical callback signature. on successful completion rep is set to point to a malloc'd reply_state struct, // and mStatus_NoError is returned. otherwise the appropriate error is returned. static mStatus gen_rr_response(domainname *servicename, mDNSInterfaceID id, request_state *request, reply_state **rep) { char *data; int len; domainlabel name; domainname type, dom; char namestr[MAX_DOMAIN_LABEL+1]; // Unescaped name: up to 63 bytes plus C-string terminating NULL. char typestr[MAX_ESCAPED_DOMAIN_NAME]; char domstr [MAX_ESCAPED_DOMAIN_NAME]; *rep = NULL; if (!DeconstructServiceName(servicename, &name, &type, &dom)) return kDNSServiceErr_Unknown; ConvertDomainLabelToCString_unescaped(&name, namestr); ConvertDomainNameToCString(&type, typestr); ConvertDomainNameToCString(&dom, domstr); // calculate reply data length len = sizeof(DNSServiceFlags); len += sizeof(uint32_t); // if index len += sizeof(DNSServiceErrorType); len += (int) (strlen(namestr) + 1); len += (int) (strlen(typestr) + 1); len += (int) (strlen(domstr) + 1); *rep = create_reply(query_reply, len, request); (*rep)->rhdr->flags = dnssd_htonl(0); (*rep)->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(gmDNS, id)); (*rep)->rhdr->error = dnssd_htonl(kDNSServiceErr_NoError); data = (*rep)->sdata; put_string(namestr, &data); put_string(typestr, &data); put_string(domstr, &data); return mStatus_NoError; } static int build_domainname_from_strings(domainname *srv, char *name, char *regtype, char *domain) { domainlabel n; domainname d, t; if (!MakeDomainLabelFromLiteralString(&n, name)) return -1; if (!MakeDomainNameFromDNSNameString(&t, regtype)) return -1; if (!MakeDomainNameFromDNSNameString(&d, domain)) return -1; if (!ConstructServiceName(srv, &n, &t, &d)) return -1; return 0; } // append a reply to the list in a request object static void append_reply(request_state *req, reply_state *rep) { reply_state *ptr; if (!req->replies) req->replies = rep; else { ptr = req->replies; while (ptr->next) ptr = ptr->next; ptr->next = rep; } rep->next = NULL; } // read_msg may be called any time when the transfer state (rs->ts) is t_morecoming. // returns the current state of the request (morecoming, error, complete, terminated.) // if there is no data on the socket, the socket will be closed and t_terminated will be returned static int read_msg(request_state *rs) { uint32_t nleft; int nread; char buf[4]; // dummy for death notification if (rs->ts == t_terminated || rs->ts == t_error) { LogMsg("ERROR: read_msg called with transfer state terminated or error"); rs->ts = t_error; return t_error; } if (rs->ts == t_complete) { // this must be death or something is wrong nread = recv(rs->sd, buf, 4, 0); if (!nread) { rs->ts = t_terminated; return t_terminated; } if (nread < 0) goto rerror; LogMsg("ERROR: read data from a completed request."); rs->ts = t_error; return t_error; } if (rs->ts != t_morecoming) { LogMsg("ERROR: read_msg called with invalid transfer state (%d)", rs->ts); rs->ts = t_error; return t_error; } if (rs->hdr_bytes < sizeof(ipc_msg_hdr)) { nleft = sizeof(ipc_msg_hdr) - rs->hdr_bytes; nread = recv(rs->sd, (char *)&rs->hdr + rs->hdr_bytes, nleft, 0); if (nread == 0) { rs->ts = t_terminated; return t_terminated; } if (nread < 0) goto rerror; rs->hdr_bytes += nread; if (rs->hdr_bytes == sizeof(ipc_msg_hdr)) { ConvertHeaderBytes(&rs->hdr); if (rs->hdr.version != VERSION) { LogMsg("ERROR: read_msg - client version 0x%08X does not match daemon version 0x%08X", rs->hdr.version, VERSION); rs->ts = t_error; return t_error; } } if (rs->hdr_bytes > sizeof(ipc_msg_hdr)) { LogMsg("ERROR: read_msg - read too many header bytes"); rs->ts = t_error; return t_error; } } // only read data if header is complete if (rs->hdr_bytes == sizeof(ipc_msg_hdr)) { if (rs->hdr.datalen == 0) // ok in removerecord requests { rs->ts = t_complete; rs->msgbuf = NULL; return t_complete; } if (!rs->msgbuf) // allocate the buffer first time through { rs->msgbuf = mallocL("read_msg", rs->hdr.datalen + MSG_PAD_BYTES); if (!rs->msgbuf) { my_perror("ERROR: malloc"); rs->ts = t_error; return t_error; } rs->msgdata = rs->msgbuf; } bzero(rs->msgbuf, rs->hdr.datalen + MSG_PAD_BYTES); nleft = rs->hdr.datalen - rs->data_bytes; nread = recv(rs->sd, rs->msgbuf + rs->data_bytes, nleft, 0); if (nread == 0) { rs->ts = t_terminated; return t_terminated; } if (nread < 0) goto rerror; rs->data_bytes += nread; if (rs->data_bytes > rs->hdr.datalen) { LogMsg("ERROR: read_msg - read too many data bytes"); rs->ts = t_error; return t_error; } } if (rs->hdr_bytes == sizeof(ipc_msg_hdr) && rs->data_bytes == rs->hdr.datalen) rs->ts = t_complete; else rs->ts = t_morecoming; return rs->ts; rerror: if (dnssd_errno() == dnssd_EWOULDBLOCK || dnssd_errno() == dnssd_EINTR) return t_morecoming; my_perror("ERROR: read_msg"); rs->ts = t_error; return t_error; } static int send_msg(reply_state *rs) { ssize_t nwriten; if (!rs->msgbuf) { LogMsg("ERROR: send_msg called with NULL message buffer"); return t_error; } if (rs->request->no_reply) //!!!KRS this behavior should be optimized if it becomes more common { rs->ts = t_complete; freeL("send_msg", rs->msgbuf); return t_complete; } ConvertHeaderBytes(rs->mhdr); nwriten = send(rs->sd, rs->msgbuf + rs->nwriten, rs->len - rs->nwriten, 0); ConvertHeaderBytes(rs->mhdr); if (nwriten < 0) { if (dnssd_errno() == dnssd_EINTR || dnssd_errno() == dnssd_EWOULDBLOCK) nwriten = 0; else { #if !defined(PLATFORM_NO_EPIPE) if (dnssd_errno() == EPIPE) { LogMsg("%3d: broken pipe - cleanup will be handled by run-loop read wakeup", rs->sd); rs->ts = t_terminated; rs->request->ts = t_terminated; return t_terminated; } else #endif { my_perror("ERROR: send\n"); rs->ts = t_error; return t_error; } } } rs->nwriten += nwriten; if (rs->nwriten == rs->len) { rs->ts = t_complete; freeL("send_msg", rs->msgbuf); } return rs->ts; } static reply_state *create_reply(reply_op_t op, size_t datalen, request_state *request) { reply_state *reply; int totallen; if ((unsigned)datalen < sizeof(reply_hdr)) { LogMsg("ERROR: create_reply - data length less than lenght of required fields"); return NULL; } totallen = (int) (datalen + sizeof(ipc_msg_hdr)); reply = mallocL("create_reply", sizeof(reply_state)); if (!reply) FatalError("ERROR: malloc"); bzero(reply, sizeof(reply_state)); reply->ts = t_morecoming; reply->sd = request->sd; reply->request = request; reply->len = totallen; reply->msgbuf = mallocL("create_reply", totallen); if (!reply->msgbuf) FatalError("ERROR: malloc"); bzero(reply->msgbuf, totallen); reply->mhdr = (ipc_msg_hdr *)reply->msgbuf; reply->rhdr = (reply_hdr *)(reply->msgbuf + sizeof(ipc_msg_hdr)); reply->sdata = reply->msgbuf + sizeof(ipc_msg_hdr) + sizeof(reply_hdr); reply->mhdr->version = VERSION; reply->mhdr->op = op; reply->mhdr->datalen = totallen - sizeof(ipc_msg_hdr); return reply; } static int deliver_error(request_state *rstate, mStatus err) { int nwritten = -1; undelivered_error_t *undeliv; err = dnssd_htonl(err); nwritten = send(rstate->sd, &err, sizeof(mStatus), 0); if (nwritten < (int)sizeof(mStatus)) { if (dnssd_errno() == dnssd_EINTR || dnssd_errno() == dnssd_EWOULDBLOCK) nwritten = 0; if (nwritten < 0) { my_perror("ERROR: send - unable to deliver error to client"); return(-1); } else { //client blocked - store result and come backr undeliv = mallocL("deliver_error", sizeof(undelivered_error_t)); if (!undeliv) FatalError("ERROR: malloc"); undeliv->err = err; undeliv->nwritten = nwritten; undeliv->sd = rstate->sd; rstate->u_err = undeliv; return 0; } } return 0; } // returns 0 on success, -1 if send is incomplete, or on terminal failure (request is aborted) static transfer_state send_undelivered_error(request_state *rs) { int nwritten; nwritten = send(rs->u_err->sd, (char *)(&rs->u_err->err) + rs->u_err->nwritten, sizeof(mStatus) - rs->u_err->nwritten, 0); if (nwritten < 0) { if (dnssd_errno() == dnssd_EINTR || dnssd_errno() == dnssd_EWOULDBLOCK) nwritten = 0; else { my_perror("ERROR: send - unable to deliver error to client\n"); return t_error; } } if ((unsigned int)(nwritten + rs->u_err->nwritten) >= sizeof(mStatus)) { freeL("send_undelivered_error", rs->u_err); rs->u_err = NULL; return t_complete; } rs->u_err->nwritten += nwritten; return t_morecoming; } // send bogus data along with an error code to the app callback // returns 0 on success (linking reply into list of not fully delivered), // -1 on failure (request should be aborted) static int deliver_async_error(request_state *rs, reply_op_t op, mStatus err) { int len; reply_state *reply; transfer_state ts; if (rs->no_reply) return 0; len = 256; // long enough for any reply handler to read all args w/o buffer overrun reply = create_reply(op, len, rs); reply->rhdr->error = dnssd_htonl(err); ts = send_msg(reply); if (ts == t_error || ts == t_terminated) { freeL("deliver_async_error", reply); return -1; } else if (ts == t_complete) freeL("deliver_async_error", reply); else if (ts == t_morecoming) append_reply(rs, reply); // client is blocked, link reply into list return 0; } static void abort_request(request_state *rs) { reply_state *rep, *ptr; if (rs->terminate) rs->terminate(rs->termination_context); // terminate field may not be set yet if (rs->msgbuf) freeL("abort_request", rs->msgbuf); LogOperation("%3d: Removing FD", rs->sd); udsSupportRemoveFDFromEventLoop(rs->sd); // Note: This also closes file descriptor rs->sd for us rs->sd = dnssd_InvalidSocket; // free pending replies rep = rs->replies; while(rep) { if (rep->msgbuf) freeL("abort_request", rep->msgbuf); ptr = rep; rep = rep->next; freeL("abort_request", ptr); } if (rs->u_err) { freeL("abort_request", rs->u_err); rs->u_err = NULL; } } static void unlink_request(request_state *rs) { request_state *ptr; if (rs == all_requests) { all_requests = all_requests->next; freeL("unlink_request", rs); return; } for(ptr = all_requests; ptr->next; ptr = ptr->next) if (ptr->next == rs) { ptr->next = rs->next; freeL("unlink_request", rs); return; } } //hack to search-replace perror's to LogMsg's static void my_perror(char *errmsg) { LogMsg("%s: %s", errmsg, dnssd_strerror(dnssd_errno())); } // check that the message delivered by the client is sufficiently long to extract the required data from the buffer // without overrunning it. // returns 0 on success, -1 on error. static int validate_message(request_state *rstate) { uint32_t min_size; switch(rstate->hdr.op) { case resolve_request: min_size = sizeof(DNSServiceFlags) + // flags sizeof(uint32_t) + // interface (3 * sizeof(char)); // name, regtype, domain break; case query_request: min_size = sizeof(DNSServiceFlags) + // flags sizeof(uint32_t) + // interface sizeof(char) + // fullname (2 * sizeof(uint16_t)); // type, class break; case browse_request: min_size = sizeof(DNSServiceFlags) + // flags sizeof(uint32_t) + // interface (2 * sizeof(char)); // regtype, domain break; case reg_service_request: min_size = sizeof(DNSServiceFlags) + // flags sizeof(uint32_t) + // interface (4 * sizeof(char)) + // name, type, domain, host (2 * sizeof(uint16_t)); // port, textlen break; case enumeration_request: min_size = sizeof(DNSServiceFlags) + // flags sizeof(uint32_t); // interface break; case reg_record_request: min_size = sizeof(DNSServiceFlags) + // flags sizeof(uint32_t) + // interface sizeof(char) + // fullname (3 * sizeof(uint16_t)) + // type, class, rdlen sizeof(uint32_t); // ttl break; case add_record_request: min_size = sizeof(DNSServiceFlags) + // flags (2 * sizeof(uint16_t)) + // type, rdlen sizeof(uint32_t); // ttl break; case update_record_request: min_size = sizeof(DNSServiceFlags) + // flags sizeof(uint16_t) + // rdlen sizeof(uint32_t); // ttl break; case remove_record_request: min_size = sizeof(DNSServiceFlags); // flags break; case reconfirm_record_request: min_size=sizeof(DNSServiceFlags) + // flags sizeof(uint32_t) + // interface sizeof(char) + // fullname (3 * sizeof(uint16_t)); // type, class, rdlen break; case setdomain_request: min_size = sizeof(DNSServiceFlags) + sizeof(char); // flags + domain break; default: LogMsg("ERROR: validate_message - unsupported request type: %d", rstate->hdr.op); return -1; } return (rstate->data_bytes >= min_size ? 0 : -1); } static uint32_t dnssd_htonl(uint32_t l) { uint32_t ret; char * data; data = (char*) &ret; put_long(l, &data); return ret; } #if defined(_WIN32) static char * win32_strerror(int inErrorCode) { static char buffer[1024]; DWORD n; memset(buffer, 0, sizeof(buffer)); n = FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, (DWORD) inErrorCode, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), buffer, sizeof( buffer ), NULL ); if( n > 0 ) { // Remove any trailing CR's or LF's since some messages have them. while( ( n > 0 ) && isspace( ( (unsigned char *) buffer)[ n - 1 ] ) ) { buffer[ --n ] = '\0'; } } return buffer; } #endif