1 /* $NetBSD: omshell.c,v 1.3 2022/04/03 01:10:58 christos Exp $ */
2
3 /* omshell.c
4
5 Examine and modify omapi objects. */
6
7 /*
8 * Copyright (C) 2004-2022 Internet Systems Consortium, Inc. ("ISC")
9 * Copyright (c) 2001-2003 by Internet Software Consortium
10 *
11 * This Source Code Form is subject to the terms of the Mozilla Public
12 * License, v. 2.0. If a copy of the MPL was not distributed with this
13 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
21 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 *
23 * Internet Systems Consortium, Inc.
24 * PO Box 360
25 * Newmarket, NH 03857 USA
26 * <info@isc.org>
27 * https://www.isc.org/
28 *
29 */
30
31 #include <sys/cdefs.h>
32 __RCSID("$NetBSD: omshell.c,v 1.3 2022/04/03 01:10:58 christos Exp $");
33
34 #include "config.h"
35
36 #include <time.h>
37 #include <sys/time.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <stdarg.h>
41 #include <string.h>
42 //#include "result.h"
43 #include <syslog.h>
44 #include "dhcpctl.h"
45 #include "dhcpd.h"
46 #include <isc/file.h>
47
48 extern uint16_t local_port;
49 extern uint16_t remote_port;
50 libdhcp_callbacks_t omshell_callbacks = {
51 &local_port,
52 &remote_port,
53 classify,
54 check_collection,
55 dhcp,
56 #ifdef DHCPv6
57 dhcpv6,
58 #endif /* DHCPv6 */
59 bootp,
60 find_class,
61 parse_allow_deny,
62 dhcp_set_control_state,
63 };
64
65 /* Fixups */
find_class(struct class ** c,const char * n,const char * f,int l)66 isc_result_t find_class (struct class **c, const char *n, const char *f, int l)
67 {
68 return 0;
69 }
parse_allow_deny(struct option_cache ** oc,struct parse * cfile,int flag)70 int parse_allow_deny (struct option_cache **oc, struct parse *cfile, int flag)
71 {
72 return 0;
73 }
dhcp(struct packet * packet)74 void dhcp (struct packet *packet) { }
bootp(struct packet * packet)75 void bootp (struct packet *packet) { }
76
77 #ifdef DHCPv6
78 /* XXX: should we warn or something here? */
dhcpv6(struct packet * packet)79 void dhcpv6(struct packet *packet) { }
80 #ifdef DHCP4o6
dhcpv4o6_handler(omapi_object_t * h)81 isc_result_t dhcpv4o6_handler(omapi_object_t *h)
82 {
83 return ISC_R_NOTIMPLEMENTED;
84 }
85 #endif /* DHCP4o6 */
86 #endif /* DHCPv6 */
87
check_collection(struct packet * p,struct lease * l,struct collection * c)88 int check_collection (struct packet *p, struct lease *l, struct collection *c)
89 {
90 return 0;
91 }
classify(struct packet * packet,struct class * class)92 void classify (struct packet *packet, struct class *class) { }
93
usage(const char * s)94 static void usage (const char *s) {
95 fprintf (stderr, "Usage: %s\n", s);
96 exit (1);
97 }
98
check(isc_result_t status,const char * func)99 static void check (isc_result_t status, const char *func) {
100 if (status != ISC_R_SUCCESS) {
101 fprintf (stderr, "%s: %s\n", func, isc_result_totext (status));
102 exit (1);
103 }
104 }
105
106 int
main(int argc,char ** argv)107 main(int argc, char **argv) {
108 isc_result_t status, waitstatus;
109 dhcpctl_handle connection;
110 dhcpctl_handle authenticator;
111 dhcpctl_handle oh;
112 struct data_string secret;
113 const char *name = 0, *algorithm = "hmac-md5";
114 int i;
115 int port = 7911;
116 const char *server = "127.0.0.1";
117 struct parse *cfile;
118 enum dhcp_token token;
119 const char *val;
120 char *s;
121 char buf[1024];
122 char s1[1024];
123 int connected = 0;
124 char hex_buf[1025];
125 char *progname;
126
127 #ifdef OLD_LOG_NAME
128 progname = "omshell";
129 #else
130 progname = argv[0];
131 #endif
132
133 libdhcp_callbacks_register(&omshell_callbacks);
134
135 for (i = 1; i < argc; i++) {
136 usage(isc_file_basename(progname));
137 }
138
139 /* Initially, log errors to stderr as well as to syslogd. */
140 openlog (isc_file_basename(progname),
141 DHCP_LOG_OPTIONS, DHCPD_LOG_FACILITY);
142 status = dhcpctl_initialize ();
143 if (status != ISC_R_SUCCESS) {
144 fprintf (stderr, "dhcpctl_initialize: %s\n",
145 isc_result_totext (status));
146 exit (1);
147 }
148
149 memset (&oh, 0, sizeof oh);
150
151 do {
152 if (!connected) {
153 } else if (oh == NULL) {
154 printf ("obj: <null>\n");
155 } else {
156 dhcpctl_remote_object_t *r = (dhcpctl_remote_object_t *)oh;
157 omapi_generic_object_t *g =
158 (omapi_generic_object_t *)(r -> inner);
159
160 printf ("obj: ");
161
162 if (r -> rtype -> type != omapi_datatype_string) {
163 printf ("?\n");
164 } else {
165 printf ("%.*s\n",
166 (int)(r -> rtype -> u . buffer . len),
167 r -> rtype -> u . buffer . value);
168 }
169
170 for (i = 0; i < g -> nvalues; i++) {
171 omapi_value_t *v = g -> values [i];
172
173 if (!g -> values [i])
174 continue;
175
176 printf ("%.*s = ", (int)v -> name -> len,
177 v -> name -> value);
178
179 if (!v -> value) {
180 printf ("<null>\n");
181 continue;
182 }
183 switch (v -> value -> type) {
184 case omapi_datatype_int:
185 printf ("%d\n",
186 v -> value -> u . integer);
187 break;
188
189 case omapi_datatype_string:
190 printf ("\"%.*s\"\n",
191 (int) v -> value -> u.buffer.len,
192 v -> value -> u.buffer.value);
193 break;
194
195 case omapi_datatype_data:
196 print_hex_or_string(v->value->u.buffer.len,
197 v->value->u.buffer.value,
198 sizeof(hex_buf), hex_buf);
199 printf("%s\n", hex_buf);
200 break;
201
202 case omapi_datatype_object:
203 printf ("<obj>\n");
204 break;
205 }
206 }
207 }
208
209 fputs ("> ", stdout);
210 fflush (stdout);
211 if (fgets (buf, sizeof(buf), stdin) == NULL)
212 break;
213
214 status = new_parse (&cfile, -1, buf, strlen(buf), "<STDIN>", 1);
215 check(status, "new_parse()");
216
217 token = next_token (&val, (unsigned *)0, cfile);
218 switch (token) {
219 default:
220 parse_warn (cfile, "unknown token: %s", val);
221 skip_to_semi (cfile);
222 break;
223
224 case END_OF_FILE:
225 case ENDOFLINE: /* EOL: */
226 break;
227
228 case TOKEN_HELP:
229 case QUESTIONMARK: /* '?': */
230 printf ("Commands:\n");
231 printf (" port <server omapi port>\n");
232 printf (" server <server address>\n");
233 printf (" key <key name> <key value>\n");
234 printf (" connect\n");
235 printf (" disconnect\n");
236 printf (" new <object-type>\n");
237 printf (" set <name> = <value>\n");
238 printf (" create\n");
239 printf (" open\n");
240 printf (" update\n");
241 printf (" unset <name>\n");
242 printf (" refresh\n");
243 printf (" remove\n");
244 skip_to_semi (cfile);
245 break;
246
247 case PORT:
248 token = next_token (&val, (unsigned *)0, cfile);
249 if (is_identifier (token)) {
250 struct servent *se;
251 se = getservbyname (val, "tcp");
252 if (se)
253 port = ntohs (se -> s_port);
254 else {
255 printf ("unknown service name: %s\n", val);
256 break;
257 }
258 } else if (token == NUMBER) {
259 port = atoi (val);
260 } else {
261 skip_to_semi (cfile);
262 printf ("usage: port <port>\n");
263 break;
264 }
265 token = next_token (&val, (unsigned *)0, cfile);
266 if (token != END_OF_FILE && token != EOL) {
267 printf ("usage: port <server>\n");
268 skip_to_semi (cfile);
269 break;
270 }
271 break;
272
273 case TOKEN_SERVER:
274 token = next_token (&val, (unsigned *)0, cfile);
275 if (token == NUMBER) {
276 int alen = (sizeof buf) - 1;
277 int len;
278
279 s = &buf [0];
280 len = strlen (val);
281 if (len + 1 > alen) {
282 baddq:
283 printf ("usage: server <server>\n");
284 skip_to_semi (cfile);
285 break;
286 } strcpy (buf, val);
287 s += len;
288 token = next_token (&val, (unsigned *)0, cfile);
289 if (token != DOT)
290 goto baddq;
291 *s++ = '.';
292 token = next_token (&val, (unsigned *)0, cfile);
293 if (token != NUMBER)
294 goto baddq;
295 len = strlen (val);
296 if (len + 1 > alen)
297 goto baddq;
298 strcpy (s, val);
299 s += len;
300 token = next_token (&val, (unsigned *)0, cfile);
301 if (token != DOT)
302 goto baddq;
303 *s++ = '.';
304 token = next_token (&val, (unsigned *)0, cfile);
305 if (token != NUMBER)
306 goto baddq;
307 len = strlen (val);
308 if (len + 1 > alen)
309 goto baddq;
310 strcpy (s, val);
311 s += len;
312 token = next_token (&val, (unsigned *)0, cfile);
313 if (token != DOT)
314 goto baddq;
315 *s++ = '.';
316 token = next_token (&val, (unsigned *)0, cfile);
317 if (token != NUMBER)
318 goto baddq;
319 len = strlen (val);
320 if (len + 1 > alen)
321 goto baddq;
322 strcpy (s, val);
323 val = &buf [0];
324 } else if (is_identifier (token)) {
325 /* Use val directly. */
326 } else {
327 printf ("usage: server <server>\n");
328 skip_to_semi (cfile);
329 break;
330 }
331
332 s = dmalloc (strlen (val) + 1, MDL);
333 if (!server) {
334 printf ("no memory to store server name.\n");
335 skip_to_semi (cfile);
336 break;
337 }
338 strcpy (s, val);
339 server = s;
340
341 token = next_token (&val, (unsigned *)0, cfile);
342 if (token != END_OF_FILE && token != EOL) {
343 printf ("usage: server <server>\n");
344 skip_to_semi (cfile);
345 break;
346 }
347 break;
348
349 case KEY_ALGORITHM:
350 /* Algorithm is optional */
351 token = next_token (&val, (unsigned *)0, cfile);
352 if (token != NAME || !is_identifier(token)) {
353 printf ("missing or invalid algorithm name\n");
354 printf ("usage: key-algoritm <algorithm name>\n");
355 skip_to_semi (cfile);
356 break;
357 }
358
359 s = dmalloc (strlen (val) + 1, MDL);
360 if (!s) {
361 printf ("no memory for algorithm name.\n");
362 skip_to_semi (cfile);
363 break;
364 }
365
366 strcpy (s, val);
367 algorithm = s;
368
369 token = next_token (&val, (unsigned *)0, cfile);
370 if (token != END_OF_FILE && token != EOL) {
371 printf ("extra information after %s\n", algorithm);
372 printf ("usage: key-algorithm <algorithm name>\n");
373 skip_to_semi (cfile);
374 break;
375 }
376
377 break;
378
379 case KEY:
380 token = peek_token(&val, (unsigned *)0, cfile);
381 if (token == STRING) {
382 token = next_token (&val, (unsigned *)0, cfile);
383 if (!is_identifier (token)) {
384 printf ("usage: key <name> <value>\n");
385 skip_to_semi (cfile);
386 break;
387 }
388 s = dmalloc (strlen (val) + 1, MDL);
389 if (!s) {
390 printf ("no memory for key name.\n");
391 skip_to_semi (cfile);
392 break;
393 }
394 strcpy (s, val);
395 } else {
396 s = parse_host_name(cfile);
397 if (s == NULL) {
398 printf ("usage: key <name> <value>\n");
399 skip_to_semi(cfile);
400 break;
401 }
402 }
403 name = s;
404
405 memset (&secret, 0, sizeof secret);
406 if (!parse_base64 (&secret, cfile)) {
407 skip_to_semi (cfile);
408 break;
409 }
410
411 token = next_token (&val, (unsigned *)0, cfile);
412 if (token != END_OF_FILE && token != EOL) {
413 printf ("usage: key <name> <value>\n");
414 skip_to_semi (cfile);
415 break;
416 }
417
418 break;
419
420 case CONNECT:
421 token = next_token (&val, (unsigned *)0, cfile);
422 if (token != END_OF_FILE && token != EOL) {
423 printf ("usage: connect\n");
424 skip_to_semi (cfile);
425 break;
426 }
427
428 authenticator = dhcpctl_null_handle;
429
430 if (name) {
431 status = dhcpctl_new_authenticator (&authenticator,
432 name, algorithm,
433 secret.data,
434 secret.len);
435
436 if (status != ISC_R_SUCCESS) {
437 fprintf (stderr,
438 "Cannot create authenticator: %s\n",
439 isc_result_totext (status));
440 break;
441 }
442 }
443
444 memset (&connection, 0, sizeof connection);
445 status = dhcpctl_connect (&connection,
446 server, port, authenticator);
447 if (status != ISC_R_SUCCESS) {
448 fprintf (stderr, "dhcpctl_connect: %s\n",
449 isc_result_totext (status));
450 break;
451 }
452 connected = 1;
453 break;
454
455 case DISCONNECT:
456 token = next_token (&val, (unsigned *)0, cfile);
457 if (token != END_OF_FILE && token != EOL) {
458 printf ("usage: disconnect\n");
459 skip_to_semi (cfile);
460 break;
461 }
462
463 if (!connected || !connection) {
464 fprintf (stderr, "not connected\n");
465 break;
466 }
467
468 status = dhcpctl_disconnect (&connection, 0);
469 if (status != ISC_R_SUCCESS) {
470 fprintf (stderr, "dhcpctl_disconnect: %s\n",
471 isc_result_totext (status));
472 break;
473 }
474 connected = 0;
475 break;
476
477 case TOKEN_NEW:
478 token = next_token (&val, (unsigned *)0, cfile);
479 if ((!is_identifier (token) && token != STRING)) {
480 printf ("usage: new <object-type>\n");
481 break;
482 }
483
484 if (oh) {
485 printf ("an object is already open.\n");
486 skip_to_semi (cfile);
487 break;
488 }
489
490 if (!connected) {
491 printf ("not connected.\n");
492 skip_to_semi (cfile);
493 break;
494 }
495
496 status = dhcpctl_new_object (&oh, connection, val);
497 if (status != ISC_R_SUCCESS) {
498 printf ("can't create object: %s\n",
499 isc_result_totext (status));
500 break;
501 }
502
503 token = next_token (&val, (unsigned *)0, cfile);
504 if (token != END_OF_FILE && token != EOL) {
505 printf ("usage: new <object-type>\n");
506 skip_to_semi (cfile);
507 break;
508 }
509 break;
510
511 case TOKEN_CLOSE:
512 token = next_token (&val, (unsigned *)0, cfile);
513 if (token != END_OF_FILE && token != EOL) {
514 printf ("usage: close\n");
515 skip_to_semi (cfile);
516 break;
517 }
518
519 if (!connected) {
520 printf ("not connected.\n");
521 skip_to_semi (cfile);
522 break;
523 }
524
525 if (!oh) {
526 printf ("not open.\n");
527 skip_to_semi (cfile);
528 break;
529 }
530 omapi_object_dereference (&oh, MDL);
531
532 break;
533
534 case TOKEN_SET:
535 token = next_token (&val, (unsigned *)0, cfile);
536
537 if ((!is_identifier (token) && token != STRING)) {
538 set_usage:
539 printf ("usage: set <name> = <value>\n");
540 skip_to_semi (cfile);
541 break;
542 }
543
544 if (oh == NULL) {
545 printf ("no open object.\n");
546 skip_to_semi (cfile);
547 break;
548 }
549
550 if (!connected) {
551 printf ("not connected.\n");
552 skip_to_semi (cfile);
553 break;
554 }
555
556 #ifdef HAVE_STRLCPY
557 strlcpy (s1, val, sizeof(s1));
558 #else
559 s1[0] = 0;
560 strncat (s1, val, sizeof(s1)-strlen(s1)-1);
561 #endif
562
563 token = next_token (&val, (unsigned *)0, cfile);
564 if (token != EQUAL)
565 goto set_usage;
566
567 token = next_token (&val, (unsigned *)0, cfile);
568 switch (token) {
569 case STRING:
570 dhcpctl_set_string_value (oh, val, s1);
571 token = next_token (&val, (unsigned *)0, cfile);
572 break;
573
574 case NUMBER:
575 strcpy (buf, val);
576 token = peek_token (&val, (unsigned *)0, cfile);
577 /* Colon-separated hex list? */
578 if (token == COLON)
579 goto cshl;
580 else if (token == DOT) {
581 s = buf;
582 val = buf;
583 do {
584 int intval = atoi (val);
585 if (intval > 255) {
586 parse_warn (cfile,
587 "dotted octet > 255: %s",
588 val);
589 skip_to_semi (cfile);
590 goto badnum;
591 }
592 *s++ = intval;
593 token = next_token (&val,
594 (unsigned *)0, cfile);
595 if (token != DOT)
596 break;
597 /* DOT is zero. */
598 while ((token = next_token (&val,
599 (unsigned *)0, cfile)) == DOT)
600 *s++ = 0;
601 } while (token == NUMBER);
602 dhcpctl_set_data_value (oh, buf,
603 (unsigned)(s - buf),
604 s1);
605 break;
606 }
607 dhcpctl_set_int_value (oh, atoi (buf), s1);
608 token = next_token (&val, (unsigned *)0, cfile);
609 badnum:
610 break;
611
612 case NUMBER_OR_NAME:
613 strcpy (buf, val);
614 cshl:
615 s = buf;
616 val = buf;
617 do {
618 convert_num (cfile, (unsigned char *)s,
619 val, 16, 8);
620 ++s;
621 token = next_token (&val,
622 (unsigned *)0, cfile);
623 if (token != COLON)
624 break;
625 token = next_token (&val,
626 (unsigned *)0, cfile);
627 } while (token == NUMBER ||
628 token == NUMBER_OR_NAME);
629 dhcpctl_set_data_value (oh, buf,
630 (unsigned)(s - buf), s1);
631 break;
632
633 default:
634 printf ("invalid value.\n");
635 skip_to_semi (cfile);
636 }
637
638 if (token != END_OF_FILE && token != EOL)
639 goto set_usage;
640 break;
641
642 case UNSET:
643 token = next_token (&val, (unsigned *)0, cfile);
644
645 if ((!is_identifier (token) && token != STRING)) {
646 unset_usage:
647 printf ("usage: unset <name>\n");
648 skip_to_semi (cfile);
649 break;
650 }
651
652 if (!oh) {
653 printf ("no open object.\n");
654 skip_to_semi (cfile);
655 break;
656 }
657
658 if (!connected) {
659 printf ("not connected.\n");
660 skip_to_semi (cfile);
661 break;
662 }
663
664 #if HAVE_STRLCPY
665 strlcpy (s1, val, sizeof(s1));
666 #else
667 s1[0] = 0;
668 strncat (s1, val, sizeof(s1)-strlen(s1)-1);
669 #endif
670
671 token = next_token (&val, (unsigned *)0, cfile);
672 if (token != END_OF_FILE && token != EOL)
673 goto unset_usage;
674
675 dhcpctl_set_null_value (oh, s1);
676 break;
677
678
679 case TOKEN_CREATE:
680 case TOKEN_OPEN:
681 i = token;
682 token = next_token (&val, (unsigned *)0, cfile);
683 if (token != END_OF_FILE && token != EOL) {
684 printf ("usage: %s\n", val);
685 skip_to_semi (cfile);
686 break;
687 }
688
689 if (!connected) {
690 printf ("not connected.\n");
691 skip_to_semi (cfile);
692 break;
693 }
694
695 if (!oh) {
696 printf ("you must make a new object first!\n");
697 skip_to_semi (cfile);
698 break;
699 }
700
701 if (i == TOKEN_CREATE)
702 i = DHCPCTL_CREATE | DHCPCTL_EXCL;
703 else
704 i = 0;
705
706 status = dhcpctl_open_object (oh, connection, i);
707 if (status == ISC_R_SUCCESS)
708 status = dhcpctl_wait_for_completion
709 (oh, &waitstatus);
710 if (status == ISC_R_SUCCESS)
711 status = waitstatus;
712 if (status != ISC_R_SUCCESS) {
713 printf ("can't open object: %s\n",
714 isc_result_totext (status));
715 break;
716 }
717
718 break;
719
720 case UPDATE:
721 token = next_token (&val, (unsigned *)0, cfile);
722 if (token != END_OF_FILE && token != EOL) {
723 printf ("usage: %s\n", val);
724 skip_to_semi (cfile);
725 break;
726 }
727
728 if (!connected) {
729 printf ("not connected.\n");
730 skip_to_semi (cfile);
731 break;
732 }
733
734 if (!oh) {
735 printf ("you haven't opened an object yet!\n");
736 skip_to_semi (cfile);
737 break;
738 }
739
740 status = dhcpctl_object_update(connection, oh);
741 if (status == ISC_R_SUCCESS)
742 status = dhcpctl_wait_for_completion
743 (oh, &waitstatus);
744 if (status == ISC_R_SUCCESS)
745 status = waitstatus;
746 if (status != ISC_R_SUCCESS) {
747 printf ("can't update object: %s\n",
748 isc_result_totext (status));
749 break;
750 }
751
752 break;
753
754 case REMOVE:
755 token = next_token (&val, (unsigned *)0, cfile);
756 if (token != END_OF_FILE && token != EOL) {
757 printf ("usage: remove\n");
758 skip_to_semi (cfile);
759 break;
760 }
761
762 if (!connected) {
763 printf ("not connected.\n");
764 break;
765 }
766
767 if (!oh) {
768 printf ("no object.\n");
769 break;
770 }
771
772 status = dhcpctl_object_remove(connection, oh);
773 if (status == ISC_R_SUCCESS)
774 status = dhcpctl_wait_for_completion
775 (oh, &waitstatus);
776 if (status == ISC_R_SUCCESS)
777 status = waitstatus;
778 if (status != ISC_R_SUCCESS) {
779 printf ("can't destroy object: %s\n",
780 isc_result_totext (status));
781 break;
782 }
783 omapi_object_dereference (&oh, MDL);
784 break;
785
786 case REFRESH:
787 token = next_token (&val, (unsigned *)0, cfile);
788 if (token != END_OF_FILE && token != EOL) {
789 printf ("usage: refresh\n");
790 skip_to_semi (cfile);
791 break;
792 }
793
794 if (!connected) {
795 printf ("not connected.\n");
796 break;
797 }
798
799 if (!oh) {
800 printf ("no object.\n");
801 break;
802 }
803
804 status = dhcpctl_object_refresh(connection, oh);
805 if (status == ISC_R_SUCCESS)
806 status = dhcpctl_wait_for_completion
807 (oh, &waitstatus);
808 if (status == ISC_R_SUCCESS)
809 status = waitstatus;
810 if (status != ISC_R_SUCCESS) {
811 printf ("can't refresh object: %s\n",
812 isc_result_totext (status));
813 break;
814 }
815
816 break;
817 }
818 end_parse (&cfile);
819 } while (1);
820
821 exit (0);
822 }
823
824 /* Sigh */
dhcp_set_control_state(control_object_state_t oldstate,control_object_state_t newstate)825 isc_result_t dhcp_set_control_state (control_object_state_t oldstate,
826 control_object_state_t newstate)
827 {
828 if (newstate != server_shutdown)
829 return ISC_R_SUCCESS;
830 exit (0);
831 }
832