1/* -*- Mode: C; tab-width: 4 -*-
2 *
3 * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#include <stdio.h>				// For printf()
19#include <stdlib.h>				// For exit() etc.
20#include <string.h>				// For strlen() etc.
21#include <unistd.h>				// For select()
22#include <signal.h>				// For SIGINT, SIGTERM
23#include <errno.h>				// For errno, EINTR
24#include <netinet/in.h>			// For INADDR_NONE
25#include <arpa/inet.h>			// For inet_addr()
26#include <netdb.h>				// For gethostbyname()
27
28#include "mDNSEmbeddedAPI.h"	// Defines the interface to the client layer above
29#include "mDNSPosix.h"			// Defines the specific types needed to run mDNS on this platform
30#include "ExampleClientApp.h"
31
32// Compatibility workaround: Solaris 2.5 has no INADDR_NONE
33#ifndef	INADDR_NONE
34#define	INADDR_NONE	(mDNSu32)0xffffffff
35#endif
36
37//*************************************************************************************************************
38// Globals
39static mDNS mDNSStorage;       // mDNS core uses this to store its globals
40static mDNS_PlatformSupport PlatformStorage;  // Stores this platform's globals
41mDNSexport const char ProgramName[] = "mDNSProxyResponderPosix";
42
43//*************************************************************************************************************
44// Proxy Host Registration
45
46typedef struct
47	{
48	mDNSv4Addr ip;
49	domainlabel hostlabel;		// Conforms to standard DNS letter-digit-hyphen host name rules
50	AuthRecord RR_A;		// 'A' (address) record for our ".local" name
51	AuthRecord RR_PTR;		// PTR (reverse lookup) record
52	} ProxyHost;
53
54mDNSlocal void HostNameCallback(mDNS *const m, AuthRecord *const rr, mStatus result)
55	{
56	ProxyHost *f = (ProxyHost*)rr->RecordContext;
57	if (result == mStatus_NoError)
58		debugf("Host name successfully registered: %##s", rr->resrec.name->c);
59	else
60		{
61		debugf("Host name conflict for %##s", rr->resrec.name->c);
62		mDNS_Deregister(m, &f->RR_A);
63		mDNS_Deregister(m, &f->RR_PTR);
64		exit(-1);
65		}
66	}
67
68mDNSlocal mStatus mDNS_RegisterProxyHost(mDNS *m, ProxyHost *p)
69	{
70	char buffer[32];
71
72	mDNS_SetupResourceRecord(&p->RR_A,   mDNSNULL, mDNSInterface_Any, kDNSType_A,   60, kDNSRecordTypeUnique,      AuthRecordAny, HostNameCallback, p);
73	mDNS_SetupResourceRecord(&p->RR_PTR, mDNSNULL, mDNSInterface_Any, kDNSType_PTR, 60, kDNSRecordTypeKnownUnique, AuthRecordAny, HostNameCallback, p);
74
75	p->RR_A.namestorage.c[0] = 0;
76	AppendDomainLabel(&p->RR_A.namestorage, &p->hostlabel);
77	AppendLiteralLabelString(&p->RR_A.namestorage, "local");
78
79	// Note: This is reverse order compared to a normal dotted-decimal IP address, so we can't use our customary "%.4a" format code
80	mDNS_snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d.in-addr.arpa.", p->ip.b[3], p->ip.b[2], p->ip.b[1], p->ip.b[0]);
81	MakeDomainNameFromDNSNameString(&p->RR_PTR.namestorage, buffer);
82	p->RR_PTR.ForceMCast = mDNStrue; // This PTR points to our dot-local name, so don't ever try to write it into a uDNS server
83
84	p->RR_A.  resrec.rdata->u.ipv4 = p->ip;
85	AssignDomainName(&p->RR_PTR.resrec.rdata->u.name, p->RR_A.resrec.name);
86
87	mDNS_Register(m, &p->RR_A);
88	mDNS_Register(m, &p->RR_PTR);
89
90	debugf("Made Proxy Host Records for %##s", p->RR_A.resrec.name->c);
91
92	return(mStatus_NoError);
93	}
94
95//*************************************************************************************************************
96// Service Registration
97
98// This sample ServiceCallback just calls mDNS_RenameAndReregisterService to automatically pick a new
99// unique name for the service. For a device such as a printer, this may be appropriate.
100// For a device with a user interface, and a screen, and a keyboard, the appropriate
101// response may be to prompt the user and ask them to choose a new name for the service.
102mDNSlocal void ServiceCallback(mDNS *const m, ServiceRecordSet *const sr, mStatus result)
103	{
104	switch (result)
105		{
106		case mStatus_NoError:      debugf("Callback: %##s Name Registered",    sr->RR_SRV.resrec.name->c); break;
107		case mStatus_NameConflict: debugf("Callback: %##s Name Conflict",      sr->RR_SRV.resrec.name->c); break;
108		case mStatus_MemFree:      debugf("Callback: %##s Memory Free",        sr->RR_SRV.resrec.name->c); break;
109		default:                   debugf("Callback: %##s Unknown Result %ld", sr->RR_SRV.resrec.name->c, result); break;
110		}
111
112	if (result == mStatus_NoError)
113		{
114		char buffer[MAX_ESCAPED_DOMAIN_NAME];
115		ConvertDomainNameToCString(sr->RR_SRV.resrec.name, buffer);
116		printf("Service %s now registered and active\n", buffer);
117		}
118
119	if (result == mStatus_NameConflict)
120		{
121		char buffer1[MAX_ESCAPED_DOMAIN_NAME], buffer2[MAX_ESCAPED_DOMAIN_NAME];
122		ConvertDomainNameToCString(sr->RR_SRV.resrec.name, buffer1);
123		mDNS_RenameAndReregisterService(m, sr, mDNSNULL);
124		ConvertDomainNameToCString(sr->RR_SRV.resrec.name, buffer2);
125		printf("Name Conflict! %s renamed as %s\n", buffer1, buffer2);
126		}
127	}
128
129// RegisterService() is a simple wrapper function which takes C string
130// parameters, converts them to domainname parameters, and calls mDNS_RegisterService()
131mDNSlocal void RegisterService(mDNS *m, ServiceRecordSet *recordset,
132	const char name[], const char type[], const char domain[],
133	const domainname *host, mDNSu16 PortAsNumber, int argc, char **argv)
134	{
135	domainlabel n;
136	domainname t, d;
137	unsigned char txtbuffer[1024], *bptr = txtbuffer;
138	char buffer[MAX_ESCAPED_DOMAIN_NAME];
139
140	MakeDomainLabelFromLiteralString(&n, name);
141	MakeDomainNameFromDNSNameString(&t, type);
142	MakeDomainNameFromDNSNameString(&d, domain);
143	while (argc)
144		{
145		int len = strlen(argv[0]);
146		if (len > 255 || bptr + 1 + len >= txtbuffer + sizeof(txtbuffer)) break;
147		printf("STR: %s\n", argv[0]);
148		bptr[0] = len;
149		strcpy((char*)(bptr+1), argv[0]);
150		bptr += 1 + len;
151		argc--;
152		argv++;
153		}
154
155	mDNS_RegisterService(m, recordset,
156		&n, &t, &d,					// Name, type, domain
157		host, mDNSOpaque16fromIntVal(PortAsNumber),
158		txtbuffer, bptr-txtbuffer,	// TXT data, length
159		mDNSNULL, 0,				// Subtypes
160		mDNSInterface_Any,			// Interface ID
161		ServiceCallback, mDNSNULL, 0);	// Callback, context, flags
162
163	ConvertDomainNameToCString(recordset->RR_SRV.resrec.name, buffer);
164	printf("Made Service Records for %s\n", buffer);
165	}
166
167//*************************************************************************************************************
168// Service non-existence assertion
169// (claiming a service name without actually providing a service at that name, to prevent anyone else using that name)
170// This is useful to avoid confusion between similar services
171// e.g. A printer that implements IPP printing service using the name "My Printer", but doesn't implement LPR service,
172// should also claim the LPR service name "My Printer" to stop a different printer offering LPR service under the same name,
173// since it would be confusing to users to have two equivalent services with the same name.
174
175mDNSlocal void NoSuchServiceCallback(mDNS *const m, AuthRecord *const rr, mStatus result)
176	{
177	const domainname *proxyhostname = (const domainname *)rr->RecordContext;
178	switch (result)
179		{
180		case mStatus_NoError:      debugf("Callback: %##s Name Registered",    rr->resrec.name->c); break;
181		case mStatus_NameConflict: debugf("Callback: %##s Name Conflict",      rr->resrec.name->c); break;
182		case mStatus_MemFree:      debugf("Callback: %##s Memory Free",        rr->resrec.name->c); break;
183		default:                   debugf("Callback: %##s Unknown Result %ld", rr->resrec.name->c, result); break;
184		}
185
186	if (result == mStatus_NoError)
187		{
188		char buffer[MAX_ESCAPED_DOMAIN_NAME];
189		ConvertDomainNameToCString(rr->resrec.name, buffer);
190		printf("Non-existence assertion %s now registered and active\n", buffer);
191		}
192
193	if (result == mStatus_NameConflict)
194		{
195		domainlabel n;
196		domainname t, d;
197		char buffer1[MAX_ESCAPED_DOMAIN_NAME], buffer2[MAX_ESCAPED_DOMAIN_NAME];
198		ConvertDomainNameToCString(rr->resrec.name, buffer1);
199		DeconstructServiceName(rr->resrec.name, &n, &t, &d);
200		IncrementLabelSuffix(&n, mDNStrue);
201		mDNS_RegisterNoSuchService(m, rr, &n, &t, &d, proxyhostname, mDNSInterface_Any, NoSuchServiceCallback, mDNSNULL, mDNSfalse);
202		ConvertDomainNameToCString(rr->resrec.name, buffer2);
203		printf("Name Conflict! %s renamed as %s\n", buffer1, buffer2);
204		}
205	}
206
207mDNSlocal void RegisterNoSuchService(mDNS *m, AuthRecord *const rr, domainname *proxyhostname,
208	const char name[], const char type[], const char domain[])
209	{
210	domainlabel n;
211	domainname t, d;
212	char buffer[MAX_ESCAPED_DOMAIN_NAME];
213	MakeDomainLabelFromLiteralString(&n, name);
214	MakeDomainNameFromDNSNameString(&t, type);
215	MakeDomainNameFromDNSNameString(&d, domain);
216	mDNS_RegisterNoSuchService(m, rr, &n, &t, &d, proxyhostname, mDNSInterface_Any, NoSuchServiceCallback, proxyhostname, mDNSfalse);
217	ConvertDomainNameToCString(rr->resrec.name, buffer);
218	printf("Made Non-existence Record for %s\n", buffer);
219	}
220
221//*************************************************************************************************************
222// Main
223
224mDNSexport int main(int argc, char **argv)
225	{
226	mStatus			status;
227	sigset_t		signals;
228
229	if (argc < 3) goto usage;
230
231	status = mDNS_Init(&mDNSStorage, &PlatformStorage,
232		mDNS_Init_NoCache, mDNS_Init_ZeroCacheSize,
233		mDNS_Init_DontAdvertiseLocalAddresses,
234		mDNS_Init_NoInitCallback, mDNS_Init_NoInitCallbackContext);
235	if (status) { fprintf(stderr, "Daemon start: mDNS_Init failed %d\n", (int)status); return(status); }
236
237	mDNSPosixListenForSignalInEventLoop(SIGINT);
238	mDNSPosixListenForSignalInEventLoop(SIGTERM);
239
240	if (!strcmp(argv[1], "-"))
241		{
242		domainname proxyhostname;
243		AuthRecord proxyrecord;
244		if (argc < 5) goto usage;
245		proxyhostname.c[0] = 0;
246		AppendLiteralLabelString(&proxyhostname, argv[2]);
247		AppendLiteralLabelString(&proxyhostname, "local");
248		RegisterNoSuchService(&mDNSStorage, &proxyrecord, &proxyhostname, argv[3], argv[4], "local.");
249		}
250	else
251		{
252		ProxyHost proxyhost;
253		ServiceRecordSet proxyservice;
254
255		proxyhost.ip.NotAnInteger = inet_addr(argv[1]);
256		if (proxyhost.ip.NotAnInteger == INADDR_NONE)	// INADDR_NONE is 0xFFFFFFFF
257			{
258			struct hostent *h = gethostbyname(argv[1]);
259			if (h) proxyhost.ip.NotAnInteger = *(long*)h->h_addr;
260			}
261		if (proxyhost.ip.NotAnInteger == INADDR_NONE)	// INADDR_NONE is 0xFFFFFFFF
262			{
263			fprintf(stderr, "%s is not valid host address\n", argv[1]);
264			return(-1);
265			}
266
267		MakeDomainLabelFromLiteralString(&proxyhost.hostlabel, argv[2]);
268
269		mDNS_RegisterProxyHost(&mDNSStorage, &proxyhost);
270
271		if (argc >=6)
272			RegisterService(&mDNSStorage, &proxyservice, argv[3], argv[4], "local.",
273							proxyhost.RR_A.resrec.name, atoi(argv[5]), argc-6, &argv[6]);
274		}
275
276	do
277		{
278		struct timeval	timeout = { 0x3FFFFFFF, 0 };	// wait until SIGINT or SIGTERM
279		mDNSBool		gotSomething;
280		mDNSPosixRunEventLoopOnce(&mDNSStorage, &timeout, &signals, &gotSomething);
281		}
282	while ( !( sigismember( &signals, SIGINT) || sigismember( &signals, SIGTERM)));
283
284	mDNS_Close(&mDNSStorage);
285
286	return(0);
287
288usage:
289	fprintf(stderr, "%s ip hostlabel [srvname srvtype port txt [txt ...]]\n", argv[0]);
290	fprintf(stderr, "ip        Real IP address (or valid host name) of the host where the service actually resides\n");
291	fprintf(stderr, "hostlabel First label of the dot-local host name to create for this host, e.g. \"foo\" for \"foo.local.\"\n");
292	fprintf(stderr, "srvname   Descriptive name of service, e.g. \"Stuart's Ink Jet Printer\"\n");
293	fprintf(stderr, "srvtype   IANA service type, e.g. \"_ipp._tcp\" or \"_ssh._tcp\", etc.\n");
294	fprintf(stderr, "port      Port number where the service resides (1-65535)\n");
295	fprintf(stderr, "txt       Additional name/value pairs specified in service definition, e.g. \"pdl=application/postscript\"\n");
296	fprintf(stderr, "e.g. %s 169.254.12.34 thehost                                (just create a dot-local host name)\n", argv[0]);
297	fprintf(stderr, "or   %s 169.254.12.34 thehost \"My Printer\" _printer._tcp. 515 rp=lpt1 pdl=application/postscript\n", argv[0]);
298	fprintf(stderr, "or   %s -             thehost \"My Printer\" _printer._tcp.           (assertion of non-existence)\n", argv[0]);
299	return(-1);
300	}
301