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
34 #include <sys/socket.h>
38 extern int daemon(int, int);
41 #include "mDNSEmbeddedAPI.h" // Defines the interface to the client layer above
42 #include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform
43 #include "mDNSUNP.h" // For daemon()
45 #if COMPILER_LIKES_PRAGMA_MARK
46 #pragma mark ***** Globals
49 mDNS mDNSStorage
; // mDNS core uses this to store its globals
50 static mDNS_PlatformSupport PlatformStorage
; // Stores this platform's globals
52 mDNSexport
const char ProgramName
[] = "mDNSResponderPosix";
54 static const char *gProgramName
= ProgramName
;
56 #if COMPILER_LIKES_PRAGMA_MARK
57 #pragma mark ***** Signals
60 static volatile mDNSBool gReceivedSigUsr1
;
61 static volatile mDNSBool gReceivedSigHup
;
62 static volatile mDNSBool gStopNow
;
64 // We support 4 signals.
66 // o SIGUSR1 toggles verbose mode on and off in debug builds
67 // o SIGHUP triggers the program to re-read its preferences.
68 // o SIGINT causes an orderly shutdown of the program.
69 // o SIGQUIT causes a somewhat orderly shutdown (direct but dangerous)
70 // o SIGKILL kills us dead (easy to implement :-)
72 // There are fatal race conditions in our signal handling, but there's not much
73 // we can do about them while remaining within the Posix space. Specifically,
74 // if a signal arrives after we test the globals its sets but before we call
75 // select, the signal will be dropped. The user will have to send the signal
76 // again. Unfortunately, Posix does not have a "sigselect" to atomically
77 // modify the signal mask and start a select.
79 static void HandleSigUsr1(int sigraised
)
80 // If we get a SIGUSR1 we toggle the state of the
83 assert(sigraised
== SIGUSR1
);
84 gReceivedSigUsr1
= mDNStrue
;
87 static void HandleSigHup(int sigraised
)
88 // A handler for SIGHUP that causes us to break out of the
89 // main event loop when the user kill 1's us. This has the
90 // effect of triggered the main loop to deregister the
91 // current services and re-read the preferences.
93 assert(sigraised
== SIGHUP
);
94 gReceivedSigHup
= mDNStrue
;
97 static void HandleSigInt(int sigraised
)
98 // A handler for SIGINT that causes us to break out of the
99 // main event loop when the user types ^C. This has the
100 // effect of quitting the program.
102 assert(sigraised
== SIGINT
);
104 if (gMDNSPlatformPosixVerboseLevel
> 0) {
105 fprintf(stderr
, "\nSIGINT\n");
110 static void HandleSigQuit(int sigraised
)
111 // If we get a SIGQUIT the user is desperate and we
112 // just call mDNS_Close directly. This is definitely
113 // not safe (because it could reenter mDNS), but
114 // we presume that the user has already tried the safe
117 assert(sigraised
== SIGQUIT
);
119 if (gMDNSPlatformPosixVerboseLevel
> 0) {
120 fprintf(stderr
, "\nSIGQUIT\n");
122 mDNS_Close(&mDNSStorage
);
126 #if COMPILER_LIKES_PRAGMA_MARK
127 #pragma mark ***** Parameter Checking
130 static mDNSBool
CheckThatRichTextNameIsUsable(const char *richTextName
, mDNSBool printExplanation
)
131 // Checks that richTextName is reasonable
132 // label and, if it isn't and printExplanation is true, prints
133 // an explanation of why not.
135 mDNSBool result
= mDNStrue
;
136 if (result
&& strlen(richTextName
) > 63) {
137 if (printExplanation
) {
139 "%s: Service name is too long (must be 63 characters or less)\n",
144 if (result
&& richTextName
[0] == 0) {
145 if (printExplanation
) {
146 fprintf(stderr
, "%s: Service name can't be empty\n", gProgramName
);
153 static mDNSBool
CheckThatServiceTypeIsUsable(const char *serviceType
, mDNSBool printExplanation
)
154 // Checks that serviceType is a reasonable service type
155 // label and, if it isn't and printExplanation is true, prints
156 // an explanation of why not.
161 if (result
&& strlen(serviceType
) > 63) {
162 if (printExplanation
) {
164 "%s: Service type is too long (must be 63 characters or less)\n",
169 if (result
&& serviceType
[0] == 0) {
170 if (printExplanation
) {
172 "%s: Service type can't be empty\n",
180 static mDNSBool
CheckThatPortNumberIsUsable(long portNumber
, mDNSBool printExplanation
)
181 // Checks that portNumber is a reasonable port number
182 // and, if it isn't and printExplanation is true, prints
183 // an explanation of why not.
188 if (result
&& (portNumber
<= 0 || portNumber
> 65535)) {
189 if (printExplanation
) {
191 "%s: Port number specified by -p must be in range 1..65535\n",
199 #if COMPILER_LIKES_PRAGMA_MARK
200 #pragma mark ***** Command Line Arguments
203 static const char kDefaultPIDFile
[] = "/var/run/mDNSResponder.pid";
204 static const char kDefaultServiceType
[] = "_afpovertcp._tcp.";
205 static const char kDefaultServiceDomain
[] = "local.";
207 kDefaultPortNumber
= 548
210 static void PrintUsage()
213 "Usage: %s [-v level ] [-r] [-n name] [-t type] [-d domain] [-p port] [-f file] [-b] [-P pidfile] [-x name=val ...]\n",
215 fprintf(stderr
, " -v verbose mode, level is a number from 0 to 2\n");
216 fprintf(stderr
, " 0 = no debugging info (default)\n");
217 fprintf(stderr
, " 1 = standard debugging info\n");
218 fprintf(stderr
, " 2 = intense debugging info\n");
219 fprintf(stderr
, " can be cycled kill -USR1\n");
220 fprintf(stderr
, " -r also bind to port 53 (port 5353 is always bound)\n");
221 fprintf(stderr
, " -n uses 'name' as the service name (required)\n");
222 fprintf(stderr
, " -t uses 'type' as the service type (default is '%s')\n", kDefaultServiceType
);
223 fprintf(stderr
, " -d uses 'domain' as the service domain (default is '%s')\n", kDefaultServiceDomain
);
224 fprintf(stderr
, " -p uses 'port' as the port number (default is '%d')\n", kDefaultPortNumber
);
225 fprintf(stderr
, " -f reads a service list from 'file'\n");
226 fprintf(stderr
, " -b forces daemon (background) mode\n");
227 fprintf(stderr
, " -P uses 'pidfile' as the PID file\n");
228 fprintf(stderr
, " (default is '%s')\n", kDefaultPIDFile
);
229 fprintf(stderr
, " only meaningful if -b also specified\n");
230 fprintf(stderr
, " -x stores name=val in TXT record (default is empty).\n");
231 fprintf(stderr
, " MUST be the last command-line argument;\n");
232 fprintf(stderr
, " all subsequent arguments after -x are treated as name=val pairs.\n");
235 static mDNSBool gAvoidPort53
= mDNStrue
;
236 static const char *gServiceName
= "";
237 static const char *gServiceType
= kDefaultServiceType
;
238 static const char *gServiceDomain
= kDefaultServiceDomain
;
239 static mDNSu8 gServiceText
[sizeof(RDataBody
)];
240 static mDNSu16 gServiceTextLen
= 0;
241 static int gPortNumber
= kDefaultPortNumber
;
242 static const char *gServiceFile
= "";
243 static mDNSBool gDaemon
= mDNSfalse
;
244 static const char *gPIDFile
= kDefaultPIDFile
;
246 static void ParseArguments(int argc
, char **argv
)
247 // Parses our command line arguments into the global variables
252 // Set gProgramName to the last path component of argv[0]
254 gProgramName
= strrchr(argv
[0], '/');
255 if (gProgramName
== NULL
) {
256 gProgramName
= argv
[0];
261 // Parse command line options using getopt.
264 ch
= getopt(argc
, argv
, "v:rn:t:d:p:f:dP:bx");
268 gMDNSPlatformPosixVerboseLevel
= atoi(optarg
);
269 if (gMDNSPlatformPosixVerboseLevel
< 0 || gMDNSPlatformPosixVerboseLevel
> 2) {
271 "%s: Verbose mode must be in the range 0..2\n",
277 gAvoidPort53
= mDNSfalse
;
280 gServiceName
= optarg
;
281 if ( !CheckThatRichTextNameIsUsable(gServiceName
, mDNStrue
) ) {
286 gServiceType
= optarg
;
287 if ( !CheckThatServiceTypeIsUsable(gServiceType
, mDNStrue
) ) {
292 gServiceDomain
= optarg
;
295 gPortNumber
= atol(optarg
);
296 if ( !CheckThatPortNumberIsUsable(gPortNumber
, mDNStrue
) ) {
301 gServiceFile
= optarg
;
310 while (optind
< argc
)
312 gServiceText
[gServiceTextLen
] = strlen(argv
[optind
]);
313 mDNSPlatformMemCopy(gServiceText
+gServiceTextLen
+1, argv
[optind
], gServiceText
[gServiceTextLen
]);
314 gServiceTextLen
+= 1 + gServiceText
[gServiceTextLen
];
328 // Check for any left over command line arguments.
330 if (optind
!= argc
) {
332 fprintf(stderr
, "%s: Unexpected argument '%s'\n", gProgramName
, argv
[optind
]);
336 // Check for inconsistency between the arguments.
338 if ( (gServiceName
[0] == 0) && (gServiceFile
[0] == 0) ) {
340 fprintf(stderr
, "%s: You must specify a service name to register (-n) or a service file (-f).\n", gProgramName
);
345 #if COMPILER_LIKES_PRAGMA_MARK
346 #pragma mark ***** Registration
349 typedef struct PosixService PosixService
;
351 struct PosixService
{
352 ServiceRecordSet coreServ
;
357 static PosixService
*gServiceList
= NULL
;
359 static void RegistrationCallback(mDNS
*const m
, ServiceRecordSet
*const thisRegistration
, mStatus status
)
360 // mDNS core calls this routine to tell us about the status of
361 // our registration. The appropriate action to take depends
362 // entirely on the value of status.
366 case mStatus_NoError
:
367 debugf("Callback: %##s Name Registered", thisRegistration
->RR_SRV
.resrec
.name
->c
);
368 // Do nothing; our name was successfully registered. We may
369 // get more call backs in the future.
372 case mStatus_NameConflict
:
373 debugf("Callback: %##s Name Conflict", thisRegistration
->RR_SRV
.resrec
.name
->c
);
375 // In the event of a conflict, this sample RegistrationCallback
376 // just calls mDNS_RenameAndReregisterService to automatically
377 // pick a new unique name for the service. For a device such as a
378 // printer, this may be appropriate. For a device with a user
379 // interface, and a screen, and a keyboard, the appropriate response
380 // may be to prompt the user and ask them to choose a new name for
383 // Also, what do we do if mDNS_RenameAndReregisterService returns an
384 // error. Right now I have no place to send that error to.
386 status
= mDNS_RenameAndReregisterService(m
, thisRegistration
, mDNSNULL
);
387 assert(status
== mStatus_NoError
);
390 case mStatus_MemFree
:
391 debugf("Callback: %##s Memory Free", thisRegistration
->RR_SRV
.resrec
.name
->c
);
393 // When debugging is enabled, make sure that thisRegistration
394 // is not on our gServiceList.
398 PosixService
*cursor
;
400 cursor
= gServiceList
;
401 while (cursor
!= NULL
) {
402 assert(&cursor
->coreServ
!= thisRegistration
);
403 cursor
= cursor
->next
;
407 free(thisRegistration
);
411 debugf("Callback: %##s Unknown Status %ld", thisRegistration
->RR_SRV
.resrec
.name
->c
, status
);
416 static int gServiceID
= 0;
418 static mStatus
RegisterOneService(const char * richTextName
,
419 const char * serviceType
,
420 const char * serviceDomain
,
426 PosixService
* thisServ
;
431 status
= mStatus_NoError
;
432 thisServ
= (PosixService
*) calloc(sizeof(*thisServ
), 1);
433 if (thisServ
== NULL
) {
434 status
= mStatus_NoMemoryErr
;
436 if (status
== mStatus_NoError
) {
437 MakeDomainLabelFromLiteralString(&name
, richTextName
);
438 MakeDomainNameFromDNSNameString(&type
, serviceType
);
439 MakeDomainNameFromDNSNameString(&domain
, serviceDomain
);
440 status
= mDNS_RegisterService(&mDNSStorage
, &thisServ
->coreServ
,
441 &name
, &type
, &domain
, // Name, type, domain
442 NULL
, mDNSOpaque16fromIntVal(portNumber
),
443 NULL
, text
, textLen
, // TXT data, length
445 mDNSInterface_Any
, // Interface ID
446 RegistrationCallback
, thisServ
, 0); // Callback, context, flags
448 if (status
== mStatus_NoError
) {
449 thisServ
->serviceID
= gServiceID
;
452 thisServ
->next
= gServiceList
;
453 gServiceList
= thisServ
;
455 if (gMDNSPlatformPosixVerboseLevel
> 0) {
457 "%s: Registered service %d, name \"%s\", type \"%s\", domain \"%s\", port %ld\n",
466 if (thisServ
!= NULL
) {
473 static mDNSBool
ReadALine(char *buf
, size_t bufSize
, FILE *fp
, mDNSBool skipBlankLines
)
476 mDNSBool readNextLine
;
479 readNextLine
= mDNSfalse
;
481 if (fgets(buf
, bufSize
, fp
) == NULL
)
482 return mDNSfalse
; // encountered EOF or an error condition
484 // These first characters indicate a blank line.
485 if (buf
[0] == ' ' || buf
[0] == '\t' || buf
[0] == '\r' || buf
[0] == '\n') {
488 readNextLine
= mDNStrue
;
490 // always skip comment lines
492 readNextLine
= mDNStrue
;
494 } while (readNextLine
);
497 if ( buf
[len
- 1] == '\r' || buf
[len
- 1] == '\n')
503 static mStatus
RegisterServicesInFile(const char *filePath
)
505 mStatus status
= mStatus_NoError
;
506 FILE * fp
= fopen(filePath
, "r");
510 return mStatus_UnknownErr
;
513 if (gMDNSPlatformPosixVerboseLevel
> 1)
514 fprintf(stderr
, "Parsing %s for services\n", filePath
);
518 char * name
= nameBuf
;
520 const char *dom
= kDefaultServiceDomain
;
522 mDNSu8 text
[sizeof(RDataBody
)];
523 unsigned int textLen
= 0;
527 // Read the service name, type, port, and optional text record fields.
528 // Skip blank lines while looking for the next service name.
529 if (!ReadALine(name
, sizeof(nameBuf
), fp
, mDNStrue
))
532 // Special case that allows service name to begin with a '#'
533 // character by escaping it with a '\' to distiguish it from
534 // a comment line. Remove the leading '\' here before
535 // registering the service.
536 if (name
[0] == '\\' && name
[1] == '#')
539 if (gMDNSPlatformPosixVerboseLevel
> 1)
540 fprintf(stderr
, "Service name: \"%s\"\n", name
);
542 // Don't skip blank lines in calls to ReadAline() after finding the
543 // service name since the next blank line indicates the end
544 // of this service record.
545 if (!ReadALine(type
, sizeof(type
), fp
, mDNSfalse
))
548 // see if a domain name is specified
550 while (*p
&& *p
!= ' ' && *p
!= '\t') p
++;
552 *p
= 0; // NULL terminate the <type>.<protocol> string
553 // skip any leading whitespace before domain name
555 while (*p
&& (*p
== ' ' || *p
== '\t')) p
++;
559 if (gMDNSPlatformPosixVerboseLevel
> 1) {
560 fprintf(stderr
, "Service type: \"%s\"\n", type
);
561 fprintf(stderr
, "Service domain: \"%s\"\n", dom
);
564 if (!ReadALine(port
, sizeof(port
), fp
, mDNSfalse
))
566 if (gMDNSPlatformPosixVerboseLevel
> 1)
567 fprintf(stderr
, "Service port: %s\n", port
);
569 if ( !CheckThatRichTextNameIsUsable(name
, mDNStrue
)
570 || !CheckThatServiceTypeIsUsable(type
, mDNStrue
)
571 || !CheckThatPortNumberIsUsable(atol(port
), mDNStrue
))
574 // read the TXT record fields
577 if (!ReadALine(rawText
, sizeof(rawText
), fp
, mDNSfalse
)) break;
578 if (gMDNSPlatformPosixVerboseLevel
> 1)
579 fprintf(stderr
, "Text string: \"%s\"\n", rawText
);
580 len
= strlen(rawText
);
583 unsigned int newlen
= textLen
+ 1 + len
;
584 if (len
== 0 || newlen
>= sizeof(text
)) break;
586 mDNSPlatformMemCopy(text
+ textLen
+ 1, rawText
, len
);
590 fprintf(stderr
, "%s: TXT attribute too long for name = %s, type = %s, port = %s\n",
591 gProgramName
, name
, type
, port
);
594 status
= RegisterOneService(name
, type
, dom
, text
, textLen
, atol(port
));
595 if (status
!= mStatus_NoError
) {
596 // print error, but try to read and register other services in the file
597 fprintf(stderr
, "%s: Failed to register service, name \"%s\", type \"%s\", domain \"%s\", port %s\n",
598 gProgramName
, name
, type
, dom
, port
);
604 fprintf(stderr
, "%s: Error reading service file %s\n", gProgramName
, filePath
);
605 status
= mStatus_UnknownErr
;
614 static mStatus
RegisterOurServices(void)
618 status
= mStatus_NoError
;
619 if (gServiceName
[0] != 0) {
620 status
= RegisterOneService(gServiceName
,
623 gServiceText
, gServiceTextLen
,
626 if (status
== mStatus_NoError
&& gServiceFile
[0] != 0) {
627 status
= RegisterServicesInFile(gServiceFile
);
632 static void DeregisterOurServices(void)
634 PosixService
*thisServ
;
636 while (gServiceList
!= NULL
) {
637 thisServ
= gServiceList
;
638 gServiceList
= thisServ
->next
;
640 mDNS_DeregisterService(&mDNSStorage
, &thisServ
->coreServ
);
642 if (gMDNSPlatformPosixVerboseLevel
> 0) {
644 "%s: Deregistered service %d\n",
646 thisServ
->serviceID
);
651 #if COMPILER_LIKES_PRAGMA_MARK
652 #pragma mark **** Main
655 int main(int argc
, char **argv
)
660 // Parse our command line arguments. This won't come back if there's an error.
662 ParseArguments(argc
, argv
);
664 // If we're told to run as a daemon, then do that straight away.
665 // Note that we don't treat the inability to create our PID
666 // file as an error. Also note that we assign getpid to a long
667 // because printf has no format specified for pid_t.
671 if (gMDNSPlatformPosixVerboseLevel
> 0) {
672 fprintf(stderr
, "%s: Starting in daemon mode\n", gProgramName
);
674 result
= daemon(0,0);
679 fp
= fopen(gPIDFile
, "w");
681 fprintf(fp
, "%ld\n", (long) getpid());
686 fprintf(stderr
, "%s: Could not run as daemon - exiting\n", gProgramName
);
690 if (gMDNSPlatformPosixVerboseLevel
> 0) {
691 fprintf(stderr
, "%s: Starting in foreground mode, PID %ld\n", gProgramName
, (long) getpid());
695 status
= mDNS_Init(&mDNSStorage
, &PlatformStorage
,
696 mDNS_Init_NoCache
, mDNS_Init_ZeroCacheSize
,
697 mDNS_Init_AdvertiseLocalAddresses
,
698 mDNS_Init_NoInitCallback
, mDNS_Init_NoInitCallbackContext
);
699 if (status
!= mStatus_NoError
) return(2);
701 status
= RegisterOurServices();
702 if (status
!= mStatus_NoError
) return(2);
704 signal(SIGHUP
, HandleSigHup
); // SIGHUP has to be sent by kill -HUP <pid>
705 signal(SIGINT
, HandleSigInt
); // SIGINT is what you get for a Ctrl-C
706 signal(SIGQUIT
, HandleSigQuit
); // SIGQUIT is what you get for a Ctrl-\ (indeed)
707 signal(SIGUSR1
, HandleSigUsr1
); // SIGUSR1 has to be sent by kill -USR1 <pid>
712 fd_set readfds
, writefds
;
713 struct timeval timeout
;
716 // 1. Set up the fd_set as usual here.
717 // This example client has no file descriptors of its own,
718 // but a real application would call FD_SET to add them to the set here
722 // 2. Set up the timeout.
723 // This example client has no other work it needs to be doing,
724 // so we set an effectively infinite timeout
725 timeout
.tv_sec
= FutureTime
;
728 // 3. Give the mDNSPosix layer a chance to add its information to the fd_set and timeout
729 mDNSPosixGetFDSet(&mDNSStorage
, &nfds
, &readfds
, &writefds
, &timeout
);
731 // 4. Call select as normal
732 verbosedebugf("select(%d, %d.%06d)", nfds
, timeout
.tv_sec
, timeout
.tv_usec
);
733 result
= select(nfds
, &readfds
, NULL
, NULL
, &timeout
);
737 verbosedebugf("select() returned %d errno %d", result
, errno
);
738 if (errno
!= EINTR
) gStopNow
= mDNStrue
;
741 if (gReceivedSigUsr1
)
743 gReceivedSigUsr1
= mDNSfalse
;
744 gMDNSPlatformPosixVerboseLevel
+= 1;
745 if (gMDNSPlatformPosixVerboseLevel
> 2)
746 gMDNSPlatformPosixVerboseLevel
= 0;
747 if ( gMDNSPlatformPosixVerboseLevel
> 0 )
748 fprintf(stderr
, "\nVerbose level %d\n", gMDNSPlatformPosixVerboseLevel
);
752 if (gMDNSPlatformPosixVerboseLevel
> 0)
753 fprintf(stderr
, "\nSIGHUP\n");
754 gReceivedSigHup
= mDNSfalse
;
755 DeregisterOurServices();
756 status
= mDNSPlatformPosixRefreshInterfaceList(&mDNSStorage
);
757 if (status
!= mStatus_NoError
) break;
758 status
= RegisterOurServices();
759 if (status
!= mStatus_NoError
) break;
765 // 5. Call mDNSPosixProcessFDSet to let the mDNSPosix layer do its work
766 mDNSPosixProcessFDSet(&mDNSStorage
, &readfds
, &writefds
);
768 // 6. This example client has no other work it needs to be doing,
769 // but a real client would do its work here
776 DeregisterOurServices();
777 mDNS_Close(&mDNSStorage
);
779 if (status
== mStatus_NoError
) {
784 if ( (result
!= 0) || (gMDNSPlatformPosixVerboseLevel
> 0) ) {
785 fprintf(stderr
, "%s: Finished with status %d, result %d\n", gProgramName
, (int)status
, result
);