1 /* -*- Mode: C; tab-width: 4 -*-
3 * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 // In Mac OS X 10.5 and later trying to use the daemon function gives a “‘daemon’ is deprecated”
20 // error, which prevents compilation because we build with "-Werror".
21 // Since this is supposed to be portable cross-platform code, we don't care that daemon is
22 // deprecated on Mac OS X 10.5, so we use this preprocessor trick to eliminate the error message.
23 #define daemon yes_we_know_that_daemon_is_deprecated_in_os_x_10_5_thankyou
27 #include <stdio.h> // For printf()
28 #include <stdlib.h> // For exit() etc.
29 #include <string.h> // For strlen() etc.
30 #include <unistd.h> // For select()
31 #include <errno.h> // For errno, EINTR
37 extern int daemon(int, int);
40 #include "mDNSEmbeddedAPI.h" // Defines the interface to the client layer above
41 #include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform
42 #include "mDNSUNP.h" // For daemon()
44 #if COMPILER_LIKES_PRAGMA_MARK
45 #pragma mark ***** Globals
48 static mDNS mDNSStorage; // mDNS core uses this to store its globals
49 static mDNS_PlatformSupport PlatformStorage; // Stores this platform's globals
51 mDNSexport const char ProgramName[] = "mDNSResponderPosix";
53 static const char *gProgramName = ProgramName;
55 #if COMPILER_LIKES_PRAGMA_MARK
56 #pragma mark ***** Signals
59 static volatile mDNSBool gReceivedSigUsr1;
60 static volatile mDNSBool gReceivedSigHup;
61 static volatile mDNSBool gStopNow;
63 // We support 4 signals.
65 // o SIGUSR1 toggles verbose mode on and off in debug builds
66 // o SIGHUP triggers the program to re-read its preferences.
67 // o SIGINT causes an orderly shutdown of the program.
68 // o SIGQUIT causes a somewhat orderly shutdown (direct but dangerous)
69 // o SIGKILL kills us dead (easy to implement :-)
71 // There are fatal race conditions in our signal handling, but there's not much
72 // we can do about them while remaining within the Posix space. Specifically,
73 // if a signal arrives after we test the globals its sets but before we call
74 // select, the signal will be dropped. The user will have to send the signal
75 // again. Unfortunately, Posix does not have a "sigselect" to atomically
76 // modify the signal mask and start a select.
78 static void HandleSigUsr1(int sigraised)
79 // If we get a SIGUSR1 we toggle the state of the
82 assert(sigraised == SIGUSR1);
83 gReceivedSigUsr1 = mDNStrue;
86 static void HandleSigHup(int sigraised)
87 // A handler for SIGHUP that causes us to break out of the
88 // main event loop when the user kill 1's us. This has the
89 // effect of triggered the main loop to deregister the
90 // current services and re-read the preferences.
92 assert(sigraised == SIGHUP);
93 gReceivedSigHup = mDNStrue;
96 static void HandleSigInt(int sigraised)
97 // A handler for SIGINT that causes us to break out of the
98 // main event loop when the user types ^C. This has the
99 // effect of quitting the program.
101 assert(sigraised == SIGINT);
103 if (gMDNSPlatformPosixVerboseLevel > 0) {
104 fprintf(stderr, "\nSIGINT\n");
109 static void HandleSigQuit(int sigraised)
110 // If we get a SIGQUIT the user is desperate and we
111 // just call mDNS_Close directly. This is definitely
112 // not safe (because it could reenter mDNS), but
113 // we presume that the user has already tried the safe
116 assert(sigraised == SIGQUIT);
118 if (gMDNSPlatformPosixVerboseLevel > 0) {
119 fprintf(stderr, "\nSIGQUIT\n");
121 mDNS_Close(&mDNSStorage);
125 #if COMPILER_LIKES_PRAGMA_MARK
126 #pragma mark ***** Parameter Checking
129 static mDNSBool CheckThatRichTextNameIsUsable(const char *richTextName, mDNSBool printExplanation)
130 // Checks that richTextName is reasonable
131 // label and, if it isn't and printExplanation is true, prints
132 // an explanation of why not.
134 mDNSBool result = mDNStrue;
135 if (result && strlen(richTextName) > 63) {
136 if (printExplanation) {
138 "%s: Service name is too long (must be 63 characters or less)\n",
143 if (result && richTextName[0] == 0) {
144 if (printExplanation) {
145 fprintf(stderr, "%s: Service name can't be empty\n", gProgramName);
152 static mDNSBool CheckThatServiceTypeIsUsable(const char *serviceType, mDNSBool printExplanation)
153 // Checks that serviceType is a reasonable service type
154 // label and, if it isn't and printExplanation is true, prints
155 // an explanation of why not.
160 if (result && strlen(serviceType) > 63) {
161 if (printExplanation) {
163 "%s: Service type is too long (must be 63 characters or less)\n",
168 if (result && serviceType[0] == 0) {
169 if (printExplanation) {
171 "%s: Service type can't be empty\n",
179 static mDNSBool CheckThatPortNumberIsUsable(long portNumber, mDNSBool printExplanation)
180 // Checks that portNumber is a reasonable port number
181 // and, if it isn't and printExplanation is true, prints
182 // an explanation of why not.
187 if (result && (portNumber <= 0 || portNumber > 65535)) {
188 if (printExplanation) {
190 "%s: Port number specified by -p must be in range 1..65535\n",
198 #if COMPILER_LIKES_PRAGMA_MARK
199 #pragma mark ***** Command Line Arguments
202 static const char kDefaultPIDFile[] = "/var/run/mDNSResponder.pid";
203 static const char kDefaultServiceType[] = "_afpovertcp._tcp.";
204 static const char kDefaultServiceDomain[] = "local.";
206 kDefaultPortNumber = 548
209 static void PrintUsage()
212 "Usage: %s [-v level ] [-r] [-n name] [-t type] [-d domain] [-p port] [-f file] [-b] [-P pidfile] [-x name=val ...]\n",
214 fprintf(stderr, " -v verbose mode, level is a number from 0 to 2\n");
215 fprintf(stderr, " 0 = no debugging info (default)\n");
216 fprintf(stderr, " 1 = standard debugging info\n");
217 fprintf(stderr, " 2 = intense debugging info\n");
218 fprintf(stderr, " can be cycled kill -USR1\n");
219 fprintf(stderr, " -r also bind to port 53 (port 5353 is always bound)\n");
220 fprintf(stderr, " -n uses 'name' as the service name (required)\n");
221 fprintf(stderr, " -t uses 'type' as the service type (default is '%s')\n", kDefaultServiceType);
222 fprintf(stderr, " -d uses 'domain' as the service domain (default is '%s')\n", kDefaultServiceDomain);
223 fprintf(stderr, " -p uses 'port' as the port number (default is '%d')\n", kDefaultPortNumber);
224 fprintf(stderr, " -f reads a service list from 'file'\n");
225 fprintf(stderr, " -b forces daemon (background) mode\n");
226 fprintf(stderr, " -P uses 'pidfile' as the PID file\n");
227 fprintf(stderr, " (default is '%s')\n", kDefaultPIDFile);
228 fprintf(stderr, " only meaningful if -b also specified\n");
229 fprintf(stderr, " -x stores name=val in TXT record (default is empty).\n");
230 fprintf(stderr, " MUST be the last command-line argument;\n");
231 fprintf(stderr, " all subsequent arguments after -x are treated as name=val pairs.\n");
234 static mDNSBool gAvoidPort53 = mDNStrue;
235 static const char *gServiceName = "";
236 static const char *gServiceType = kDefaultServiceType;
237 static const char *gServiceDomain = kDefaultServiceDomain;
238 static mDNSu8 gServiceText[sizeof(RDataBody)];
239 static mDNSu16 gServiceTextLen = 0;
240 static int gPortNumber = kDefaultPortNumber;
241 static const char *gServiceFile = "";
242 static mDNSBool gDaemon = mDNSfalse;
243 static const char *gPIDFile = kDefaultPIDFile;
245 static void ParseArguments(int argc, char **argv)
246 // Parses our command line arguments into the global variables
251 // Set gProgramName to the last path component of argv[0]
253 gProgramName = strrchr(argv[0], '/');
254 if (gProgramName == NULL) {
255 gProgramName = argv[0];
260 // Parse command line options using getopt.
263 ch = getopt(argc, argv, "v:rn:t:d:p:f:dP:bx");
267 gMDNSPlatformPosixVerboseLevel = atoi(optarg);
268 if (gMDNSPlatformPosixVerboseLevel < 0 || gMDNSPlatformPosixVerboseLevel > 2) {
270 "%s: Verbose mode must be in the range 0..2\n",
276 gAvoidPort53 = mDNSfalse;
279 gServiceName = optarg;
280 if ( !CheckThatRichTextNameIsUsable(gServiceName, mDNStrue) ) {
285 gServiceType = optarg;
286 if ( !CheckThatServiceTypeIsUsable(gServiceType, mDNStrue) ) {
291 gServiceDomain = optarg;
294 gPortNumber = atol(optarg);
295 if ( !CheckThatPortNumberIsUsable(gPortNumber, mDNStrue) ) {
300 gServiceFile = optarg;
309 while (optind < argc)
311 gServiceText[gServiceTextLen] = strlen(argv[optind]);
312 mDNSPlatformMemCopy(gServiceText+gServiceTextLen+1, argv[optind], gServiceText[gServiceTextLen]);
313 gServiceTextLen += 1 + gServiceText[gServiceTextLen];
327 // Check for any left over command line arguments.
329 if (optind != argc) {
331 fprintf(stderr, "%s: Unexpected argument '%s'\n", gProgramName, argv[optind]);
335 // Check for inconsistency between the arguments.
337 if ( (gServiceName[0] == 0) && (gServiceFile[0] == 0) ) {
339 fprintf(stderr, "%s: You must specify a service name to register (-n) or a service file (-f).\n", gProgramName);
344 #if COMPILER_LIKES_PRAGMA_MARK
345 #pragma mark ***** Registration
348 typedef struct PosixService PosixService;
350 struct PosixService {
351 ServiceRecordSet coreServ;
356 static PosixService *gServiceList = NULL;
358 static void RegistrationCallback(mDNS *const m, ServiceRecordSet *const thisRegistration, mStatus status)
359 // mDNS core calls this routine to tell us about the status of
360 // our registration. The appropriate action to take depends
361 // entirely on the value of status.
365 case mStatus_NoError:
366 debugf("Callback: %##s Name Registered", thisRegistration->RR_SRV.resrec.name->c);
367 // Do nothing; our name was successfully registered. We may
368 // get more call backs in the future.
371 case mStatus_NameConflict:
372 debugf("Callback: %##s Name Conflict", thisRegistration->RR_SRV.resrec.name->c);
374 // In the event of a conflict, this sample RegistrationCallback
375 // just calls mDNS_RenameAndReregisterService to automatically
376 // pick a new unique name for the service. For a device such as a
377 // printer, this may be appropriate. For a device with a user
378 // interface, and a screen, and a keyboard, the appropriate response
379 // may be to prompt the user and ask them to choose a new name for
382 // Also, what do we do if mDNS_RenameAndReregisterService returns an
383 // error. Right now I have no place to send that error to.
385 status = mDNS_RenameAndReregisterService(m, thisRegistration, mDNSNULL);
386 assert(status == mStatus_NoError);
389 case mStatus_MemFree:
390 debugf("Callback: %##s Memory Free", thisRegistration->RR_SRV.resrec.name->c);
392 // When debugging is enabled, make sure that thisRegistration
393 // is not on our gServiceList.
397 PosixService *cursor;
399 cursor = gServiceList;
400 while (cursor != NULL) {
401 assert(&cursor->coreServ != thisRegistration);
402 cursor = cursor->next;
406 free(thisRegistration);
410 debugf("Callback: %##s Unknown Status %ld", thisRegistration->RR_SRV.resrec.name->c, status);
415 static int gServiceID = 0;
417 static mStatus RegisterOneService(const char * richTextName,
418 const char * serviceType,
419 const char * serviceDomain,
425 PosixService * thisServ;
430 status = mStatus_NoError;
431 thisServ = (PosixService *) malloc(sizeof(*thisServ));
432 if (thisServ == NULL) {
433 status = mStatus_NoMemoryErr;
435 if (status == mStatus_NoError) {
436 MakeDomainLabelFromLiteralString(&name, richTextName);
437 MakeDomainNameFromDNSNameString(&type, serviceType);
438 MakeDomainNameFromDNSNameString(&domain, serviceDomain);
439 status = mDNS_RegisterService(&mDNSStorage, &thisServ->coreServ,
440 &name, &type, &domain, // Name, type, domain
441 NULL, mDNSOpaque16fromIntVal(portNumber),
442 text, textLen, // TXT data, length
444 mDNSInterface_Any, // Interface ID
445 RegistrationCallback, thisServ, 0); // Callback, context, flags
447 if (status == mStatus_NoError) {
448 thisServ->serviceID = gServiceID;
451 thisServ->next = gServiceList;
452 gServiceList = thisServ;
454 if (gMDNSPlatformPosixVerboseLevel > 0) {
456 "%s: Registered service %d, name \"%s\", type \"%s\", domain \"%s\", port %ld\n",
465 if (thisServ != NULL) {
472 static mDNSBool ReadALine(char *buf, size_t bufSize, FILE *fp, mDNSBool skipBlankLines)
475 mDNSBool readNextLine;
478 readNextLine = mDNSfalse;
480 if (fgets(buf, bufSize, fp) == NULL)
481 return mDNSfalse; // encountered EOF or an error condition
483 // These first characters indicate a blank line.
484 if (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\r' || buf[0] == '\n') {
487 readNextLine = mDNStrue;
489 // always skip comment lines
491 readNextLine = mDNStrue;
493 } while (readNextLine);
496 if ( buf[len - 1] == '\r' || buf[len - 1] == '\n')
502 static mStatus RegisterServicesInFile(const char *filePath)
504 mStatus status = mStatus_NoError;
505 FILE * fp = fopen(filePath, "r");
509 return mStatus_UnknownErr;
512 if (gMDNSPlatformPosixVerboseLevel > 1)
513 fprintf(stderr, "Parsing %s for services\n", filePath);
517 char * name = nameBuf;
519 const char *dom = kDefaultServiceDomain;
521 mDNSu8 text[sizeof(RDataBody)];
522 unsigned int textLen = 0;
526 // Read the service name, type, port, and optional text record fields.
527 // Skip blank lines while looking for the next service name.
528 if (!ReadALine(name, sizeof(nameBuf), fp, mDNStrue))
531 // Special case that allows service name to begin with a '#'
532 // character by escaping it with a '\' to distiguish it from
533 // a comment line. Remove the leading '\' here before
534 // registering the service.
535 if (name[0] == '\\' && name[1] == '#')
538 if (gMDNSPlatformPosixVerboseLevel > 1)
539 fprintf(stderr, "Service name: \"%s\"\n", name);
541 // Don't skip blank lines in calls to ReadAline() after finding the
542 // service name since the next blank line indicates the end
543 // of this service record.
544 if (!ReadALine(type, sizeof(type), fp, mDNSfalse))
547 // see if a domain name is specified
549 while (*p && *p != ' ' && *p != '\t') p++;
551 *p = 0; // NULL terminate the <type>.<protocol> string
552 // skip any leading whitespace before domain name
554 while (*p && (*p == ' ' || *p == '\t')) p++;
558 if (gMDNSPlatformPosixVerboseLevel > 1) {
559 fprintf(stderr, "Service type: \"%s\"\n", type);
560 fprintf(stderr, "Service domain: \"%s\"\n", dom);
563 if (!ReadALine(port, sizeof(port), fp, mDNSfalse))
565 if (gMDNSPlatformPosixVerboseLevel > 1)
566 fprintf(stderr, "Service port: %s\n", port);
568 if ( !CheckThatRichTextNameIsUsable(name, mDNStrue)
569 || !CheckThatServiceTypeIsUsable(type, mDNStrue)
570 || !CheckThatPortNumberIsUsable(atol(port), mDNStrue))
573 // read the TXT record fields
576 if (!ReadALine(rawText, sizeof(rawText), fp, mDNSfalse)) break;
577 if (gMDNSPlatformPosixVerboseLevel > 1)
578 fprintf(stderr, "Text string: \"%s\"\n", rawText);
579 len = strlen(rawText);
582 unsigned int newlen = textLen + 1 + len;
583 if (len == 0 || newlen >= sizeof(text)) break;
585 mDNSPlatformMemCopy(text + textLen + 1, rawText, len);
589 fprintf(stderr, "%s: TXT attribute too long for name = %s, type = %s, port = %s\n",
590 gProgramName, name, type, port);
593 status = RegisterOneService(name, type, dom, text, textLen, atol(port));
594 if (status != mStatus_NoError) {
595 // print error, but try to read and register other services in the file
596 fprintf(stderr, "%s: Failed to register service, name \"%s\", type \"%s\", domain \"%s\", port %s\n",
597 gProgramName, name, type, dom, port);
603 fprintf(stderr, "%s: Error reading service file %s\n", gProgramName, filePath);
604 status = mStatus_UnknownErr;
613 static mStatus RegisterOurServices(void)
617 status = mStatus_NoError;
618 if (gServiceName[0] != 0) {
619 status = RegisterOneService(gServiceName,
622 gServiceText, gServiceTextLen,
625 if (status == mStatus_NoError && gServiceFile[0] != 0) {
626 status = RegisterServicesInFile(gServiceFile);
631 static void DeregisterOurServices(void)
633 PosixService *thisServ;
636 while (gServiceList != NULL) {
637 thisServ = gServiceList;
638 gServiceList = thisServ->next;
640 thisServID = thisServ->serviceID;
642 mDNS_DeregisterService(&mDNSStorage, &thisServ->coreServ);
644 if (gMDNSPlatformPosixVerboseLevel > 0) {
646 "%s: Deregistered service %d\n",
648 thisServ->serviceID);
653 #if COMPILER_LIKES_PRAGMA_MARK
654 #pragma mark **** Main
657 int main(int argc, char **argv)
662 // Parse our command line arguments. This won't come back if there's an error.
664 ParseArguments(argc, argv);
666 // If we're told to run as a daemon, then do that straight away.
667 // Note that we don't treat the inability to create our PID
668 // file as an error. Also note that we assign getpid to a long
669 // because printf has no format specified for pid_t.
673 if (gMDNSPlatformPosixVerboseLevel > 0) {
674 fprintf(stderr, "%s: Starting in daemon mode\n", gProgramName);
676 result = daemon(0,0);
681 fp = fopen(gPIDFile, "w");
683 fprintf(fp, "%ld\n", (long) getpid());
688 fprintf(stderr, "%s: Could not run as daemon - exiting\n", gProgramName);
692 if (gMDNSPlatformPosixVerboseLevel > 0) {
693 fprintf(stderr, "%s: Starting in foreground mode, PID %ld\n", gProgramName, (long) getpid());
697 status = mDNS_Init(&mDNSStorage, &PlatformStorage,
698 mDNS_Init_NoCache, mDNS_Init_ZeroCacheSize,
699 mDNS_Init_AdvertiseLocalAddresses,
700 mDNS_Init_NoInitCallback, mDNS_Init_NoInitCallbackContext);
701 if (status != mStatus_NoError) return(2);
703 status = RegisterOurServices();
704 if (status != mStatus_NoError) return(2);
706 signal(SIGHUP, HandleSigHup); // SIGHUP has to be sent by kill -HUP <pid>
707 signal(SIGINT, HandleSigInt); // SIGINT is what you get for a Ctrl-C
708 signal(SIGQUIT, HandleSigQuit); // SIGQUIT is what you get for a Ctrl-\ (indeed)
709 signal(SIGUSR1, HandleSigUsr1); // SIGUSR1 has to be sent by kill -USR1 <pid>
715 struct timeval timeout;
718 // 1. Set up the fd_set as usual here.
719 // This example client has no file descriptors of its own,
720 // but a real application would call FD_SET to add them to the set here
723 // 2. Set up the timeout.
724 // This example client has no other work it needs to be doing,
725 // so we set an effectively infinite timeout
726 timeout.tv_sec = 0x3FFFFFFF;
729 // 3. Give the mDNSPosix layer a chance to add its information to the fd_set and timeout
730 mDNSPosixGetFDSet(&mDNSStorage, &nfds, &readfds, &timeout);
732 // 4. Call select as normal
733 verbosedebugf("select(%d, %d.%06d)", nfds, timeout.tv_sec, timeout.tv_usec);
734 result = select(nfds, &readfds, NULL, NULL, &timeout);
738 verbosedebugf("select() returned %d errno %d", result, errno);
739 if (errno != EINTR) gStopNow = mDNStrue;
742 if (gReceivedSigUsr1)
744 gReceivedSigUsr1 = mDNSfalse;
745 gMDNSPlatformPosixVerboseLevel += 1;
746 if (gMDNSPlatformPosixVerboseLevel > 2)
747 gMDNSPlatformPosixVerboseLevel = 0;
748 if ( gMDNSPlatformPosixVerboseLevel > 0 )
749 fprintf(stderr, "\nVerbose level %d\n", gMDNSPlatformPosixVerboseLevel);
753 if (gMDNSPlatformPosixVerboseLevel > 0)
754 fprintf(stderr, "\nSIGHUP\n");
755 gReceivedSigHup = mDNSfalse;
756 DeregisterOurServices();
757 status = mDNSPlatformPosixRefreshInterfaceList(&mDNSStorage);
758 if (status != mStatus_NoError) break;
759 status = RegisterOurServices();
760 if (status != mStatus_NoError) break;
766 // 5. Call mDNSPosixProcessFDSet to let the mDNSPosix layer do its work
767 mDNSPosixProcessFDSet(&mDNSStorage, &readfds);
769 // 6. This example client has no other work it needs to be doing,
770 // but a real client would do its work here
777 DeregisterOurServices();
778 mDNS_Close(&mDNSStorage);
780 if (status == mStatus_NoError) {
785 if ( (result != 0) || (gMDNSPlatformPosixVerboseLevel > 0) ) {
786 fprintf(stderr, "%s: Finished with status %d, result %d\n", gProgramName, (int)status, result);