1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "ZeroconfMDNS.h"
10 #include <arpa/inet.h>
11 
12 #include <string>
13 #include <sstream>
14 #include "threads/SingleLock.h"
15 #include "utils/log.h"
16 #include "dialogs/GUIDialogKaiToast.h"
17 #include "guilib/LocalizeStrings.h"
18 #if defined(TARGET_WINDOWS)
19 #include "platform/win32/WIN32Util.h"
20 #endif //TARGET_WINDOWS
21 
22 #if defined(HAS_MDNS_EMBEDDED)
23 #include <mDnsEmbedded.h>
24 #endif //HAS_MDNS_EMBEDDED
25 
26 extern HWND g_hWnd;
27 
Process()28 void CZeroconfMDNS::Process()
29 {
30 #if defined(HAS_MDNS_EMBEDDED)
31   CLog::Log(LOGDEBUG, "ZeroconfEmbedded - processing...");
32   struct timeval timeout;
33   timeout.tv_sec = 1;
34   while (( !m_bStop ))
35     embedded_mDNSmainLoop(timeout);
36 #endif //HAS_MDNS_EMBEDDED
37 
38 }
39 
40 
CZeroconfMDNS()41 CZeroconfMDNS::CZeroconfMDNS()  : CThread("ZeroconfEmbedded")
42 {
43   m_service = NULL;
44 #if defined(HAS_MDNS_EMBEDDED)
45   embedded_mDNSInit();
46   Create();
47 #endif //HAS_MDNS_EMBEDDED
48 }
49 
~CZeroconfMDNS()50 CZeroconfMDNS::~CZeroconfMDNS()
51 {
52   doStop();
53 #if defined(HAS_MDNS_EMBEDDED)
54   StopThread();
55   embedded_mDNSExit();
56 #endif //HAS_MDNS_EMBEDDED
57 }
58 
IsZCdaemonRunning()59 bool CZeroconfMDNS::IsZCdaemonRunning()
60 {
61 #if !defined(HAS_MDNS_EMBEDDED)
62   uint32_t version;
63   uint32_t size = sizeof(version);
64   DNSServiceErrorType err = DNSServiceGetProperty(kDNSServiceProperty_DaemonVersion, &version, &size);
65   if(err != kDNSServiceErr_NoError)
66   {
67     CLog::Log(LOGERROR, "ZeroconfMDNS: Zeroconf can't be started probably because Apple's Bonjour Service isn't installed. You can get it by either installing Itunes or Apple's Bonjour Print Service for Windows (http://support.apple.com/kb/DL999)");
68     CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(34300), g_localizeStrings.Get(34301), 10000, true);
69     return false;
70   }
71   CLog::Log(LOGDEBUG, "ZeroconfMDNS:Bonjour version is %d.%d", version / 10000, version / 100 % 100);
72 #endif //!HAS_MDNS_EMBEDDED
73   return true;
74 }
75 
76 //methods to implement for concrete implementations
doPublishService(const std::string & fcr_identifier,const std::string & fcr_type,const std::string & fcr_name,unsigned int f_port,const std::vector<std::pair<std::string,std::string>> & txt)77 bool CZeroconfMDNS::doPublishService(const std::string& fcr_identifier,
78                       const std::string& fcr_type,
79                       const std::string& fcr_name,
80                       unsigned int f_port,
81                       const std::vector<std::pair<std::string, std::string> >& txt)
82 {
83   DNSServiceRef netService = NULL;
84   TXTRecordRef txtRecord;
85   DNSServiceErrorType err;
86   TXTRecordCreate(&txtRecord, 0, NULL);
87 
88 #if !defined(HAS_MDNS_EMBEDDED)
89   CSingleLock lock(m_data_guard);
90   if(m_service == NULL)
91   {
92     err = DNSServiceCreateConnection(&m_service);
93     if (err != kDNSServiceErr_NoError)
94     {
95       CLog::Log(LOGERROR, "ZeroconfMDNS: DNSServiceCreateConnection failed with error = %ld", (int) err);
96       return false;
97     }
98 #ifdef TARGET_WINDOWS_STORE
99     CLog::Log(LOGERROR, "ZeroconfMDNS: WSAAsyncSelect not yet supported for TARGET_WINDOWS_STORE");
100 #else
101     err = WSAAsyncSelect( (SOCKET) DNSServiceRefSockFD( m_service ), g_hWnd, BONJOUR_EVENT, FD_READ | FD_CLOSE );
102     if (err != kDNSServiceErr_NoError)
103       CLog::Log(LOGERROR, "ZeroconfMDNS: WSAAsyncSelect failed with error = %ld", (int) err);
104 #endif
105   }
106 #endif //!HAS_MDNS_EMBEDDED
107 
108   CLog::Log(LOGDEBUG, "ZeroconfMDNS: identifier: %s type: %s name:%s port:%i", fcr_identifier.c_str(), fcr_type.c_str(), fcr_name.c_str(), f_port);
109 
110   //add txt records
111   if(!txt.empty())
112   {
113     for (const auto& it : txt)
114     {
115       CLog::Log(LOGDEBUG, "ZeroconfMDNS: key:%s, value:%s", it.first.c_str(), it.second.c_str());
116       uint8_t txtLen = (uint8_t)strlen(it.second.c_str());
117       TXTRecordSetValue(&txtRecord, it.first.c_str(), txtLen, it.second.c_str());
118     }
119   }
120 
121   {
122     CSingleLock lock(m_data_guard);
123     netService = m_service;
124     err = DNSServiceRegister(&netService, kDNSServiceFlagsShareConnection, 0, fcr_name.c_str(), fcr_type.c_str(), NULL, NULL, htons(f_port), TXTRecordGetLength(&txtRecord), TXTRecordGetBytesPtr(&txtRecord), registerCallback, NULL);
125   }
126 
127   if (err != kDNSServiceErr_NoError)
128   {
129     // Something went wrong so lets clean up.
130     if (netService)
131       DNSServiceRefDeallocate(netService);
132 
133     CLog::Log(LOGERROR, "ZeroconfMDNS: DNSServiceRegister returned (error = %ld)", (int) err);
134   }
135   else
136   {
137     CSingleLock lock(m_data_guard);
138     struct tServiceRef newService;
139     newService.serviceRef = netService;
140     newService.txtRecordRef = txtRecord;
141     newService.updateNumber = 0;
142     m_services.insert(make_pair(fcr_identifier, newService));
143   }
144 
145   return err == kDNSServiceErr_NoError;
146 }
147 
doForceReAnnounceService(const std::string & fcr_identifier)148 bool CZeroconfMDNS::doForceReAnnounceService(const std::string& fcr_identifier)
149 {
150   bool ret = false;
151   CSingleLock lock(m_data_guard);
152   tServiceMap::iterator it = m_services.find(fcr_identifier);
153   if(it != m_services.end())
154   {
155     // for force announcing a service with mdns we need
156     // to change a txt record - so we diddle between
157     // even and odd dummy records here
158     if ( (it->second.updateNumber % 2) == 0)
159       TXTRecordSetValue(&it->second.txtRecordRef, "xbmcdummy", strlen("evendummy"), "evendummy");
160     else
161       TXTRecordSetValue(&it->second.txtRecordRef, "xbmcdummy", strlen("odddummy"), "odddummy");
162     it->second.updateNumber++;
163 
164     if (DNSServiceUpdateRecord(it->second.serviceRef, NULL, 0, TXTRecordGetLength(&it->second.txtRecordRef), TXTRecordGetBytesPtr(&it->second.txtRecordRef), 0) ==  kDNSServiceErr_NoError)
165       ret = true;
166   }
167   return ret;
168 }
169 
doRemoveService(const std::string & fcr_ident)170 bool CZeroconfMDNS::doRemoveService(const std::string& fcr_ident)
171 {
172   CSingleLock lock(m_data_guard);
173   tServiceMap::iterator it = m_services.find(fcr_ident);
174   if(it != m_services.end())
175   {
176     DNSServiceRefDeallocate(it->second.serviceRef);
177     TXTRecordDeallocate(&it->second.txtRecordRef);
178     m_services.erase(it);
179     CLog::Log(LOGDEBUG, "ZeroconfMDNS: Removed service %s", fcr_ident.c_str());
180     return true;
181   }
182   else
183     return false;
184 }
185 
doStop()186 void CZeroconfMDNS::doStop()
187 {
188   {
189     CSingleLock lock(m_data_guard);
190     CLog::Log(LOGDEBUG, "ZeroconfMDNS: Shutdown services");
191     for (auto& it : m_services)
192     {
193       DNSServiceRefDeallocate(it.second.serviceRef);
194       TXTRecordDeallocate(&it.second.txtRecordRef);
195       CLog::Log(LOGDEBUG, "ZeroconfMDNS: Removed service %s", it.first.c_str());
196     }
197     m_services.clear();
198   }
199   {
200     CSingleLock lock(m_data_guard);
201 #if defined(TARGET_WINDOWS_STORE)
202     CLog::Log(LOGERROR, "ZeroconfMDNS: WSAAsyncSelect not yet supported for TARGET_WINDOWS_STORE");
203 #else
204     WSAAsyncSelect( (SOCKET) DNSServiceRefSockFD( m_service ), g_hWnd, BONJOUR_EVENT, 0 );
205 #endif //TARGET_WINDOWS
206 
207     if (m_service)
208       DNSServiceRefDeallocate(m_service);
209     m_service = NULL;
210   }
211 }
212 
registerCallback(DNSServiceRef sdref,const DNSServiceFlags flags,DNSServiceErrorType errorCode,const char * name,const char * regtype,const char * domain,void * context)213 void DNSSD_API CZeroconfMDNS::registerCallback(DNSServiceRef sdref, const DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context)
214 {
215   (void)sdref;    // Unused
216   (void)flags;    // Unused
217   (void)context;  // Unused
218 
219   if (errorCode == kDNSServiceErr_NoError)
220   {
221     if (flags & kDNSServiceFlagsAdd)
222       CLog::Log(LOGDEBUG, "ZeroconfMDNS: %s.%s%s now registered and active", name, regtype, domain);
223     else
224       CLog::Log(LOGDEBUG, "ZeroconfMDNS: %s.%s%s registration removed", name, regtype, domain);
225   }
226   else if (errorCode == kDNSServiceErr_NameConflict)
227      CLog::Log(LOGDEBUG, "ZeroconfMDNS: %s.%s%s Name in use, please choose another", name, regtype, domain);
228   else
229     CLog::Log(LOGDEBUG, "ZeroconfMDNS: %s.%s%s error code %d", name, regtype, domain, errorCode);
230 }
231 
ProcessResults()232 void CZeroconfMDNS::ProcessResults()
233 {
234   CSingleLock lock(m_data_guard);
235   DNSServiceErrorType err = DNSServiceProcessResult(m_service);
236   if (err != kDNSServiceErr_NoError)
237     CLog::Log(LOGERROR, "ZeroconfMDNS: DNSServiceProcessResult returned (error = %ld)", (int) err);
238 }
239 
240