secmod_common.c (22011B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020, 2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file util/secmod_common.c 18 * @brief Common functions for the exchange security modules 19 * @author Florian Dold <dold@taler.net> 20 */ 21 #include "platform.h" 22 #include "taler/taler_util.h" 23 #include "secmod_common.h" 24 #include <poll.h> 25 #ifdef __linux__ 26 #include <sys/eventfd.h> 27 #endif 28 29 30 char * 31 TES_normalize_section (const char *section) 32 { 33 char *result = GNUNET_strdup (section); 34 35 for (char *p = result; '\0' != *p; p++) 36 *p = (char) tolower ((unsigned char) *p); 37 return result; 38 } 39 40 41 /** 42 * A denomination directory to rename after checking for conflicts. 43 */ 44 struct KeyDirectory 45 { 46 char *old_name; 47 char *new_name; 48 }; 49 50 51 /** 52 * Closure for #check_section_name and #collect_key_directory. 53 */ 54 struct KeyDirectoryContext 55 { 56 const char *cprefix; 57 struct KeyDirectory *dirs; 58 unsigned int num_dirs; 59 bool invalid_section_name; 60 }; 61 62 63 /** 64 * Reject section names that would use nested key directories. 65 * 66 * @param cls a `struct KeyDirectoryContext` 67 * @param section configuration section name to check 68 */ 69 static void 70 check_section_name (void *cls, 71 const char *section) 72 { 73 struct KeyDirectoryContext *ctx = cls; 74 size_t prefix_len = strlen (ctx->cprefix); 75 76 if ( (0 != strncasecmp (section, 77 ctx->cprefix, 78 prefix_len)) || 79 (NULL == strchr (section + prefix_len, '/')) ) 80 return; 81 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 82 "Invalid denomination section `%s': suffix must not contain '/'\n", 83 section); 84 ctx->invalid_section_name = true; 85 } 86 87 88 /** 89 * Collect directories requiring migration without modifying the directory 90 * being scanned. Reject symlinks and check all destinations before starting 91 * the migration. 92 * 93 * @param cls a `struct KeyDirectoryContext` 94 * @param filename directory entry to inspect 95 * @return #GNUNET_OK to continue, #GNUNET_SYSERR on a symlink, conflict or I/O error 96 */ 97 static enum GNUNET_GenericReturnValue 98 collect_key_directory (void *cls, 99 const char *filename) 100 { 101 struct KeyDirectoryContext *ctx = cls; 102 const char *base = strrchr (filename, '/') + 1; 103 struct KeyDirectory dir; 104 struct stat sb; 105 struct stat target; 106 char *normalized; 107 108 if (0 != lstat (filename, &sb)) 109 { 110 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 111 "lstat", 112 filename); 113 return GNUNET_SYSERR; 114 } 115 if (S_ISLNK (sb.st_mode)) 116 { 117 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 118 "Invalid key directory entry `%s': symbolic links are not allowed\n", 119 filename); 120 return GNUNET_SYSERR; 121 } 122 if (0 != strncasecmp (base, 123 ctx->cprefix, 124 strlen (ctx->cprefix))) 125 return GNUNET_OK; 126 if (! S_ISDIR (sb.st_mode)) 127 return GNUNET_OK; 128 normalized = TES_normalize_section (base); 129 if (0 == strcmp (base, normalized)) 130 { 131 GNUNET_free (normalized); 132 return GNUNET_OK; 133 } 134 GNUNET_asprintf (&dir.new_name, 135 "%.*s%s", 136 (int) (base - filename), 137 filename, 138 normalized); 139 GNUNET_free (normalized); 140 for (unsigned int i = 0; i < ctx->num_dirs; i++) 141 { 142 if (0 != strcmp (dir.new_name, ctx->dirs[i].new_name)) 143 continue; 144 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 145 "Conflicting denomination key directories `%s' and `%s'; resolve their capitalization before restarting\n", 146 filename, 147 ctx->dirs[i].old_name); 148 GNUNET_free (dir.new_name); 149 return GNUNET_SYSERR; 150 } 151 if (0 == lstat (dir.new_name, &target)) 152 { 153 /* On a case-insensitive filesystem both spellings may already refer to 154 the same directory. Still rename it to update the stored spelling. */ 155 if ( (sb.st_dev != target.st_dev) || 156 (sb.st_ino != target.st_ino) ) 157 { 158 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 159 "Cannot rename denomination key directory `%s' to `%s': destination exists; resolve the conflict before restarting\n", 160 filename, 161 dir.new_name); 162 GNUNET_free (dir.new_name); 163 return GNUNET_SYSERR; 164 } 165 } 166 else if (ENOENT != errno) 167 { 168 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 169 "lstat", 170 dir.new_name); 171 GNUNET_free (dir.new_name); 172 return GNUNET_SYSERR; 173 } 174 dir.old_name = GNUNET_strdup (filename); 175 GNUNET_array_append (ctx->dirs, ctx->num_dirs, dir); 176 return GNUNET_OK; 177 } 178 179 180 enum GNUNET_GenericReturnValue 181 TES_normalize_key_directory (const struct GNUNET_CONFIGURATION_Handle *cfg, 182 const char *keydir, 183 const char *cprefix) 184 { 185 struct KeyDirectoryContext ctx = { 186 .cprefix = cprefix 187 }; 188 enum GNUNET_GenericReturnValue ret = GNUNET_OK; 189 190 GNUNET_CONFIGURATION_iterate_sections (cfg, 191 &check_section_name, 192 &ctx); 193 if (ctx.invalid_section_name) 194 return GNUNET_SYSERR; 195 if (GNUNET_OK != GNUNET_DISK_directory_create (keydir)) 196 return GNUNET_SYSERR; 197 if (0 > GNUNET_DISK_directory_scan (keydir, 198 &collect_key_directory, 199 &ctx)) 200 ret = GNUNET_SYSERR; 201 for (unsigned int i = 0; i < ctx.num_dirs; i++) 202 { 203 struct KeyDirectory *dir = &ctx.dirs[i]; 204 205 if (GNUNET_OK == ret) 206 { 207 if (0 != rename (dir->old_name, dir->new_name)) 208 { 209 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 210 "rename", 211 dir->old_name); 212 ret = GNUNET_SYSERR; 213 } 214 else 215 { 216 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 217 "Renamed denomination key directory `%s' to `%s'\n", 218 dir->old_name, 219 dir->new_name); 220 } 221 } 222 GNUNET_free (dir->old_name); 223 GNUNET_free (dir->new_name); 224 } 225 GNUNET_free (ctx.dirs); 226 return ret; 227 } 228 229 230 /** 231 * Head of DLL of clients connected to us. 232 */ 233 struct TES_Client *TES_clients_head; 234 235 /** 236 * Tail of DLL of clients connected to us. 237 */ 238 struct TES_Client *TES_clients_tail; 239 240 /** 241 * Lock for the client queue. 242 */ 243 pthread_mutex_t TES_clients_lock = PTHREAD_MUTEX_INITIALIZER; 244 245 /** 246 * Private key of this security module. Used to sign denomination key 247 * announcements. 248 */ 249 struct TALER_SecurityModulePrivateKeyP TES_smpriv; 250 251 /** 252 * Public key of this security module. 253 */ 254 struct TALER_SecurityModulePublicKeyP TES_smpub; 255 256 /** 257 * Our listen socket. 258 */ 259 static struct GNUNET_NETWORK_Handle *unix_sock; 260 261 /** 262 * Path where we are listening. 263 */ 264 static char *unixpath; 265 266 /** 267 * Task run to accept new inbound connections. 268 */ 269 static struct GNUNET_SCHEDULER_Task *listen_task; 270 271 /** 272 * Set once we are in shutdown and workers should terminate. 273 */ 274 static volatile bool in_shutdown; 275 276 277 enum GNUNET_GenericReturnValue 278 TES_transmit_raw (int sock, 279 size_t end, 280 const void *pos) 281 { 282 size_t off = 0; 283 284 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 285 "Sending message of length %u\n", 286 (unsigned int) end); 287 while (off < end) 288 { 289 ssize_t ret = send (sock, 290 pos, 291 end - off, 292 0 /* no flags => blocking! */); 293 294 if ( (-1 == ret) && 295 ( (EAGAIN == errno) || 296 (EINTR == errno) ) ) 297 { 298 GNUNET_log_strerror (GNUNET_ERROR_TYPE_DEBUG, 299 "send"); 300 continue; 301 } 302 if (-1 == ret) 303 { 304 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 305 "send"); 306 return GNUNET_SYSERR; 307 } 308 if (0 == ret) 309 { 310 GNUNET_break (0); 311 return GNUNET_SYSERR; 312 } 313 off += ret; 314 pos += ret; 315 } 316 return GNUNET_OK; 317 } 318 319 320 enum GNUNET_GenericReturnValue 321 TES_transmit (int sock, 322 const struct GNUNET_MessageHeader *hdr) 323 { 324 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 325 "Sending message of type %u and length %u\n", 326 (unsigned int) ntohs (hdr->type), 327 (unsigned int) ntohs (hdr->size)); 328 return TES_transmit_raw (sock, 329 ntohs (hdr->size), 330 hdr); 331 } 332 333 334 struct GNUNET_NETWORK_Handle * 335 TES_open_socket (const char *my_unixpath) 336 { 337 int sock; 338 mode_t old_umask; 339 struct GNUNET_NETWORK_Handle *ret = NULL; 340 341 /* Change permissions so that group read/writes are allowed. 342 * We need this for multi-user exchange deployment with privilege 343 * separation, where taler-exchange-httpd is part of a group 344 * that allows it to talk to secmod. 345 */ 346 old_umask = umask (S_IROTH | S_IWOTH | S_IXOTH); 347 348 sock = socket (PF_UNIX, 349 SOCK_STREAM, 350 0); 351 if (-1 == sock) 352 { 353 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 354 "socket"); 355 goto cleanup; 356 } 357 { 358 struct sockaddr_un un; 359 360 if (GNUNET_OK != 361 GNUNET_DISK_directory_create_for_file (my_unixpath)) 362 { 363 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 364 "mkdir(dirname)", 365 my_unixpath); 366 } 367 if (0 != unlink (my_unixpath)) 368 { 369 if (ENOENT != errno) 370 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 371 "unlink", 372 my_unixpath); 373 } 374 memset (&un, 375 0, 376 sizeof (un)); 377 un.sun_family = AF_UNIX; 378 strncpy (un.sun_path, 379 my_unixpath, 380 sizeof (un.sun_path) - 1); 381 if (0 != bind (sock, 382 (const struct sockaddr *) &un, 383 sizeof (un))) 384 { 385 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 386 "bind", 387 my_unixpath); 388 GNUNET_break (0 == close (sock)); 389 goto cleanup; 390 } 391 ret = GNUNET_NETWORK_socket_box_native (sock); 392 if (GNUNET_OK != 393 GNUNET_NETWORK_socket_listen (ret, 394 512)) 395 { 396 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 397 "listen", 398 my_unixpath); 399 GNUNET_break (GNUNET_OK == 400 GNUNET_NETWORK_socket_close (ret)); 401 ret = NULL; 402 } 403 } 404 cleanup: 405 (void) umask (old_umask); 406 return ret; 407 } 408 409 410 void 411 TES_wake_clients (void) 412 { 413 uint64_t num = 1; 414 415 GNUNET_assert (0 == pthread_mutex_lock (&TES_clients_lock)); 416 for (struct TES_Client *client = TES_clients_head; 417 NULL != client; 418 client = client->next) 419 { 420 #ifdef __linux__ 421 if (-1 == client->esock) 422 continue; 423 GNUNET_assert (sizeof (num) == 424 write (client->esock, 425 &num, 426 sizeof (num))); 427 #else 428 if (-1 == client->esock_in) 429 continue; 430 GNUNET_assert (sizeof (num) == 431 write (client->esock_in, 432 &num, 433 sizeof (num))); 434 #endif 435 } 436 GNUNET_assert (0 == pthread_mutex_unlock (&TES_clients_lock)); 437 } 438 439 440 enum GNUNET_GenericReturnValue 441 TES_read_work (void *cls, 442 TES_MessageDispatch dispatch) 443 { 444 struct TES_Client *client = cls; 445 char *buf = client->iobuf; 446 size_t off = 0; 447 uint16_t msize = 0; 448 const struct GNUNET_MessageHeader *hdr = NULL; 449 enum GNUNET_GenericReturnValue ret; 450 451 do 452 { 453 ssize_t recv_size; 454 455 recv_size = recv (client->csock, 456 &buf[off], 457 sizeof (client->iobuf) - off, 458 0); 459 if (-1 == recv_size) 460 { 461 if ( (0 == off) && 462 (EAGAIN == errno) ) 463 return GNUNET_NO; 464 if ( (EINTR == errno) || 465 (EAGAIN == errno) ) 466 { 467 GNUNET_log_strerror (GNUNET_ERROR_TYPE_DEBUG, 468 "recv"); 469 continue; 470 } 471 if (ECONNRESET != errno) 472 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 473 "recv"); 474 return GNUNET_SYSERR; 475 } 476 if (0 == recv_size) 477 { 478 /* regular disconnect? */ 479 GNUNET_break_op (0 == off); 480 return GNUNET_SYSERR; 481 } 482 off += recv_size; 483 more: 484 msize = sizeof (*hdr); /* at least */ 485 if (off < msize) 486 continue; 487 hdr = (const struct GNUNET_MessageHeader *) buf; 488 msize = ntohs (hdr->size); 489 #if 0 490 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 491 "Received message of type %u with %u bytes\n", 492 (unsigned int) ntohs (hdr->type), 493 (unsigned int) msize); 494 #endif 495 if (msize < sizeof (*hdr)) 496 { 497 GNUNET_break_op (0); 498 return GNUNET_SYSERR; 499 } 500 } while (off < msize); 501 502 ret = dispatch (client, 503 hdr); 504 if ( (GNUNET_OK != ret) || 505 (off == msize) ) 506 return ret; 507 memmove (buf, 508 &buf[msize], 509 off - msize); 510 off -= msize; 511 goto more; 512 } 513 514 515 bool 516 TES_await_ready (struct TES_Client *client) 517 { 518 /* wait for reply with 1s timeout */ 519 struct pollfd pfds[] = { 520 { 521 .fd = client->csock, 522 .events = POLLIN 523 }, 524 { 525 #ifdef __linux__ 526 .fd = client->esock, 527 #else 528 .fd = client->esock_out, 529 #endif 530 .events = POLLIN 531 }, 532 }; 533 int ret; 534 535 ret = poll (pfds, 536 2, 537 -1); 538 if ( (-1 == ret) && 539 (EINTR != errno) ) 540 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 541 "poll"); 542 for (int i = 0; i<2; i++) 543 { 544 if ( 545 #ifdef __linux__ 546 (pfds[i].fd == client->esock) && 547 #else 548 (pfds[i].fd == client->esock_out) && 549 #endif 550 (POLLIN == pfds[i].revents) ) 551 { 552 uint64_t num; 553 554 #ifdef __linux__ 555 GNUNET_assert (sizeof (num) == 556 read (client->esock, 557 &num, 558 sizeof (num))); 559 #else 560 GNUNET_assert (sizeof (num) == 561 read (client->esock_out, 562 &num, 563 sizeof (num))); 564 #endif 565 return true; 566 } 567 } 568 return false; 569 } 570 571 572 /** 573 * Main function of a worker thread that signs. 574 * 575 * @param cls the client we are working on 576 * @return NULL 577 */ 578 static void * 579 sign_worker (void *cls) 580 { 581 struct TES_Client *client = cls; 582 583 if (GNUNET_OK != 584 client->cb.init (client)) 585 { 586 GNUNET_break (0); 587 return NULL; 588 } 589 while (! in_shutdown) 590 { 591 if (TES_await_ready (client)) 592 { 593 if (GNUNET_OK != 594 client->cb.updater (client)) 595 break; 596 } 597 else 598 { 599 if (GNUNET_SYSERR == 600 TES_read_work (client, 601 client->cb.dispatch)) 602 break; 603 } 604 } 605 GNUNET_break (0 == close (client->csock)); 606 client->csock = -1; 607 return NULL; 608 } 609 610 611 /** 612 * Clean up @a pos, joining the thread and closing the 613 * file descriptors. 614 * 615 * @param[in] pos client to clean up 616 */ 617 static void 618 join_client (struct TES_Client *pos) 619 { 620 void *rval; 621 622 GNUNET_CONTAINER_DLL_remove (TES_clients_head, 623 TES_clients_tail, 624 pos); 625 GNUNET_break (0 == 626 pthread_join (pos->worker, 627 &rval)); 628 #ifdef __linux__ 629 GNUNET_break (0 == close (pos->esock)); 630 pos->esock = -1; 631 #else 632 GNUNET_break (0 == close (pos->esock_in)); 633 pos->esock_in = -1; 634 GNUNET_break (0 == close (pos->esock_out)); 635 pos->esock_out = -1; 636 #endif 637 GNUNET_free (pos); 638 } 639 640 641 /** 642 * Task that listens for incoming clients. 643 * 644 * @param cls a `struct TES_Callbacks` 645 */ 646 static void 647 listen_job (void *cls) 648 { 649 const struct TES_Callbacks *cb = cls; 650 int s; 651 #ifdef __linux__ 652 int e; 653 #else 654 int e[2]; 655 #endif 656 struct sockaddr_storage sa; 657 socklen_t sa_len = sizeof (sa); 658 659 listen_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, 660 unix_sock, 661 &listen_job, 662 cls); 663 s = accept (GNUNET_NETWORK_get_fd (unix_sock), 664 (struct sockaddr *) &sa, 665 &sa_len); 666 if (-1 == s) 667 { 668 bool st = ( (ENFILE == errno) || 669 (EMFILE == errno) ); 670 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 671 "accept"); 672 if (st) 673 { 674 GNUNET_SCHEDULER_cancel (listen_task); 675 listen_task = NULL; 676 } 677 return; 678 } 679 #ifdef __linux__ 680 e = eventfd (0, 681 EFD_CLOEXEC); 682 if (-1 == e) 683 { 684 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 685 "eventfd"); 686 GNUNET_break (0 == close (s)); 687 return; 688 } 689 #else 690 if (0 != pipe (e)) 691 { 692 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 693 "pipe"); 694 GNUNET_break (0 == close (s)); 695 return; 696 } 697 #endif 698 { 699 struct TES_Client *client; 700 struct TES_Client *nxt; 701 702 client = GNUNET_new (struct TES_Client); 703 client->cb = *cb; 704 client->csock = s; 705 #ifdef __linux__ 706 client->esock = e; 707 #else 708 client->esock_in = e[1]; 709 client->esock_out = e[0]; 710 #endif 711 GNUNET_assert (0 == pthread_mutex_lock (&TES_clients_lock)); 712 for (struct TES_Client *pos = TES_clients_head; 713 NULL != pos; 714 pos = nxt) 715 { 716 nxt = pos->next; 717 if (-1 == pos->csock) 718 { 719 join_client (pos); 720 } 721 } 722 GNUNET_CONTAINER_DLL_insert (TES_clients_head, 723 TES_clients_tail, 724 client); 725 GNUNET_assert (0 == pthread_mutex_unlock (&TES_clients_lock)); 726 if (0 != 727 pthread_create (&client->worker, 728 NULL, 729 &sign_worker, 730 client)) 731 { 732 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 733 "pthread_create"); 734 GNUNET_assert (0 == pthread_mutex_lock (&TES_clients_lock)); 735 GNUNET_CONTAINER_DLL_remove (TES_clients_head, 736 TES_clients_tail, 737 client); 738 GNUNET_assert (0 == pthread_mutex_unlock (&TES_clients_lock)); 739 GNUNET_break (0 == close (client->csock)); 740 #ifdef __linux__ 741 GNUNET_break (0 == close (client->esock)); 742 #else 743 GNUNET_break (0 == close (client->esock_in)); 744 GNUNET_break (0 == close (client->esock_out)); 745 #endif 746 GNUNET_free (client); 747 } 748 } 749 } 750 751 752 int 753 TES_listen_start (const struct GNUNET_CONFIGURATION_Handle *cfg, 754 const char *section, 755 const struct TES_Callbacks *cb) 756 { 757 { 758 char *pfn; 759 760 if (GNUNET_OK != 761 GNUNET_CONFIGURATION_get_value_filename (cfg, 762 section, 763 "SM_PRIV_KEY", 764 &pfn)) 765 { 766 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 767 section, 768 "SM_PRIV_KEY"); 769 return EXIT_NOTCONFIGURED; 770 } 771 if (GNUNET_SYSERR == 772 GNUNET_CRYPTO_eddsa_key_from_file (pfn, 773 GNUNET_YES, 774 &TES_smpriv.eddsa_priv)) 775 { 776 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 777 section, 778 "SM_PRIV_KEY", 779 "Could not use file to persist private key"); 780 GNUNET_free (pfn); 781 return EXIT_NOPERMISSION; 782 } 783 GNUNET_free (pfn); 784 GNUNET_CRYPTO_eddsa_key_get_public (&TES_smpriv.eddsa_priv, 785 &TES_smpub.eddsa_pub); 786 } 787 788 if (GNUNET_OK != 789 GNUNET_CONFIGURATION_get_value_filename (cfg, 790 section, 791 "UNIXPATH", 792 &unixpath)) 793 { 794 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 795 section, 796 "UNIXPATH"); 797 return EXIT_NOTCONFIGURED; 798 } 799 GNUNET_assert (NULL != unixpath); 800 unix_sock = TES_open_socket (unixpath); 801 if (NULL == unix_sock) 802 { 803 GNUNET_free (unixpath); 804 GNUNET_break (0); 805 return EXIT_NOPERMISSION; 806 } 807 /* start job to accept incoming requests on 'sock' */ 808 listen_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, 809 unix_sock, 810 &listen_job, 811 (void *) cb); 812 return 0; 813 } 814 815 816 void 817 TES_listen_stop (void) 818 { 819 struct TES_Client *client; 820 821 if (NULL != listen_task) 822 { 823 GNUNET_SCHEDULER_cancel (listen_task); 824 listen_task = NULL; 825 } 826 if (NULL != unix_sock) 827 { 828 GNUNET_break (GNUNET_OK == 829 GNUNET_NETWORK_socket_close (unix_sock)); 830 unix_sock = NULL; 831 } 832 if (0 != unlink (unixpath)) 833 { 834 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 835 "unlink", 836 unixpath); 837 } 838 GNUNET_free (unixpath); 839 in_shutdown = true; 840 TES_wake_clients (); 841 GNUNET_assert (0 == pthread_mutex_lock (&TES_clients_lock)); 842 while (NULL != (client = TES_clients_head)) 843 join_client (client); 844 GNUNET_assert (0 == pthread_mutex_unlock (&TES_clients_lock)); 845 }