translate DNS queries to avahi-resolve
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

initial import

joshua stein 7bf49b32

+624
+13
LICENSE
··· 1 + Copyright (c) 2026 joshua stein <jcs@jcs.org> 2 + 3 + Permission to use, copy, modify, and distribute this software for any 4 + purpose with or without fee is hereby granted, provided that the above 5 + copyright notice and this permission notice appear in all copies. 6 + 7 + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+35
Makefile
··· 1 + CC ?= cc 2 + CFLAGS = -O2 -Wall -Wextra -Wunused -Wmissing-prototypes -Wstrict-prototypes 3 + CFLAGS += -g 4 + 5 + INSTALL_PROGRAM ?= install 6 + 7 + PREFIX ?= /usr/local 8 + BINDIR ?= $(PREFIX)/bin 9 + 10 + PROG = avahi-proxy 11 + OBJS = avahi-proxy.o 12 + 13 + RC = avahi_proxy.rc 14 + RCDEST = avahi_proxy 15 + 16 + all: $(PROG) 17 + 18 + clean: 19 + rm -f $(PROG) $(OBJS) 20 + 21 + $(PROG): $(OBJS) 22 + $(CC) $(OBJS) -o $@ 23 + 24 + $(OBJS): *.c 25 + $(CC) $(CFLAGS) -c avahi-proxy.c -o $@ 26 + 27 + install: all 28 + mkdir -p $(DESTDIR)$(BINDIR) 29 + $(INSTALL_PROGRAM) -s $(PROG) $(DESTDIR)$(BINDIR) 30 + $(INSTALL_PROGRAM) $(RC) /etc/rc.d/$(RCDEST) 31 + 32 + clean: 33 + rm -f $(PROG) $(OBJS) 34 + 35 + .PHONY: all install clean
+43
README.md
··· 1 + # avahi-proxy 2 + 3 + Act as a DNS server listening on a given port (and optional IP, otherwise 4 + defaulting to `127.0.0.1`) and pass each `A` and `AAAA` query received for the 5 + `local` domain to `avahi-resolve`. 6 + If it gets an answer, pass it back as a proper DNS reply, otherwise return 7 + `NXDOMAIN`. 8 + 9 + ## Compiling 10 + 11 + $ git clone https://github.com/jcs/avahi-proxy 12 + $ cd avahi-proxy 13 + avahi-proxy$ make 14 + 15 + ## Installing 16 + 17 + avahi-proxy$ doas make install 18 + avahi-proxy$ doas rcctl enable avahi_proxy 19 + avahi-proxy$ doas rcctl start avahi_proxy 20 + 21 + ## Use with `unwind` 22 + 23 + When using OpenBSD's `unwind`, configure a `force` block for `local.` and a 24 + `forwarder` to `avahi-proxy`, assuming its default port of 5300: 25 + 26 + preference { autoconf } 27 + forwarder { 127.0.0.1 port 5300 } 28 + force forwarder { local } 29 + 30 + Now queries for `local.` hosts will forward to `avahi-proxy`, which will proxy 31 + them to `avahi-resolve`: 32 + 33 + $ dig @127.0.0.1 giraffe.local. a 34 + [...] 35 + ;; QUESTION SECTION: 36 + ;giraffe.local. IN A 37 + 38 + ;; ANSWER SECTION: 39 + giraffe.local. 9 IN A 192.168.1.3 40 + 41 + To directly query `avahi-daemon`, just supply the port it's running on: 42 + 43 + $ dig @127.0.0.1 -p 5300 giraffe.local. a
+524
avahi-proxy.c
··· 1 + /* 2 + * Copyright (c) 2026 joshua stein <jcs@jcs.org> 3 + * 4 + * Permission to use, copy, modify, and distribute this software for any 5 + * purpose with or without fee is hereby granted, provided that the above 6 + * copyright notice and this permission notice appear in all copies. 7 + * 8 + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 + */ 16 + 17 + #include <sys/types.h> 18 + #include <sys/socket.h> 19 + #include <sys/wait.h> 20 + 21 + #include <arpa/inet.h> 22 + #include <netinet/in.h> 23 + 24 + #include <err.h> 25 + #include <errno.h> 26 + #include <netdb.h> 27 + #include <signal.h> 28 + #include <stdarg.h> 29 + #include <stdio.h> 30 + #include <stdlib.h> 31 + #include <string.h> 32 + #include <syslog.h> 33 + #include <unistd.h> 34 + 35 + #define DNS_HEADER_SIZE 12 36 + #define DNS_MAX_NAME 255 37 + #define DNS_MAX_LABEL 63 38 + #define DNS_TYPE_A 1 39 + #define DNS_TYPE_NS 2 40 + #define DNS_TYPE_AAAA 28 41 + #define DNS_CLASS_IN 1 42 + #define DNS_RCODE_NOERROR 0 43 + #define DNS_RCODE_NXDOMAIN 3 44 + 45 + #define DNS_FLAG_QR 0x8000 /* response */ 46 + #define DNS_FLAG_AA 0x0400 /* authoritative */ 47 + #define DNS_FLAG_RD 0x0100 /* recursion desired */ 48 + #define DNS_FLAG_RA 0x0080 /* recursion available */ 49 + 50 + int debug, verbose; 51 + 52 + __dead void usage(void); 53 + void logmsg(const char *, ...); 54 + int dns_parse_name(uint8_t *, size_t, size_t, char *, size_t, size_t *); 55 + const char *dns_type_str(uint16_t); 56 + size_t dns_build_response(uint8_t *, size_t, uint8_t *, size_t, uint16_t, 57 + void *, size_t); 58 + size_t dns_build_ns_response(uint8_t *, size_t, uint8_t *); 59 + size_t dns_build_nxdomain(uint8_t *, size_t, uint8_t *, size_t); 60 + int avahi_resolve(const char *, int, void *); 61 + 62 + __dead void 63 + usage(void) 64 + { 65 + extern char *__progname; 66 + 67 + fprintf(stderr, "usage: %s [-dv] [-b address] -p port\n", __progname); 68 + exit(1); 69 + } 70 + 71 + void 72 + logmsg(const char *fmt, ...) 73 + { 74 + va_list ap; 75 + 76 + va_start(ap, fmt); 77 + if (debug) 78 + vprintf(fmt, ap); 79 + else 80 + vsyslog(LOG_INFO, fmt, ap); 81 + va_end(ap); 82 + } 83 + 84 + int 85 + main(int argc, char *argv[]) 86 + { 87 + struct addrinfo hints, *res, *res0; 88 + struct sockaddr_storage ss; 89 + struct in_addr addr4; 90 + struct in6_addr addr6; 91 + socklen_t sslen; 92 + ssize_t n; 93 + size_t namelen, qnamelen, resplen; 94 + char name[DNS_MAX_NAME + 1]; 95 + const char *bindaddr = NULL; 96 + const char *port = NULL; 97 + uint8_t qbuf[512], rbuf[512]; 98 + uint16_t qtype, qclass; 99 + int ch, error, s; 100 + 101 + while ((ch = getopt(argc, argv, "b:dp:v")) != -1) { 102 + switch (ch) { 103 + case 'b': 104 + bindaddr = optarg; 105 + break; 106 + case 'd': 107 + debug = 1; 108 + break; 109 + case 'p': 110 + port = optarg; 111 + break; 112 + case 'v': 113 + verbose++; 114 + break; 115 + default: 116 + usage(); 117 + } 118 + } 119 + argc -= optind; 120 + argv += optind; 121 + 122 + if (port == NULL) 123 + usage(); 124 + 125 + if (!debug) { 126 + if (daemon(0, 0) == -1) 127 + err(1, "daemon"); 128 + openlog("avahi-proxy", LOG_PID | LOG_NDELAY, LOG_DAEMON); 129 + } 130 + 131 + signal(SIGPIPE, SIG_IGN); 132 + 133 + memset(&hints, 0, sizeof(hints)); 134 + hints.ai_family = AF_UNSPEC; 135 + hints.ai_socktype = SOCK_DGRAM; 136 + hints.ai_flags = AI_PASSIVE; 137 + 138 + error = getaddrinfo(bindaddr, port, &hints, &res0); 139 + if (error != 0) 140 + errx(1, "getaddrinfo: %s", gai_strerror(error)); 141 + 142 + s = -1; 143 + for (res = res0; res != NULL; res = res->ai_next) { 144 + s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 145 + if (s == -1) 146 + continue; 147 + if (bind(s, res->ai_addr, res->ai_addrlen) == -1) { 148 + close(s); 149 + s = -1; 150 + continue; 151 + } 152 + break; 153 + } 154 + freeaddrinfo(res0); 155 + 156 + if (s == -1) 157 + err(1, "socket/bind"); 158 + 159 + for (;;) { 160 + sslen = sizeof(ss); 161 + n = recvfrom(s, qbuf, sizeof(qbuf), 0, (struct sockaddr *)&ss, 162 + &sslen); 163 + if (n == -1) { 164 + if (errno == EINTR) 165 + continue; 166 + warn("recvfrom"); 167 + continue; 168 + } 169 + 170 + if (n < DNS_HEADER_SIZE) 171 + continue; 172 + 173 + /* parse question */ 174 + if (dns_parse_name(qbuf, n, DNS_HEADER_SIZE, name, sizeof(name), 175 + &qnamelen) == -1) { 176 + warnx("failed parsing DNS query"); 177 + continue; 178 + } 179 + 180 + if (DNS_HEADER_SIZE + qnamelen + 4 > (size_t)n) { 181 + warnx("bogus qnamelen %zu + 4 > %zu", qnamelen, n); 182 + continue; 183 + } 184 + 185 + qtype = (qbuf[DNS_HEADER_SIZE + qnamelen] << 8) | 186 + qbuf[DNS_HEADER_SIZE + qnamelen + 1]; 187 + qclass = (qbuf[DNS_HEADER_SIZE + qnamelen + 2] << 8) | 188 + qbuf[DNS_HEADER_SIZE + qnamelen + 3]; 189 + 190 + if (qclass != DNS_CLASS_IN) { 191 + if (verbose) 192 + logmsg("ignoring query for non-IN (%d) name " 193 + "%s\n", qclass, name); 194 + continue; 195 + } 196 + 197 + /* respond to ". NS" probe from unwind */ 198 + if (strcmp(name, ".") == 0 && qtype == DNS_TYPE_NS) { 199 + resplen = dns_build_ns_response(rbuf, sizeof(rbuf), 200 + qbuf); 201 + if (verbose) 202 + logmsg("%s %s -> localhost.\n", 203 + dns_type_str(qtype), name); 204 + } else if ((namelen = strlen(name)) >= 6 && 205 + strcasecmp(name + namelen - 6, ".local") == 0 && 206 + qtype == DNS_TYPE_A && 207 + avahi_resolve(name, AF_INET, &addr4) == 0) { 208 + resplen = dns_build_response(rbuf, sizeof(rbuf), 209 + qbuf, n, DNS_TYPE_A, &addr4, 4); 210 + } else if (namelen >= 6 && 211 + strcasecmp(name + namelen - 6, ".local") == 0 && 212 + qtype == DNS_TYPE_AAAA && 213 + avahi_resolve(name, AF_INET6, &addr6) == 0) { 214 + resplen = dns_build_response(rbuf, sizeof(rbuf), 215 + qbuf, n, DNS_TYPE_AAAA, &addr6, 16); 216 + } else { 217 + resplen = dns_build_nxdomain(rbuf, sizeof(rbuf), 218 + qbuf, n); 219 + if (verbose) 220 + logmsg("%s %s NXDOMAIN\n", 221 + dns_type_str(qtype), name); 222 + } 223 + 224 + if (resplen > 0) 225 + sendto(s, rbuf, resplen, 0, (struct sockaddr *)&ss, 226 + sslen); 227 + } 228 + 229 + return 0; 230 + } 231 + 232 + int 233 + dns_parse_name(uint8_t *pkt, size_t pktlen, size_t offset, char *dst, 234 + size_t dstlen, size_t *lenp) 235 + { 236 + size_t i, label_len, total = 0, dst_off = 0; 237 + 238 + for (;;) { 239 + if (offset >= pktlen) 240 + return -1; 241 + 242 + label_len = pkt[offset++]; 243 + total++; 244 + 245 + if (label_len == 0) { 246 + /* root label */ 247 + if (dst_off == 0) { 248 + if (dstlen < 2) 249 + return -1; 250 + dst[dst_off++] = '.'; 251 + } 252 + dst[dst_off] = '\0'; 253 + *lenp = total; 254 + return 0; 255 + } 256 + 257 + if (label_len > DNS_MAX_LABEL) 258 + return -1; 259 + if (offset + label_len > pktlen) 260 + return -1; 261 + if (dst_off + label_len + 1 >= dstlen) 262 + return -1; 263 + 264 + if (dst_off > 0) 265 + dst[dst_off++] = '.'; 266 + 267 + for (i = 0; i < label_len; i++) 268 + dst[dst_off++] = pkt[offset++]; 269 + total += label_len; 270 + } 271 + } 272 + 273 + const char * 274 + dns_type_str(uint16_t type) 275 + { 276 + switch (type) { 277 + case DNS_TYPE_A: 278 + return "A"; 279 + case DNS_TYPE_NS: 280 + return "NS"; 281 + case DNS_TYPE_AAAA: 282 + return "AAAA"; 283 + default: 284 + return "?"; 285 + } 286 + } 287 + 288 + size_t 289 + dns_build_response(uint8_t *rbuf, size_t rlen, uint8_t *qbuf, size_t qlen, 290 + uint16_t type, void *addr, size_t addrlen) 291 + { 292 + size_t off, qsec_len; 293 + uint16_t flags; 294 + 295 + /* question section length: from byte 12 to end of qtype/qclass */ 296 + for (qsec_len = 0; DNS_HEADER_SIZE + qsec_len < qlen; qsec_len++) { 297 + if (qbuf[DNS_HEADER_SIZE + qsec_len] == 0) { 298 + qsec_len += 5; /* null + qtype(2) + qclass(2) */ 299 + break; 300 + } 301 + } 302 + 303 + if (DNS_HEADER_SIZE + qsec_len + 12 + addrlen > rlen) { 304 + warnx("%s: bogus size (%d + %zu + 12 + %zu) > %zu", 305 + __func__, DNS_HEADER_SIZE, qsec_len, addrlen, rlen); 306 + return 0; 307 + } 308 + 309 + /* copy header and question */ 310 + memcpy(rbuf, qbuf, DNS_HEADER_SIZE + qsec_len); 311 + 312 + /* set response flags */ 313 + flags = DNS_FLAG_QR | DNS_FLAG_AA | DNS_FLAG_RA; 314 + if (qbuf[2] & (DNS_FLAG_RD >> 8)) 315 + flags |= DNS_FLAG_RD; 316 + rbuf[2] = flags >> 8; 317 + rbuf[3] = flags & 0xff; 318 + 319 + /* qdcount = 1 */ 320 + rbuf[4] = 0; rbuf[5] = 1; 321 + /* ancount = 1 */ 322 + rbuf[6] = 0; rbuf[7] = 1; 323 + /* nscount = 0 */ 324 + rbuf[8] = 0; rbuf[9] = 0; 325 + /* arcount = 0 */ 326 + rbuf[10] = 0; rbuf[11] = 0; 327 + 328 + off = DNS_HEADER_SIZE + qsec_len; 329 + 330 + /* answer: pointer to qname */ 331 + rbuf[off++] = 0xc0; 332 + rbuf[off++] = 0x0c; 333 + /* type */ 334 + rbuf[off++] = type >> 8; 335 + rbuf[off++] = type & 0xff; 336 + /* class IN */ 337 + rbuf[off++] = 0; 338 + rbuf[off++] = DNS_CLASS_IN; 339 + /* TTL = 60 */ 340 + rbuf[off++] = 0; 341 + rbuf[off++] = 0; 342 + rbuf[off++] = 0; 343 + rbuf[off++] = 60; 344 + /* rdlength */ 345 + rbuf[off++] = addrlen >> 8; 346 + rbuf[off++] = addrlen & 0xff; 347 + /* rdata */ 348 + memcpy(rbuf + off, addr, addrlen); 349 + off += addrlen; 350 + 351 + return off; 352 + } 353 + 354 + size_t 355 + dns_build_ns_response(uint8_t *rbuf, size_t rlen, uint8_t *qbuf) 356 + { 357 + /* "localhost." in wire format */ 358 + static uint8_t localhost[] = { 359 + 9, 'l','o','c','a','l','h','o','s','t', '\0' 360 + }; 361 + size_t off, qsec_len; 362 + uint16_t flags; 363 + 364 + /* question: null label + qtype(2) + qclass(2) = 5 bytes for "." */ 365 + qsec_len = 5; 366 + 367 + if (DNS_HEADER_SIZE + qsec_len + 12 + sizeof(localhost) > rlen) { 368 + warnx("%s: bogus size (%d + %zu + 12 + %zu) > %zu", 369 + __func__, DNS_HEADER_SIZE, qsec_len, sizeof(localhost), 370 + rlen); 371 + return 0; 372 + } 373 + 374 + /* copy header and question */ 375 + memcpy(rbuf, qbuf, DNS_HEADER_SIZE + qsec_len); 376 + 377 + /* set response flags */ 378 + flags = DNS_FLAG_QR | DNS_FLAG_AA | DNS_FLAG_RA; 379 + if (qbuf[2] & (DNS_FLAG_RD >> 8)) 380 + flags |= DNS_FLAG_RD; 381 + rbuf[2] = flags >> 8; 382 + rbuf[3] = flags & 0xff; 383 + 384 + /* qdcount = 1 */ 385 + rbuf[4] = 0; rbuf[5] = 1; 386 + /* ancount = 1 */ 387 + rbuf[6] = 0; rbuf[7] = 1; 388 + /* nscount = 0 */ 389 + rbuf[8] = 0; rbuf[9] = 0; 390 + /* arcount = 0 */ 391 + rbuf[10] = 0; rbuf[11] = 0; 392 + 393 + off = DNS_HEADER_SIZE + qsec_len; 394 + 395 + /* answer: pointer to qname */ 396 + rbuf[off++] = 0xc0; 397 + rbuf[off++] = 0x0c; 398 + /* type NS */ 399 + rbuf[off++] = 0; 400 + rbuf[off++] = DNS_TYPE_NS; 401 + /* class IN */ 402 + rbuf[off++] = 0; 403 + rbuf[off++] = DNS_CLASS_IN; 404 + /* TTL = 86400 */ 405 + rbuf[off++] = 0; 406 + rbuf[off++] = 0x01; 407 + rbuf[off++] = 0x51; 408 + rbuf[off++] = 0x80; 409 + /* rdlength */ 410 + rbuf[off++] = 0; 411 + rbuf[off++] = sizeof(localhost); 412 + /* rdata: localhost. */ 413 + memcpy(rbuf + off, localhost, sizeof(localhost)); 414 + off += sizeof(localhost); 415 + 416 + return off; 417 + } 418 + 419 + size_t 420 + dns_build_nxdomain(uint8_t *rbuf, size_t rlen, uint8_t *qbuf, size_t qlen) 421 + { 422 + size_t qsec_len; 423 + uint16_t flags; 424 + 425 + /* find question section length */ 426 + for (qsec_len = 0; DNS_HEADER_SIZE + qsec_len < qlen; qsec_len++) { 427 + if (qbuf[DNS_HEADER_SIZE + qsec_len] == 0) { 428 + qsec_len += 5; /* null + qtype(2) + qclass(2) */ 429 + break; 430 + } 431 + } 432 + 433 + if (DNS_HEADER_SIZE + qsec_len > rlen) { 434 + warnx("%s: bogus size (%d + %zu) > %zu", 435 + __func__, DNS_HEADER_SIZE, qsec_len, rlen); 436 + return 0; 437 + } 438 + 439 + /* copy header and question */ 440 + memcpy(rbuf, qbuf, DNS_HEADER_SIZE + qsec_len); 441 + 442 + /* set response flags with NXDOMAIN rcode */ 443 + flags = DNS_FLAG_QR | DNS_FLAG_AA | DNS_FLAG_RA; 444 + if (qbuf[2] & (DNS_FLAG_RD >> 8)) 445 + flags |= DNS_FLAG_RD; 446 + rbuf[2] = flags >> 8; 447 + rbuf[3] = (flags & 0xff) | DNS_RCODE_NXDOMAIN; 448 + 449 + /* qdcount = 1 */ 450 + rbuf[4] = 0; rbuf[5] = 1; 451 + /* ancount = 0 */ 452 + rbuf[6] = 0; rbuf[7] = 0; 453 + /* nscount = 0 */ 454 + rbuf[8] = 0; rbuf[9] = 0; 455 + /* arcount = 0 */ 456 + rbuf[10] = 0; rbuf[11] = 0; 457 + 458 + return DNS_HEADER_SIZE + qsec_len; 459 + } 460 + 461 + int 462 + avahi_resolve(const char *name, int af, void *addr) 463 + { 464 + char buf[128], *tab, abuf[INET6_ADDRSTRLEN]; 465 + ssize_t n; 466 + pid_t pid; 467 + int pipefd[2], status; 468 + 469 + if (pipe(pipefd) == -1) 470 + return -1; 471 + 472 + pid = fork(); 473 + if (pid == -1) { 474 + close(pipefd[0]); 475 + close(pipefd[1]); 476 + return -1; 477 + } 478 + 479 + if (pid == 0) { 480 + close(pipefd[0]); 481 + if (dup2(pipefd[1], STDOUT_FILENO) == -1) 482 + _exit(1); 483 + close(pipefd[1]); 484 + execlp("avahi-resolve", "avahi-resolve", 485 + af == AF_INET6 ? "-6" : "-4", 486 + "-n", name, 487 + (char *)NULL); 488 + _exit(1); 489 + } 490 + 491 + close(pipefd[1]); 492 + 493 + n = read(pipefd[0], buf, sizeof(buf) - 1); 494 + close(pipefd[0]); 495 + 496 + if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status) || 497 + WEXITSTATUS(status) != 0) 498 + return -1; 499 + 500 + if (n <= 0) 501 + return -1; 502 + 503 + buf[n] = '\0'; 504 + 505 + /* strip trailing newline */ 506 + while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r')) 507 + buf[--n] = '\0'; 508 + 509 + /* avahi-resolve output: "hostname.local\t192.168.1.1\n" */ 510 + tab = strchr(buf, '\t'); 511 + if (tab == NULL) 512 + return -1; 513 + 514 + if (inet_pton(af, tab + 1, addr) != 1) 515 + return -1; 516 + 517 + if (verbose) { 518 + inet_ntop(af, addr, abuf, sizeof(abuf)); 519 + logmsg("%s %s -> %s\n", af == AF_INET6 ? "AAAA" : "A", 520 + name, abuf); 521 + } 522 + 523 + return 0; 524 + }
+9
avahi_proxy.rc
··· 1 + #!/bin/ksh 2 + 3 + daemon="/usr/local/bin/avahi-proxy" 4 + daemon_flags="-p 5300" 5 + daemon_user="nobody" 6 + 7 + . /etc/rc.d/rc.subr 8 + 9 + rc_cmd $1