1 /*
2 * Copyright (c) 2015-2019 Red Hat, Inc.
3 *
4 * All rights reserved.
5 *
6 * Author: Jan Friesse (jfriesse@redhat.com)
7 *
8 * This software licensed under BSD license, the text of which follows:
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met:
12 *
13 * - Redistributions of source code must retain the above copyright notice,
14 * this list of conditions and the following disclaimer.
15 * - Redistributions in binary form must reproduce the above copyright notice,
16 * this list of conditions and the following disclaimer in the documentation
17 * and/or other materials provided with the distribution.
18 * - Neither the name of the Red Hat, Inc. nor the names of its
19 * contributors may be used to endorse or promote products derived from this
20 * software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
32 * THE POSSIBILITY OF SUCH DAMAGE.
33 */
34
35 #include <err.h>
36 #include <errno.h>
37 #include <getopt.h>
38 #include <limits.h>
39 #include <signal.h>
40 #include <unistd.h>
41
42 #include "qnet-config.h"
43
44 #include "dynar.h"
45 #include "dynar-str.h"
46 #include "dynar-getopt-lex.h"
47 #include "nss-sock.h"
48 #include "pr-poll-array.h"
49 #include "qnetd-advanced-settings.h"
50 #include "qnetd-algorithm.h"
51 #include "qnetd-instance.h"
52 #include "qnetd-ipc.h"
53 #include "qnetd-log.h"
54 #include "qnetd-client-net.h"
55 #include "qnetd-client-msg-received.h"
56 #include "qnetd-poll-array-user-data.h"
57 #include "utils.h"
58 #include "msg.h"
59
60 /*
61 * This is global variable used for comunication with main loop and signal (calls close)
62 */
63 struct qnetd_instance *global_instance;
64
65 enum tlv_decision_algorithm_type
66 qnetd_static_supported_decision_algorithms[QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE] = {
67 TLV_DECISION_ALGORITHM_TYPE_TEST,
68 TLV_DECISION_ALGORITHM_TYPE_FFSPLIT,
69 TLV_DECISION_ALGORITHM_TYPE_2NODELMS,
70 TLV_DECISION_ALGORITHM_TYPE_LMS,
71 };
72
73 static void
qnetd_err_nss(void)74 qnetd_err_nss(void)
75 {
76
77 qnetd_log_nss(LOG_CRIT, "NSS error");
78
79 exit(1);
80 }
81
82 static void
qnetd_warn_nss(void)83 qnetd_warn_nss(void)
84 {
85
86 qnetd_log_nss(LOG_WARNING, "NSS warning");
87 }
88
89 static PRPollDesc *
qnetd_pr_poll_array_create(struct qnetd_instance * instance)90 qnetd_pr_poll_array_create(struct qnetd_instance *instance)
91 {
92 struct pr_poll_array *poll_array;
93 const struct qnetd_client_list *client_list;
94 struct qnetd_client *client;
95 PRPollDesc *poll_desc;
96 struct qnetd_poll_array_user_data *user_data;
97 const struct unix_socket_client_list *ipc_client_list;
98 struct unix_socket_client *ipc_client;
99
100 poll_array = &instance->poll_array;
101 client_list = &instance->clients;
102 ipc_client_list = &instance->local_ipc.clients;
103
104 pr_poll_array_clean(poll_array);
105
106 if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
107 return (NULL);
108 }
109
110 poll_desc->fd = instance->server.socket;
111 poll_desc->in_flags = PR_POLL_READ;
112
113 user_data->type = QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET;
114
115 if (qnetd_ipc_is_closed(instance)) {
116 qnetd_log(LOG_DEBUG, "Listening socket is closed");
117
118 return (NULL);
119 }
120
121 if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
122 return (NULL);
123 }
124
125 poll_desc->fd = instance->ipc_socket_poll_fd;
126 poll_desc->in_flags = PR_POLL_READ;
127 user_data->type = QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET;
128
129 TAILQ_FOREACH(client, client_list, entries) {
130 if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
131 return (NULL);
132 }
133 poll_desc->fd = client->socket;
134 poll_desc->in_flags = PR_POLL_READ;
135
136 if (!send_buffer_list_empty(&client->send_buffer_list)) {
137 poll_desc->in_flags |= PR_POLL_WRITE;
138 }
139
140 user_data->type = QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT;
141 user_data->client = client;
142 }
143
144 TAILQ_FOREACH(ipc_client, ipc_client_list, entries) {
145 if (!ipc_client->reading_line && !ipc_client->writing_buffer) {
146 continue;
147 }
148
149 if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
150 return (NULL);
151 }
152
153 poll_desc->fd = ((struct qnetd_ipc_user_data *)ipc_client->user_data)->nspr_poll_fd;
154 if (ipc_client->reading_line) {
155 poll_desc->in_flags |= PR_POLL_READ;
156 }
157
158 if (ipc_client->writing_buffer) {
159 poll_desc->in_flags |= PR_POLL_WRITE;
160 }
161
162 user_data->type = QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT;
163 user_data->ipc_client = ipc_client;
164 }
165
166 pr_poll_array_gc(poll_array);
167
168 return (poll_array->array);
169 }
170
171 static int
qnetd_poll(struct qnetd_instance * instance)172 qnetd_poll(struct qnetd_instance *instance)
173 {
174 struct qnetd_client *client;
175 PRPollDesc *pfds;
176 PRInt32 poll_res;
177 ssize_t i;
178 int client_disconnect;
179 struct qnetd_poll_array_user_data *user_data;
180 struct unix_socket_client *ipc_client;
181
182 client = NULL;
183 client_disconnect = 0;
184
185 pfds = qnetd_pr_poll_array_create(instance);
186 if (pfds == NULL) {
187 return (-1);
188 }
189
190 if ((poll_res = PR_Poll(pfds, pr_poll_array_size(&instance->poll_array),
191 timer_list_time_to_expire(&instance->main_timer_list))) >= 0) {
192 timer_list_expire(&instance->main_timer_list);
193
194 /*
195 * Walk thru pfds array and process events
196 */
197 for (i = 0; i < pr_poll_array_size(&instance->poll_array); i++) {
198 user_data = pr_poll_array_get_user_data(&instance->poll_array, i);
199
200 client = NULL;
201 ipc_client = NULL;
202 client_disconnect = 0;
203
204 switch (user_data->type) {
205 case QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET:
206 break;
207 case QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT:
208 client = user_data->client;
209 client_disconnect = client->schedule_disconnect;
210 break;
211 case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET:
212 break;
213 case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT:
214 ipc_client = user_data->ipc_client;
215 client_disconnect = ipc_client->schedule_disconnect;
216 }
217
218 if (!client_disconnect && poll_res > 0 &&
219 pfds[i].out_flags & PR_POLL_READ) {
220 switch (user_data->type) {
221 case QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET:
222 qnetd_client_net_accept(instance);
223 break;
224 case QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT:
225 if (qnetd_client_net_read(instance, client) == -1) {
226 client_disconnect = 1;
227 }
228 break;
229 case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET:
230 qnetd_ipc_accept(instance, &ipc_client);
231 break;
232 case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT:
233 qnetd_ipc_io_read(instance, ipc_client);
234 break;
235 }
236 }
237
238 if (!client_disconnect && poll_res > 0 &&
239 pfds[i].out_flags & PR_POLL_WRITE) {
240 switch (user_data->type) {
241 case QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET:
242 /*
243 * Poll write on listen socket -> fatal error
244 */
245 qnetd_log(LOG_CRIT, "POLL_WRITE on listening socket");
246
247 return (-1);
248 break;
249 case QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT:
250 if (qnetd_client_net_write(instance, client) == -1) {
251 client_disconnect = 1;
252 }
253 break;
254 case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET:
255 qnetd_log(LOG_CRIT, "POLL_WRITE on listening IPC socket");
256 return (-1);
257 break;
258 case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT:
259 qnetd_ipc_io_write(instance, ipc_client);
260 break;
261 }
262 }
263
264 if (!client_disconnect && poll_res > 0 &&
265 (pfds[i].out_flags & (PR_POLL_ERR|PR_POLL_NVAL|PR_POLL_HUP|PR_POLL_EXCEPT)) &&
266 !(pfds[i].out_flags & (PR_POLL_READ|PR_POLL_WRITE))) {
267 switch (user_data->type) {
268 case QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET:
269 case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET:
270 if (pfds[i].out_flags != PR_POLL_NVAL) {
271 /*
272 * Poll ERR on listening socket is fatal error.
273 * POLL_NVAL is used as a signal to quit poll loop.
274 */
275 qnetd_log(LOG_CRIT, "POLL_ERR (%u) on listening "
276 "socket", pfds[i].out_flags);
277 } else {
278 qnetd_log(LOG_DEBUG, "Listening socket is closed");
279 }
280
281 return (-1);
282 break;
283 case QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT:
284 qnetd_log(LOG_DEBUG, "POLL_ERR (%u) on client socket. "
285 "Disconnecting.", pfds[i].out_flags);
286
287 client_disconnect = 1;
288 break;
289 case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT:
290 qnetd_log(LOG_DEBUG, "POLL_ERR (%u) on ipc client socket."
291 " Disconnecting.", pfds[i].out_flags);
292
293 client_disconnect = 1;
294 break;
295 }
296 }
297
298 /*
299 * If client is scheduled for disconnect, disconnect it
300 */
301 if (user_data->type == QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT &&
302 client_disconnect) {
303 qnetd_instance_client_disconnect(instance, client, 0);
304 } else if (user_data->type == QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT &&
305 (client_disconnect || ipc_client->schedule_disconnect)) {
306 qnetd_ipc_client_disconnect(instance, ipc_client);
307 }
308 }
309 }
310
311
312 return (0);
313 }
314
315 static void
signal_int_handler(int sig)316 signal_int_handler(int sig)
317 {
318
319 qnetd_log(LOG_DEBUG, "SIGINT received - closing server IPC socket");
320
321 qnetd_ipc_close(global_instance);
322 }
323
324 static void
signal_term_handler(int sig)325 signal_term_handler(int sig)
326 {
327
328 qnetd_log(LOG_DEBUG, "SIGTERM received - closing server IPC socket");
329
330 qnetd_ipc_close(global_instance);
331 }
332
333 static void
signal_handlers_register(void)334 signal_handlers_register(void)
335 {
336 struct sigaction act;
337
338 act.sa_handler = signal_int_handler;
339 sigemptyset(&act.sa_mask);
340 act.sa_flags = SA_RESTART;
341
342 sigaction(SIGINT, &act, NULL);
343
344 act.sa_handler = signal_term_handler;
345 sigemptyset(&act.sa_mask);
346 act.sa_flags = SA_RESTART;
347
348 sigaction(SIGTERM, &act, NULL);
349 }
350
351 static void
usage(void)352 usage(void)
353 {
354
355 printf("usage: %s [-46dfhv] [-l listen_addr] [-p listen_port] [-s tls]\n", QNETD_PROGRAM_NAME);
356 printf("%14s[-c client_cert_required] [-m max_clients] [-S option=value[,option2=value2,...]]\n", "");
357 }
358
359 static void
display_version(void)360 display_version(void)
361 {
362 enum msg_type *supported_messages;
363 size_t no_supported_messages;
364 size_t zi;
365
366 msg_get_supported_messages(&supported_messages, &no_supported_messages);
367 printf("Corosync Qdevice Network Daemon, version '%s'\n\n", VERSION);
368 printf("Supported algorithms: ");
369 for (zi = 0; zi < QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE; zi++) {
370 if (zi != 0) {
371 printf(", ");
372 }
373 printf("%s (%u)",
374 tlv_decision_algorithm_type_to_str(qnetd_static_supported_decision_algorithms[zi]),
375 qnetd_static_supported_decision_algorithms[zi]);
376 }
377 printf("\n");
378 printf("Supported message types: ");
379 for (zi = 0; zi < no_supported_messages; zi++) {
380 if (zi != 0) {
381 printf(", ");
382 }
383 printf("%s (%u)", msg_type_to_str(supported_messages[zi]), supported_messages[zi]);
384 }
385 printf("\n");
386 }
387
388 static void
cli_parse_long_opt(struct qnetd_advanced_settings * advanced_settings,const char * long_opt)389 cli_parse_long_opt(struct qnetd_advanced_settings *advanced_settings, const char *long_opt)
390 {
391 struct dynar_getopt_lex lex;
392 struct dynar dynar_long_opt;
393 const char *opt;
394 const char *val;
395 int res;
396
397 dynar_init(&dynar_long_opt, strlen(long_opt) + 1);
398 if (dynar_str_cpy(&dynar_long_opt, long_opt) != 0) {
399 errx(1, "Can't alloc memory for long option");
400 }
401
402 dynar_getopt_lex_init(&lex, &dynar_long_opt);
403
404 while (dynar_getopt_lex_token_next(&lex) == 0 && strcmp(dynar_data(&lex.option), "") != 0) {
405 opt = dynar_data(&lex.option);
406 val = dynar_data(&lex.value);
407
408 res = qnetd_advanced_settings_set(advanced_settings, opt, val);
409 switch (res) {
410 case -1:
411 errx(1, "Unknown option '%s'", opt);
412 break;
413 case -2:
414 errx(1, "Invalid value '%s' for option '%s'", val, opt);
415 break;
416 }
417 }
418
419 dynar_getopt_lex_destroy(&lex);
420 dynar_destroy(&dynar_long_opt);
421 }
422
423 static void
cli_parse(int argc,char * const argv[],char ** host_addr,uint16_t * host_port,int * foreground,int * debug_log,int * bump_log_priority,enum tlv_tls_supported * tls_supported,int * client_cert_required,size_t * max_clients,PRIntn * address_family,struct qnetd_advanced_settings * advanced_settings)424 cli_parse(int argc, char * const argv[], char **host_addr, uint16_t *host_port, int *foreground,
425 int *debug_log, int *bump_log_priority, enum tlv_tls_supported *tls_supported,
426 int *client_cert_required, size_t *max_clients, PRIntn *address_family,
427 struct qnetd_advanced_settings *advanced_settings)
428 {
429 int ch;
430 long long int tmpll;
431
432 *host_addr = NULL;
433 *host_port = QNETD_DEFAULT_HOST_PORT;
434 *foreground = 0;
435 *debug_log = 0;
436 *bump_log_priority = 0;
437 *tls_supported = QNETD_DEFAULT_TLS_SUPPORTED;
438 *client_cert_required = QNETD_DEFAULT_TLS_CLIENT_CERT_REQUIRED;
439 *max_clients = QNETD_DEFAULT_MAX_CLIENTS;
440 *address_family = PR_AF_UNSPEC;
441
442 while ((ch = getopt(argc, argv, "46dfhvc:l:m:p:S:s:")) != -1) {
443 switch (ch) {
444 case '4':
445 *address_family = PR_AF_INET;
446 break;
447 case '6':
448 *address_family = PR_AF_INET6;
449 break;
450 case 'f':
451 *foreground = 1;
452 break;
453 case 'd':
454 if (*debug_log) {
455 *bump_log_priority = 1;
456 }
457 *debug_log = 1;
458 break;
459 case 'c':
460 if ((*client_cert_required = utils_parse_bool_str(optarg)) == -1) {
461 errx(1, "client_cert_required should be on/yes/1, off/no/0");
462 }
463 break;
464 case 'l':
465 free(*host_addr);
466 *host_addr = strdup(optarg);
467 if (*host_addr == NULL) {
468 errx(1, "Can't alloc memory for host addr string");
469 }
470 break;
471 case 'm':
472 if (utils_strtonum(optarg, 0, LLONG_MAX, &tmpll) == -1) {
473 errx(1, "max clients value %s is invalid", optarg);
474 }
475
476 *max_clients = (size_t)tmpll;
477 break;
478 case 'p':
479 if (utils_strtonum(optarg, 1, UINT16_MAX, &tmpll) == -1) {
480 errx(1, "host port must be in range 1-%u", UINT16_MAX);
481 }
482
483 *host_port = tmpll;
484 break;
485 case 'S':
486 cli_parse_long_opt(advanced_settings, optarg);
487 break;
488 case 's':
489 if (strcasecmp(optarg, "on") == 0) {
490 *tls_supported = QNETD_DEFAULT_TLS_SUPPORTED;
491 } else if (strcasecmp(optarg, "off") == 0) {
492 *tls_supported = TLV_TLS_UNSUPPORTED;
493 } else if (strcasecmp(optarg, "req") == 0) {
494 *tls_supported = TLV_TLS_REQUIRED;
495 } else {
496 errx(1, "tls must be one of on, off, req");
497 }
498 break;
499 case 'v':
500 display_version();
501 exit(1);
502 break;
503 case 'h':
504 case '?':
505 usage();
506 exit(1);
507 break;
508 }
509 }
510 }
511
512 int
main(int argc,char * const argv[])513 main(int argc, char * const argv[])
514 {
515 struct qnetd_instance instance;
516 struct qnetd_advanced_settings advanced_settings;
517 char *host_addr;
518 uint16_t host_port;
519 int foreground;
520 int debug_log;
521 int bump_log_priority;
522 enum tlv_tls_supported tls_supported;
523 int client_cert_required;
524 size_t max_clients;
525 PRIntn address_family;
526 int lock_file;
527 int another_instance_running;
528
529 if (qnetd_advanced_settings_init(&advanced_settings) != 0) {
530 errx(1, "Can't alloc memory for advanced settings");
531 }
532
533 cli_parse(argc, argv, &host_addr, &host_port, &foreground, &debug_log, &bump_log_priority,
534 &tls_supported, &client_cert_required, &max_clients, &address_family, &advanced_settings);
535
536 if (foreground) {
537 qnetd_log_init(QNETD_LOG_TARGET_STDERR);
538 } else {
539 qnetd_log_init(QNETD_LOG_TARGET_SYSLOG);
540 }
541
542 qnetd_log_set_debug(debug_log);
543 qnetd_log_set_priority_bump(bump_log_priority);
544
545 /*
546 * Check that it's possible to open NSS dir if needed
547 */
548 if (nss_sock_check_db_dir((tls_supported != TLV_TLS_UNSUPPORTED ?
549 advanced_settings.nss_db_dir : NULL)) != 0) {
550 qnetd_log_err(LOG_ERR, "Can't open NSS DB directory");
551
552 exit (1);
553 }
554
555 /*
556 * Daemonize
557 */
558 if (!foreground) {
559 utils_tty_detach();
560 }
561
562 if ((lock_file = utils_flock(advanced_settings.lock_file, getpid(),
563 &another_instance_running)) == -1) {
564 if (another_instance_running) {
565 qnetd_log(LOG_ERR, "Another instance is running");
566 } else {
567 qnetd_log_err(LOG_ERR, "Can't acquire lock");
568 }
569
570 exit(1);
571 }
572
573 qnetd_log(LOG_DEBUG, "Initializing nss");
574 if (nss_sock_init_nss((tls_supported != TLV_TLS_UNSUPPORTED ?
575 advanced_settings.nss_db_dir : NULL)) != 0) {
576 qnetd_err_nss();
577 }
578
579 if (SSL_ConfigServerSessionIDCache(0, 0, 0, NULL) != SECSuccess) {
580 qnetd_err_nss();
581 }
582
583 if (qnetd_instance_init(&instance, tls_supported, client_cert_required,
584 max_clients, &advanced_settings) == -1) {
585 qnetd_log(LOG_ERR, "Can't initialize qnetd");
586 exit(1);
587 }
588 instance.host_addr = host_addr;
589 instance.host_port = host_port;
590
591 if (tls_supported != TLV_TLS_UNSUPPORTED && qnetd_instance_init_certs(&instance) == -1) {
592 qnetd_err_nss();
593 }
594
595 qnetd_log(LOG_DEBUG, "Initializing local socket");
596 if (qnetd_ipc_init(&instance) != 0) {
597 return (1);
598 }
599
600 qnetd_log(LOG_DEBUG, "Creating listening socket");
601 instance.server.socket = nss_sock_create_listen_socket(instance.host_addr,
602 instance.host_port, address_family);
603 if (instance.server.socket == NULL) {
604 qnetd_err_nss();
605 }
606
607 if (nss_sock_set_non_blocking(instance.server.socket) != 0) {
608 qnetd_err_nss();
609 }
610
611 if (PR_Listen(instance.server.socket, instance.advanced_settings->listen_backlog) !=
612 PR_SUCCESS) {
613 qnetd_err_nss();
614 }
615
616 global_instance = &instance;
617 signal_handlers_register();
618
619 qnetd_log(LOG_DEBUG, "Registering algorithms");
620 if (qnetd_algorithm_register_all() != 0) {
621 exit(1);
622 }
623
624 qnetd_log(LOG_DEBUG, "QNetd ready to provide service");
625 /*
626 * MAIN LOOP
627 */
628 while (qnetd_poll(&instance) == 0) {
629 }
630
631 /*
632 * Cleanup
633 */
634 qnetd_ipc_destroy(&instance);
635
636 if (PR_Close(instance.server.socket) != PR_SUCCESS) {
637 qnetd_warn_nss();
638 }
639
640 CERT_DestroyCertificate(instance.server.cert);
641 SECKEY_DestroyPrivateKey(instance.server.private_key);
642
643 SSL_ClearSessionCache();
644
645 SSL_ShutdownServerSessionIDCache();
646
647 qnetd_instance_destroy(&instance);
648
649 qnetd_advanced_settings_destroy(&advanced_settings);
650
651 if (NSS_Shutdown() != SECSuccess) {
652 qnetd_warn_nss();
653 }
654
655 if (PR_Cleanup() != PR_SUCCESS) {
656 qnetd_warn_nss();
657 }
658
659 qnetd_log_close();
660
661 return (0);
662 }
663