unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
modulemanager.c (46278B)
1 /* UnrealIRCd module manager. 2 * (C) Copyright 2019 Bram Matthys ("Syzop") and the UnrealIRCd Team. 3 * License: GPLv2 or later 4 * See https://www.unrealircd.org/docs/Module_manager for user documentation. 5 */ 6 7 #include "unrealircd.h" 8 #ifndef _WIN32 9 10 #define MODULEMANAGER_CONNECT_TIMEOUT 7 11 #define MODULEMANAGER_READ_TIMEOUT 20 12 13 typedef struct ManagedModule ManagedModule; 14 15 struct ManagedModule 16 { 17 ManagedModule *prev, *next; 18 char *repo_url; 19 char *name; 20 char *author; 21 char *troubleshooting; 22 char *documentation; 23 char *version; 24 char *source; 25 char *sha256sum; 26 char *min_unrealircd_version; 27 char *max_unrealircd_version; 28 char *description; 29 MultiLine *post_install_text; 30 }; 31 32 static ManagedModule *managed_modules = NULL; 33 34 /* We normally do a 'make install' after upgrading a module. 35 * However we will skip it if --no-install is done. 36 * This is only done during 'make', as it is unexpected to 37 * already install modules at the final location before 38 * 'make install' was issued. 39 */ 40 static int no_make_install = 0; 41 42 /* Forward declarations */ 43 int mm_valid_module_name(char *name); 44 void free_managed_module(ManagedModule *m); 45 46 47 SSL_CTX *mm_init_tls(void) 48 { 49 SSL_CTX *ctx_client; 50 char buf1[512], buf2[512]; 51 char *curl_ca_bundle = buf1; 52 53 SSL_load_error_strings(); 54 SSLeay_add_ssl_algorithms(); 55 56 ctx_client = SSL_CTX_new(SSLv23_client_method()); 57 if (!ctx_client) 58 return NULL; 59 #ifdef HAS_SSL_CTX_SET_MIN_PROTO_VERSION 60 SSL_CTX_set_min_proto_version(ctx_client, TLS1_2_VERSION); 61 #endif 62 SSL_CTX_set_options(ctx_client, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1); 63 64 /* Verify peer certificate */ 65 snprintf(buf1, sizeof(buf1), "%s/tls/curl-ca-bundle.crt", CONFDIR); 66 if (!file_exists(buf1)) 67 { 68 snprintf(buf2, sizeof(buf2), "%s/doc/conf/tls/curl-ca-bundle.crt", BUILDDIR); 69 if (!file_exists(buf2)) 70 { 71 fprintf(stderr, "ERROR: Neither %s nor %s exist.\n" 72 "Cannot use module manager without curl-ca-bundle.crt\n", 73 buf1, buf2); 74 exit(-1); 75 } 76 curl_ca_bundle = buf2; 77 } 78 SSL_CTX_load_verify_locations(ctx_client, curl_ca_bundle, NULL); 79 SSL_CTX_set_verify(ctx_client, SSL_VERIFY_PEER, NULL); 80 81 /* Limit ciphers as well */ 82 SSL_CTX_set_cipher_list(ctx_client, UNREALIRCD_DEFAULT_CIPHERS); 83 84 return ctx_client; 85 } 86 87 int parse_url(const char *url, char **host, int *port, char **document) 88 { 89 char *p; 90 static char hostbuf[256]; 91 static char documentbuf[512]; 92 93 if (strncmp(url, "https://", 8)) 94 { 95 fprintf(stderr, "ERROR: URL Must start with https! URL: %s\n", url); 96 return 0; 97 } 98 url += 8; /* skip over https:// part */ 99 100 p = strchr(url, '/'); 101 if (!p) 102 return 0; 103 104 strlncpy(hostbuf, url, sizeof(hostbuf), p - url); 105 106 strlcpy(documentbuf, p, sizeof(documentbuf)); 107 108 *host = hostbuf; 109 *document = documentbuf; 110 // TODO: parse port, rather than hardcode: 111 *port = 443; 112 return 1; 113 } 114 115 int mm_http_request(char *url, char *fname, int follow_redirects) 116 { 117 char *host = NULL; 118 int port = 0; 119 char *document = NULL; 120 char hostandport[256]; 121 char buf[1024]; 122 int n; 123 FILE *fd; 124 SSL_CTX *ctx_client; 125 SSL *ssl = NULL; 126 BIO *socket = NULL; 127 char *errstr = NULL; 128 int got_data = 0, first_line = 1; 129 int http_redirect = 0; 130 131 if (!parse_url(url, &host, &port, &document)) 132 return 0; 133 134 snprintf(hostandport, sizeof(hostandport), "%s:%d", host, port); 135 136 ctx_client = mm_init_tls(); 137 if (!ctx_client) 138 { 139 fprintf(stderr, "ERROR: TLS initalization failure (I)\n"); 140 return 0; 141 } 142 143 alarm(MODULEMANAGER_CONNECT_TIMEOUT); 144 145 socket = BIO_new_ssl_connect(ctx_client); 146 if (!socket) 147 { 148 fprintf(stderr, "ERROR: TLS initalization failure (II)\n"); 149 goto out1; 150 } 151 152 BIO_get_ssl(socket, &ssl); 153 if (!ssl) 154 { 155 fprintf(stderr, "ERROR: Could not get TLS connection from BIO -- strange\n"); 156 goto out2; 157 } 158 159 BIO_set_conn_hostname(socket, hostandport); 160 SSL_set_tlsext_host_name(ssl, host); 161 162 if (BIO_do_connect(socket) != 1) 163 { 164 fprintf(stderr, "ERROR: Could not connect to %s\n", hostandport); 165 //config_report_ssl_error(); FIXME? 166 goto out2; 167 } 168 169 if (BIO_do_handshake(socket) != 1) 170 { 171 fprintf(stderr, "ERROR: Could not connect to %s (TLS handshake failed)\n", hostandport); 172 //config_report_ssl_error(); FIXME? 173 goto out2; 174 } 175 176 if (!verify_certificate(ssl, host, &errstr)) 177 { 178 fprintf(stderr, "Certificate problem for %s: %s\n", host, errstr); 179 goto out2; 180 } 181 182 snprintf(buf, sizeof(buf), "GET %s HTTP/1.1\r\n" 183 "User-Agent: UnrealIRCd %s\r\n" 184 "Host: %s\r\n" 185 "Connection: close\r\n" 186 "\r\n", 187 document, 188 VERSIONONLY, 189 hostandport); 190 191 BIO_puts(socket, buf); 192 193 fd = fopen(fname, "wb"); 194 if (!fd) 195 { 196 fprintf(stderr, "Could not write to temporary file '%s': %s\n", 197 fname, strerror(errno)); 198 goto out2; 199 } 200 201 alarm(MODULEMANAGER_READ_TIMEOUT); 202 while ((n = BIO_read(socket, buf, sizeof(buf)-1)) > 0) 203 { 204 buf[n] = '\0'; 205 if (got_data) 206 { 207 fwrite(buf, n, 1, fd); // TODO: check for write errors 208 } else { 209 /* Still need to parse header */ 210 char *line, *p = NULL; 211 212 for (line = strtoken(&p, buf, "\n"); line; line = strtoken(&p, NULL, "\n")) 213 { 214 if (first_line) 215 { 216 if (http_redirect) 217 { 218 if (!strncmp(line, "Location: ", 10)) 219 { 220 line += 10; 221 stripcrlf(line); 222 fclose(fd); 223 BIO_free_all(socket); 224 SSL_CTX_free(ctx_client); 225 if (strncmp(line, "https://", 8)) 226 { 227 fprintf(stderr, "Invalid HTTP Redirect to '%s' -- must start with https://\n", line); 228 return 0; 229 } 230 printf("Following redirect to %s\n", line); 231 return mm_http_request(line, fname, 0); 232 } 233 continue; 234 } 235 if (!strncmp(line, "HTTP/1.1 301", 12) || 236 !strncmp(line, "HTTP/1.1 302", 12) || 237 !strncmp(line, "HTTP/1.1 303", 12) || 238 !strncmp(line, "HTTP/1.1 307", 12) || 239 !strncmp(line, "HTTP/1.1 308", 12)) 240 { 241 if (!follow_redirects) 242 { 243 fprintf(stderr, "ERROR: received HTTP(S) redirect while already following another HTTP(S) redirect.\n"); 244 goto out3; 245 } 246 http_redirect = 1; 247 continue; 248 } 249 if (strncmp(line, "HTTP/1.1 200", 12)) 250 { 251 stripcrlf(line); 252 if (strlen(line) > 128) 253 line[128] = '\0'; 254 fprintf(stderr, "Error while fetching %s: %s\n", url, line); 255 goto out3; 256 } 257 first_line = 0; 258 } 259 if (!*line || !strcmp(line, "\r")) 260 { 261 int remaining_bytes; 262 263 got_data = 1; 264 /* Bit of a hack here to write part of the data 265 * that is not part of the header but the data response.. 266 * We need to jump over the NUL byte and then check 267 * to see if we can access it.. since it could be either 268 * a NUL byte due to the strtoken() or a NUL byte simply 269 * because that was all the data from BIO_read() at this point. 270 */ 271 if (*line == '\0') 272 line += 1; /* jump over \0 */ 273 else 274 line += 2; /* jump over \r\0 */ 275 remaining_bytes = n - (line - buf); 276 if (remaining_bytes > 0) 277 fwrite(line, remaining_bytes, 1, fd); 278 break; /* must break the for loop here */ 279 } 280 } 281 } 282 } 283 284 if (!got_data) 285 { 286 fprintf(stderr, "Error while fetching %s: unable to retrieve data\n", url); 287 goto out3; 288 } 289 290 fclose(fd); 291 BIO_free_all(socket); 292 SSL_CTX_free(ctx_client); 293 return 1; 294 out3: 295 fclose(fd); 296 out2: 297 BIO_free_all(socket); 298 out1: 299 SSL_CTX_free(ctx_client); 300 alarm(0); 301 return 0; 302 } 303 304 typedef enum ParseModuleHeaderStage { 305 PMH_STAGE_LOOKING = 0, 306 PMH_STAGE_MODULEHEADER = 1, 307 PMH_STAGE_MOD_HEADER = 2, 308 PMH_STAGE_GOT_NAME = 3, 309 PMH_STAGE_GOT_VERSION = 4, 310 PMH_STAGE_GOT_DESCRIPTION = 5, 311 PMH_STAGE_GOT_AUTHOR = 6, 312 PMT_STAGE_DONE = 7, 313 } ParseModuleHeaderStage; 314 315 typedef enum ParseModuleConfigStage { 316 PMC_STAGE_LOOKING = 0, 317 PMC_STAGE_STARTED = 1, 318 PMC_STAGE_FINISHED = 2, 319 } ParseModuleConfigStage; 320 321 int parse_quoted_string(char *buf, char *dest, size_t destlen) 322 { 323 char *p, *p2; 324 size_t max; 325 char *i, *o; 326 327 p = strchr(buf, '"'); 328 if (!p) 329 return 0; 330 p2 = strrchr(p+1, '"'); 331 if (!p2) 332 return 0; 333 max = p2 - p; 334 if (max > destlen) 335 max = destlen; 336 strlcpy(dest, p+1, max); 337 unreal_del_quotes(dest); 338 return 1; 339 } 340 341 #undef CheckNull 342 #define CheckNull(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", m->name, (x)->line_number); return 0; } 343 344 /** Parse a module { } line from a module (not repo!!) */ 345 int mm_module_file_config(ManagedModule *m, ConfigEntry *ce) 346 { 347 ConfigEntry *cep; 348 349 if (ce->value) 350 { 351 config_error("%s:%d: module { } block should not have a name.", 352 m->name, ce->line_number); 353 return 0; 354 } 355 356 for (cep = ce->items; cep; cep = cep->next) 357 { 358 if (!strcmp(cep->name, "source") || 359 !strcmp(cep->name, "version") || 360 !strcmp(cep->name, "author") || 361 !strcmp(cep->name, "sha256sum") || 362 !strcmp(cep->name, "description") 363 ) 364 { 365 config_error("%s:%d: module::%s should not be in here (it only exists in repository entries)", 366 m->name, cep->line_number, cep->name); 367 return 0; 368 } 369 else if (!strcmp(cep->name, "troubleshooting")) 370 { 371 CheckNull(cep); 372 safe_strdup(m->troubleshooting, cep->value); 373 } 374 else if (!strcmp(cep->name, "documentation")) 375 { 376 CheckNull(cep); 377 safe_strdup(m->documentation, cep->value); 378 } 379 else if (!strcmp(cep->name, "min-unrealircd-version")) 380 { 381 CheckNull(cep); 382 safe_strdup(m->min_unrealircd_version, cep->value); 383 } 384 else if (!strcmp(cep->name, "max-unrealircd-version")) 385 { 386 CheckNull(cep); 387 safe_strdup(m->max_unrealircd_version, cep->value); 388 } 389 else if (!strcmp(cep->name, "post-install-text")) 390 { 391 if (cep->items) 392 { 393 ConfigEntry *cepp; 394 for (cepp = cep->items; cepp; cepp = cepp->next) 395 addmultiline(&m->post_install_text, cepp->name); 396 } else { 397 CheckNull(cep); 398 addmultiline(&m->post_install_text, cep->value); 399 } 400 } 401 /* unknown items are silently ignored for future compatibility */ 402 } 403 404 if (!m->documentation) 405 { 406 config_error("%s:%d: module::documentation missing", m->name, ce->line_number); 407 return 0; 408 } 409 410 if (!m->troubleshooting) 411 { 412 config_error("%s:%d: module::troubleshooting missing", m->name, ce->line_number); 413 return 0; 414 } 415 416 if (!m->min_unrealircd_version) 417 { 418 config_error("%s:%d: module::min-unrealircd-version missing", m->name, ce->line_number); 419 return 0; 420 } 421 422 /* max_unrealircd_version is optional */ 423 424 /* post_install_text is optional */ 425 426 return 1; 427 } 428 429 #undef CheckNull 430 431 int mm_parse_module_file(ManagedModule *m, char *buf, unsigned int line_offset) 432 { 433 ConfigFile *cf; 434 ConfigEntry *ce; 435 436 cf = config_parse_with_offset(m->name, buf, line_offset); 437 if (!cf) 438 return 0; /* eg: parse errors */ 439 440 /* Parse the module { } block (only one!) */ 441 for (ce = cf->items; ce; ce = ce->next) 442 { 443 if (!strcmp(ce->name, "module")) 444 { 445 int n = mm_module_file_config(m, ce); 446 config_free(cf); 447 return n; 448 } 449 } 450 451 config_free(cf); 452 config_error("No module block found within module source file. Contact author.\n"); 453 return 1; 454 } 455 456 #define MODULECONFIGBUFFER 16384 457 ManagedModule *mm_parse_module_c_file(char *modulename, char *fname) 458 { 459 char buf[1024]; 460 FILE *fd; 461 ParseModuleHeaderStage parse_module_header = PMH_STAGE_LOOKING; 462 ParseModuleConfigStage parse_module_config = PMC_STAGE_LOOKING; 463 char *moduleconfig = NULL; 464 int linenr = 0, module_config_start_line = 0; 465 char module_header_name[128]; 466 char module_header_version[64]; 467 char module_header_description[256]; 468 char module_header_author[128]; 469 ManagedModule *m = NULL; 470 471 *module_header_name = *module_header_version = *module_header_description = *module_header_author = '\0'; 472 473 if (!mm_valid_module_name(modulename)) 474 { 475 fprintf(stderr, "Module file '%s' contains forbidden characters\n", modulename); 476 return NULL; 477 } 478 479 fd = fopen(fname, "r"); 480 if (!fd) 481 { 482 fprintf(stderr, "Unable to open module '%s', file '%s': %s\n", 483 modulename, fname, strerror(errno)); 484 return NULL; 485 } 486 487 moduleconfig = safe_alloc(MODULECONFIGBUFFER); /* should be sufficient */ 488 while ((fgets(buf, sizeof(buf), fd))) 489 { 490 linenr++; 491 stripcrlf(buf); 492 /* parse module header stuff: */ 493 switch (parse_module_header) 494 { 495 case PMH_STAGE_LOOKING: 496 if (strstr(buf, "ModuleHeader")) 497 parse_module_header = PMH_STAGE_MODULEHEADER; 498 else 499 break; 500 /*fallthrough*/ 501 case PMH_STAGE_MODULEHEADER: 502 if (strstr(buf, "MOD_HEADER")) 503 parse_module_header = PMH_STAGE_MOD_HEADER; 504 break; 505 case PMH_STAGE_MOD_HEADER: 506 if (parse_quoted_string(buf, module_header_name, sizeof(module_header_name))) 507 parse_module_header = PMH_STAGE_GOT_NAME; 508 break; 509 case PMH_STAGE_GOT_NAME: 510 if (parse_quoted_string(buf, module_header_version, sizeof(module_header_version))) 511 parse_module_header = PMH_STAGE_GOT_VERSION; 512 break; 513 case PMH_STAGE_GOT_VERSION: 514 if (parse_quoted_string(buf, module_header_description, sizeof(module_header_description))) 515 parse_module_header = PMH_STAGE_GOT_DESCRIPTION; 516 break; 517 case PMH_STAGE_GOT_DESCRIPTION: 518 if (parse_quoted_string(buf, module_header_author, sizeof(module_header_author))) 519 parse_module_header = PMH_STAGE_GOT_AUTHOR; 520 break; 521 default: 522 break; 523 } 524 /* parse module config stuff: */ 525 switch (parse_module_config) 526 { 527 case PMC_STAGE_LOOKING: 528 if (strstr(buf, "<<<MODULE MANAGER START>>>")){ 529 module_config_start_line = linenr; 530 parse_module_config = PMC_STAGE_STARTED; 531 } 532 break; 533 case PMC_STAGE_STARTED: 534 if (!strstr(buf, "<<<MODULE MANAGER END>>>")) 535 { 536 strlcat(moduleconfig, buf, MODULECONFIGBUFFER); 537 strlcat(moduleconfig, "\n", MODULECONFIGBUFFER); 538 } else 539 { 540 parse_module_config = PMC_STAGE_FINISHED; 541 } 542 break; 543 default: 544 /* Nothing to be done anymore */ 545 break; 546 } 547 } 548 fclose(fd); 549 550 if (!*module_header_name || !*module_header_version || 551 !*module_header_description || !*module_header_author) 552 { 553 fprintf(stderr, "Error parsing module header in %s\n", modulename); 554 safe_free(moduleconfig); 555 return NULL; 556 } 557 558 if (strcmp(module_header_name, modulename)) 559 { 560 fprintf(stderr, "ERROR: Mismatch in module name in header (%s) and filename (%s)\n", 561 module_header_name, modulename); 562 safe_free(moduleconfig); 563 return NULL; 564 } 565 566 if (!*moduleconfig) 567 { 568 fprintf(stderr, "ERROR: Module does not contain module config data (<<<MODULE MANAGER START>>>)\n" 569 "This means it is not meant to be managed by the module manager\n"); 570 safe_free(moduleconfig); 571 return NULL; 572 } 573 574 /* Fill in the fields from MOD_HEADER() */ 575 m = safe_alloc(sizeof(ManagedModule)); 576 safe_strdup(m->name, module_header_name); 577 safe_strdup(m->version, module_header_version); 578 safe_strdup(m->description, module_header_description); 579 safe_strdup(m->author, module_header_author); 580 581 if (!mm_parse_module_file(m, moduleconfig, module_config_start_line)) 582 { 583 fprintf(stderr, "ERROR: Problem with module manager data block within the %s module C source file.\n" 584 "You are suggested to contact the module author and paste the above to him/her\n", 585 m->name); 586 free_managed_module(m); 587 safe_free(moduleconfig); 588 return NULL; 589 } 590 591 safe_free(moduleconfig); 592 return m; 593 } 594 595 void print_documentation(void) 596 { 597 fprintf(stderr, "See https://www.unrealircd.org/docs/Module_manager for more information.\n"); 598 } 599 600 char *mm_sourceslist_file(void) 601 { 602 static char buf1[512], buf2[512]; 603 snprintf(buf1, sizeof(buf1), "%s/modules.sources.list", CONFDIR); 604 if (!file_exists(buf1)) 605 { 606 /* Possibly UnrealIRCd is not installed yet, so use this one */ 607 snprintf(buf2, sizeof(buf2), "%s/doc/conf/modules.sources.list", BUILDDIR); 608 if (!file_exists(buf2)) 609 { 610 fprintf(stderr, "ERROR: Neither '%s' nor '%s' exist.\n" 611 "No module repositories configured.\n", 612 buf1, buf2); 613 print_documentation(); 614 exit(-1); 615 } 616 return buf2; 617 } 618 return buf1; 619 } 620 621 /** Free a managed module struct */ 622 void free_managed_module(ManagedModule *m) 623 { 624 safe_free(m->repo_url); 625 safe_free(m->name); 626 safe_free(m->source); 627 safe_free(m->sha256sum); 628 safe_free(m->version); 629 safe_free(m->author); 630 safe_free(m->troubleshooting); 631 safe_free(m->documentation); 632 safe_free(m->min_unrealircd_version); 633 safe_free(m->max_unrealircd_version); 634 safe_free(m->description); 635 freemultiline(m->post_install_text); 636 safe_free(m); 637 } 638 639 /** Check for valid module name */ 640 int mm_valid_module_name(char *name) 641 { 642 char *p; 643 644 if (strncmp(name, "third/", 6)) 645 return 0; 646 name += 6; 647 if (strstr(name, "..")) 648 return 0; 649 for (p = name; *p; p++) 650 if (!isalnum(*p) && !strchr("._-", *p)) 651 return 0; 652 return 1; 653 } 654 655 #undef CheckNull 656 #define CheckNull(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", repo_url, (x)->line_number); goto fail_mm_repo_module_config; } 657 658 /** Parse a module { } line from a repository */ 659 ManagedModule *mm_repo_module_config(char *repo_url, ConfigEntry *ce) 660 { 661 ConfigEntry *cep; 662 ManagedModule *m = safe_alloc(sizeof(ManagedModule)); 663 664 if (!ce->value) 665 { 666 config_error("%s:%d: module { } with no name", 667 repo_url, ce->line_number); 668 goto fail_mm_repo_module_config; 669 } 670 if (strncmp(ce->value, "third/", 6)) 671 { 672 config_error("%s:%d: module { } name must start with: third/", 673 repo_url, ce->line_number); 674 goto fail_mm_repo_module_config; 675 } 676 if (!mm_valid_module_name(ce->value)) 677 { 678 config_error("%s:%d: module { } with illegal name: %s", 679 repo_url, ce->line_number, ce->value); 680 goto fail_mm_repo_module_config; 681 } 682 safe_strdup(m->name, ce->value); 683 safe_strdup(m->repo_url, repo_url); 684 685 for (cep = ce->items; cep; cep = cep->next) 686 { 687 if (!strcmp(cep->name, "source")) 688 { 689 CheckNull(cep); 690 safe_strdup(m->source, cep->value); 691 } 692 else if (!strcmp(cep->name, "sha256sum")) 693 { 694 CheckNull(cep); 695 safe_strdup(m->sha256sum, cep->value); 696 } 697 else if (!strcmp(cep->name, "version")) 698 { 699 CheckNull(cep); 700 safe_strdup(m->version, cep->value); 701 } 702 else if (!strcmp(cep->name, "author")) 703 { 704 CheckNull(cep); 705 safe_strdup(m->author, cep->value); 706 } 707 else if (!strcmp(cep->name, "troubleshooting")) 708 { 709 CheckNull(cep); 710 safe_strdup(m->troubleshooting, cep->value); 711 } 712 else if (!strcmp(cep->name, "documentation")) 713 { 714 CheckNull(cep); 715 safe_strdup(m->documentation, cep->value); 716 } 717 else if (!strcmp(cep->name, "min-unrealircd-version")) 718 { 719 CheckNull(cep); 720 safe_strdup(m->min_unrealircd_version, cep->value); 721 } 722 else if (!strcmp(cep->name, "max-unrealircd-version")) 723 { 724 CheckNull(cep); 725 safe_strdup(m->max_unrealircd_version, cep->value); 726 } 727 else if (!strcmp(cep->name, "description")) 728 { 729 CheckNull(cep); 730 safe_strdup(m->description, cep->value); 731 } 732 else if (!strcmp(cep->name, "post-install-text")) 733 { 734 if (cep->items) 735 { 736 ConfigEntry *cepp; 737 for (cepp = cep->items; cepp; cepp = cepp->next) 738 addmultiline(&m->post_install_text, cepp->name); 739 } else { 740 CheckNull(cep); 741 addmultiline(&m->post_install_text, cep->value); 742 } 743 } 744 /* unknown items are silently ignored for future compatibility */ 745 } 746 747 if (!m->source) 748 { 749 config_error("%s:%d: module::source missing", repo_url, ce->line_number); 750 goto fail_mm_repo_module_config; 751 } 752 if (!m->sha256sum) 753 { 754 config_error("%s:%d: module::sha256sum missing", repo_url, ce->line_number); 755 goto fail_mm_repo_module_config; 756 } 757 if (!m->version) 758 { 759 config_error("%s:%d: module::version missing", repo_url, ce->line_number); 760 goto fail_mm_repo_module_config; 761 } 762 if (!m->author) 763 { 764 config_error("%s:%d: module::author missing", repo_url, ce->line_number); 765 goto fail_mm_repo_module_config; 766 } 767 if (!m->documentation) 768 { 769 config_error("%s:%d: module::documentation missing", repo_url, ce->line_number); 770 goto fail_mm_repo_module_config; 771 } 772 if (!m->troubleshooting) 773 { 774 config_error("%s:%d: module::troubleshooting missing", repo_url, ce->line_number); 775 goto fail_mm_repo_module_config; 776 } 777 if (!m->min_unrealircd_version) 778 { 779 config_error("%s:%d: module::min-unrealircd-version missing", repo_url, ce->line_number); 780 goto fail_mm_repo_module_config; 781 } 782 /* max_unrealircd_version is optional */ 783 if (!m->description) 784 { 785 config_error("%s:%d: module::description missing", repo_url, ce->line_number); 786 goto fail_mm_repo_module_config; 787 } 788 /* post_install_text is optional */ 789 790 return m; 791 792 fail_mm_repo_module_config: 793 free_managed_module(m); 794 return NULL; 795 } 796 797 #undef CheckNull 798 799 int mm_parse_repo_db(char *url, char *filename) 800 { 801 ConfigFile *cf; 802 ConfigEntry *ce; 803 ManagedModule *m; 804 805 cf = config_load(filename, url); 806 if (!cf) 807 return 0; /* eg: parse errors */ 808 809 for (ce = cf->items; ce; ce = ce->next) 810 { 811 if (!strcmp(ce->name, "module")) 812 { 813 m = mm_repo_module_config(url, ce); 814 if (!m) 815 { 816 config_free(cf); 817 return 0; 818 } 819 AddListItem(m, managed_modules); 820 } 821 } 822 config_free(cf); 823 return 1; 824 } 825 826 int mm_refresh_repository(void) 827 { 828 char *sourceslist = mm_sourceslist_file(); 829 FILE *fd; 830 char buf[512]; 831 char *tmpfile; 832 int linenr = 0; 833 int success = 0; 834 int numrepos = 0; 835 836 if (!file_exists(TMPDIR)) 837 { 838 (void)mkdir(TMPDIR, S_IRUSR|S_IWUSR|S_IXUSR); /* Create the tmp dir, if it doesn't exist */ 839 if (!file_exists(TMPDIR)) 840 { 841 /* This is possible if the directory structure does not exist, 842 * eg if ~/unrealircd does not exist at all then ~/unrealircd/tmp 843 * cannot be mkdir'ed either. 844 */ 845 fprintf(stderr, "ERROR: %s does not exist (yet?), cannot use module manager\n", TMPDIR); 846 fprintf(stderr, " This can only happen if you did not use ./Config or if you rm -rf'ed after running ./Config.\n"); 847 exit(-1); 848 } 849 } 850 851 printf("Reading module repository list from '%s'...\n", mm_sourceslist_file()); 852 fd = fopen(sourceslist, "r"); 853 if (!fd) 854 { 855 fprintf(stderr, "ERROR: Could not open '%s': %s\n", sourceslist, strerror(errno)); 856 return 0; 857 } 858 859 while ((fgets(buf, sizeof(buf), fd))) 860 { 861 char *line = buf; 862 linenr++; 863 stripcrlf(line); 864 /* Skip whitespace */ 865 while (*line == ' ') 866 line++; 867 /* Skip empty lines and ones that start with a hash mark (#) */ 868 if (!*line || (*line == '#')) 869 continue; 870 if (strncmp(line, "https://", 8)) 871 { 872 fprintf(stderr, "ERROR in %s on line %d: URL should start with https://", 873 sourceslist, linenr); 874 fclose(fd); 875 return 0; 876 } 877 printf("Checking module repository %s...\n", line); 878 numrepos++; 879 tmpfile = unreal_mktemp(TMPDIR, "mm"); 880 if (mm_http_request(line, tmpfile, 1)) 881 { 882 if (!mm_parse_repo_db(line, tmpfile)) 883 { 884 fclose(fd); 885 return 0; 886 } 887 success++; 888 } 889 } 890 fclose(fd); 891 892 if (numrepos == 0) 893 { 894 fprintf(stderr, "ERROR: No repositories listed in module repository list. " 895 "Did you remove the default UnrealIRCd repository?\n" 896 "All commands, except for './unrealircd module uninstall third/name-of-module', are unavailable.\n"); 897 return 0; 898 } 899 900 return success ? 1 : 0; 901 } 902 903 #define COLUMN_STATUS 0 904 #define COLUMN_NAME 1 905 #define COLUMN_VERSION 2 906 907 void mm_list_print(char *status, char *name, char *version, char *description, int largest_column[3]) 908 { 909 int padstatus = MAX(largest_column[COLUMN_STATUS] - strlen(status), 0); 910 int padname = MAX(largest_column[COLUMN_NAME] - strlen(name), 0); 911 int padversion = MAX(largest_column[COLUMN_VERSION] - strlen(version), 0); 912 913 printf("| %s%*s | %s%*s | %s%*s | %s\n", 914 status, 915 padstatus, "", 916 name, 917 padname, "", 918 version, 919 padversion, "", 920 description); 921 } 922 923 int mm_check_module_compatibility(ManagedModule *m) 924 { 925 if (strchr(m->min_unrealircd_version, '*')) 926 { 927 /* By wildcard, eg: "5.*" */ 928 if (!match_simple(m->min_unrealircd_version, VERSIONONLY)) 929 return 0; 930 } else 931 { 932 /* By strcmp, eg: "5.0.0" */ 933 if (strnatcasecmp(m->min_unrealircd_version, VERSIONONLY) > 0) 934 return 0; 935 } 936 if (m->max_unrealircd_version) 937 { 938 if (strchr(m->max_unrealircd_version, '*')) 939 { 940 /* By wildcard, eg: "5.*" */ 941 if (!match_simple(m->max_unrealircd_version, VERSIONONLY)) 942 return 0; 943 } else 944 { 945 /* By strcmp, eg: "5.0.5" */ 946 if (strnatcasecmp(m->max_unrealircd_version, VERSIONONLY) <= 0) 947 return 0; 948 } 949 } 950 return 1; 951 } 952 953 #define MMMS_INSTALLED 0x0001 954 #define MMMS_UPGRADE_AVAILABLE 0x0002 955 #define MMMS_UNAVAILABLE 0x0004 956 957 int mm_get_module_status(ManagedModule *m) 958 { 959 FILE *fd; 960 char fname[512]; 961 const char *our_sha256sum; 962 963 snprintf(fname, sizeof(fname), "%s/src/modules/%s.c", BUILDDIR, m->name); 964 if (!file_exists(fname)) 965 { 966 if (!mm_check_module_compatibility(m)) 967 return MMMS_UNAVAILABLE; 968 return 0; 969 } 970 971 our_sha256sum = sha256sum_file(fname); 972 if (!strcasecmp(our_sha256sum, m->sha256sum)) 973 { 974 return MMMS_INSTALLED; 975 } else { 976 if (!mm_check_module_compatibility(m)) 977 return MMMS_INSTALLED|MMMS_UNAVAILABLE; 978 return MMMS_INSTALLED|MMMS_UPGRADE_AVAILABLE; 979 } 980 981 return 0; 982 } 983 984 char *mm_get_module_status_string(ManagedModule *m) 985 { 986 int status = mm_get_module_status(m); 987 if (status == 0) 988 return ""; 989 else if (status == MMMS_UNAVAILABLE) 990 return "unav"; 991 else if (status == MMMS_INSTALLED) 992 return "inst"; 993 else if (status == (MMMS_INSTALLED|MMMS_UNAVAILABLE)) 994 return "inst/UNAV"; 995 else if (status == (MMMS_INSTALLED|MMMS_UPGRADE_AVAILABLE)) 996 return "inst/UPD"; 997 return "UNKNOWN?"; 998 } 999 1000 char *mm_get_module_status_string_long(ManagedModule *m) 1001 { 1002 int status = mm_get_module_status(m); 1003 if (status == 0) 1004 return "Not installed"; 1005 else if (status == MMMS_UNAVAILABLE) 1006 return "Unavailable for your UnrealIRCd version"; 1007 else if (status == MMMS_INSTALLED) 1008 return "Installed and up to date"; 1009 else if (status == (MMMS_INSTALLED|MMMS_UNAVAILABLE)) 1010 return "Installed, an upgrade is available but not for your UnrealIRCd version"; 1011 else if (status == (MMMS_INSTALLED|MMMS_UPGRADE_AVAILABLE)) 1012 return "Installed, upgrade available"; 1013 return "UNKNOWN?"; 1014 } 1015 1016 /** Find a module by name, return NULL if not found. */ 1017 ManagedModule *mm_find_module(char *name) 1018 { 1019 ManagedModule *m; 1020 1021 for (m = managed_modules; m; m = m->next) 1022 if (!strcasecmp(name, m->name)) 1023 return m; 1024 return NULL; 1025 } 1026 1027 /** Count the unknown modules (untracked modules) */ 1028 int count_unknown_modules(void) 1029 { 1030 DIR *fd; 1031 struct dirent *dir; 1032 int count = 0; 1033 char dirname[512]; 1034 1035 snprintf(dirname, sizeof(dirname), "%s/src/modules/third", BUILDDIR); 1036 1037 fd = opendir(dirname); 1038 if (fd) 1039 { 1040 while ((dir = readdir(fd))) 1041 { 1042 char *fname = dir->d_name; 1043 if (filename_has_suffix(fname, ".c")) 1044 { 1045 char modname[512], *p; 1046 snprintf(modname, sizeof(modname), "third/%s", filename_strip_suffix(fname, ".c")); 1047 if (!mm_find_module(modname)) 1048 count++; 1049 } 1050 } 1051 closedir(fd); 1052 } 1053 return count; 1054 } 1055 1056 void mm_list(char *searchname) 1057 { 1058 ManagedModule *m; 1059 int largest_column[3]; 1060 int padname; 1061 int padversion; 1062 struct dirent *dir; 1063 DIR *fd; 1064 char dirname[512]; 1065 char *status; 1066 int first_unknown = 1; 1067 1068 if (searchname) 1069 printf("Searching for '%s' in names of all available modules...\n", searchname); 1070 1071 memset(&largest_column, 0, sizeof(largest_column)); 1072 largest_column[COLUMN_STATUS] = strlen("inst/UNAV"); 1073 largest_column[COLUMN_NAME] = strlen("Name:"); 1074 largest_column[COLUMN_VERSION] = strlen("Version:"); 1075 1076 for (m = managed_modules; m; m = m->next) 1077 { 1078 if (strlen(m->name) > largest_column[COLUMN_NAME]) 1079 largest_column[COLUMN_NAME] = strlen(m->name); 1080 if (strlen(m->version) > largest_column[COLUMN_VERSION]) 1081 largest_column[COLUMN_VERSION] = strlen(m->version); 1082 } 1083 /* We try to produce neat output, but not at all costs */ 1084 if (largest_column[COLUMN_NAME] > 32) 1085 largest_column[COLUMN_NAME] = 32; 1086 if (largest_column[COLUMN_VERSION] > 16) 1087 largest_column[COLUMN_VERSION] = 16; 1088 1089 mm_list_print("Status:", "Name:", "Version:", "Description:", largest_column); 1090 printf("|=======================================================================================\n"); 1091 1092 for (m = managed_modules; m; m = m->next) 1093 { 1094 if (searchname && !strstr(m->name, searchname)) 1095 continue; 1096 1097 status = mm_get_module_status_string(m); 1098 mm_list_print(status, m->name, m->version, m->description, largest_column); 1099 } 1100 1101 snprintf(dirname, sizeof(dirname), "%s/src/modules/third", BUILDDIR); 1102 fd = opendir(dirname); 1103 if (fd) 1104 { 1105 while ((dir = readdir(fd))) 1106 { 1107 char *fname = dir->d_name; 1108 if (filename_has_suffix(fname, ".c")) 1109 { 1110 char modname[512], *p; 1111 snprintf(modname, sizeof(modname), "third/%s", filename_strip_suffix(fname, ".c")); 1112 if (searchname && !strstr(searchname, modname)) 1113 continue; 1114 if (!mm_find_module(modname)) 1115 { 1116 if (first_unknown) 1117 { 1118 printf("|---------------------------------------------------------------------------------------\n"); 1119 first_unknown = 0; 1120 } 1121 mm_list_print("UNKNOWN", modname, "", "", largest_column); 1122 } 1123 } 1124 } 1125 closedir(fd); 1126 } 1127 1128 printf("|=======================================================================================\n"); 1129 1130 printf("\nStatus column legend:\n" 1131 " : not installed\n" 1132 "inst : module installed\n" 1133 "inst/UPD : module installed, upgrade available (latest version differs from yours)\n" 1134 "unav : module not available for your UnrealIRCd version\n" 1135 "inst/UNAV : module installed, upgrade exists but is not available for your UnrealIRCd version (too old UnrealIRCd version?)\n" 1136 "UNKNOWN : module does not exist in any repository (perhaps you installed it manually?), module will be left untouched\n"); 1137 1138 printf("\nFor more information about a particular module, use './unrealircd module info name-of-module'\n\n"); 1139 print_documentation(); 1140 } 1141 1142 int mm_compile(ManagedModule *m, char *tmpfile, int test) 1143 { 1144 char newpath[512]; 1145 char cmd[512]; 1146 const char *basename; 1147 char *p; 1148 FILE *fd; 1149 char buf[512]; 1150 int n; 1151 1152 if (test) 1153 printf("Test compiling %s...\n", m->name); 1154 else 1155 printf("Compiling %s...\n", m->name); 1156 1157 basename = unreal_getfilename(test ? tmpfile : m->name); 1158 snprintf(newpath, sizeof(newpath), "%s/src/modules/third/%s%s", BUILDDIR, basename, test ? "" : ".c"); 1159 if (!test) 1160 { 1161 /* If the file already exists then we are upgrading. 1162 * It's a good idea to backup the file rather than 1163 * just delete it. Perhaps the user had local changes 1164 * he/she wished to preserve and accidently went 1165 * through the upgrade procedure. 1166 */ 1167 char backupfile[512]; 1168 snprintf(backupfile, sizeof(backupfile), "%s.bak", newpath); 1169 unlink(backupfile); 1170 (void)rename(newpath, backupfile); 1171 } 1172 if (!unreal_copyfileex(tmpfile, newpath, 0)) 1173 return 0; 1174 1175 snprintf(cmd, sizeof(cmd), 1176 "cd \"%s\"; $MAKE custommodule MODULEFILE=\"%s\"", 1177 BUILDDIR, 1178 filename_strip_suffix(basename, ".c") 1179 ); 1180 fd = popen(cmd, "r"); 1181 if (!fd) 1182 { 1183 fprintf(stderr, "ERROR: Could not issue command: %s\n", cmd); 1184 unlink(newpath); 1185 return 0; 1186 } 1187 while((fgets(buf, sizeof(buf), fd))) 1188 { 1189 printf("%s", buf); 1190 } 1191 n = pclose(fd); 1192 1193 if (test) 1194 { 1195 /* Remove the XXXXXXX.modname.c file */ 1196 unlink(newpath); 1197 /* Remove the XXXXXXX.modname.so file */ 1198 newpath[strlen(newpath)-2] = '\0'; // cut off .c 1199 strlcat(newpath, ".so", sizeof(newpath)); 1200 unlink(newpath); 1201 } 1202 1203 if (WIFEXITED(n) && (WEXITSTATUS(n) == 0)) 1204 return 1; 1205 1206 fprintf(stderr, "ERROR: Compile errors encountered while compiling module '%s'\n" 1207 "You are suggested to contact the author (%s) of this module:\n%s\n", 1208 m->name, m->author, m->troubleshooting); 1209 return 0; 1210 } 1211 1212 /** Actually download and install the module. 1213 * This assumes compatibility checks have already been done. 1214 */ 1215 void mm_install_module(ManagedModule *m) 1216 { 1217 const char *basename = unreal_getfilename(m->source); 1218 char *tmpfile; 1219 const char *sha256; 1220 1221 if (!basename) 1222 basename = "mod.c"; 1223 tmpfile = unreal_mktemp(TMPDIR, basename); 1224 1225 printf("Downloading %s from %s...\n", m->name, m->source); 1226 if (!mm_http_request(m->source, tmpfile, 1)) 1227 { 1228 fprintf(stderr, "Repository %s seems to list a module file that cannot be retrieved (%s).\n", m->repo_url, m->source); 1229 fprintf(stderr, "Fatal error encountered. Contact %s: %s\n", m->author, m->troubleshooting); 1230 exit(-1); 1231 } 1232 1233 sha256 = sha256sum_file(tmpfile); 1234 if (!sha256) 1235 { 1236 fprintf(stderr, "ERROR: Temporary file '%s' has disappeared -- strange\n", tmpfile); 1237 fprintf(stderr, "Fatal error encountered. Check for errors above. Perhaps try running the command again?\n"); 1238 exit(-1); 1239 } 1240 if (strcasecmp(sha256, m->sha256sum)) 1241 { 1242 fprintf(stderr, "ERROR: SHA256 Checksum mismatch\n" 1243 "Expected (value in repository list): %s\n" 1244 "Received (value of downloaded file): %s\n", 1245 m->sha256sum, sha256); 1246 fprintf(stderr, "Fatal error encountered, see above. Try running the command again in 5-10 minutes.\n" 1247 "If the issue persists, contact the repository manager of %s\n", 1248 m->repo_url); 1249 exit(-1); 1250 } 1251 if (!mm_compile(m, tmpfile, 1)) 1252 { 1253 fprintf(stderr, "Fatal error encountered, see above.\n"); 1254 exit(-1); 1255 } 1256 1257 if (!mm_compile(m, tmpfile, 0)) 1258 { 1259 fprintf(stderr, "The test compile went OK earlier, but the final compile did not. BAD!!\n"); 1260 exit(-1); 1261 } 1262 printf("Module %s compiled successfully\n", m->name); 1263 } 1264 1265 /** Uninstall a module. 1266 * This function takes a string rather than a ManagedModule 1267 * because it also allows uninstalling of unmanaged (local) modules. 1268 */ 1269 void mm_uninstall_module(char *modulename) 1270 { 1271 struct dirent *dir; 1272 DIR *fd; 1273 char dirname[512], fullname[512]; 1274 int found = 0; 1275 1276 snprintf(dirname, sizeof(dirname), "%s/src/modules/third", BUILDDIR); 1277 fd = opendir(dirname); 1278 if (fd) 1279 { 1280 while ((dir = readdir(fd))) 1281 { 1282 char *fname = dir->d_name; 1283 if (filename_has_suffix(fname, ".c") || filename_has_suffix(fname, ".so") || filename_has_suffix(fname, ".dll")) 1284 { 1285 char modname[512], *p; 1286 snprintf(modname, sizeof(modname), "third/%s", filename_strip_suffix(fname, NULL)); 1287 if (!strcasecmp(modname, modulename)) 1288 { 1289 found = 1; 1290 snprintf(fullname, sizeof(fullname), "%s/%s", dirname, fname); 1291 //printf("Deleting '%s'\n", fullname); 1292 unlink(fullname); 1293 } 1294 } 1295 } 1296 closedir(fd); 1297 } 1298 1299 if (!found) 1300 { 1301 fprintf(stderr, "ERROR: Module '%s' is not installed, so can't uninstall.\n", modulename); 1302 exit(-1); 1303 } 1304 1305 printf("Module '%s' uninstalled successfully\n", modulename); 1306 } 1307 1308 void mm_make_install(void) 1309 { 1310 char cmd[512]; 1311 int n; 1312 if (no_make_install) 1313 return; 1314 printf("Running 'make install'...\n"); 1315 snprintf(cmd, sizeof(cmd), "cd \"%s\"; $MAKE install 1>/dev/null 2>&1", BUILDDIR); 1316 n = system(cmd); 1317 } 1318 1319 void mm_install(int argc, char *args[], int upgrade) 1320 { 1321 ManagedModule *m; 1322 MultiLine *l; 1323 char *name = args[1]; 1324 int status; 1325 1326 if (!name) 1327 { 1328 fprintf(stderr, "ERROR: Use: module install third/name-of-module\n"); 1329 exit(-1); 1330 } 1331 1332 if (strncmp(name, "third/", 6)) 1333 { 1334 fprintf(stderr, "ERROR: Use: module install third/name-of-module\nYou must prefix the modulename with third/\n"); 1335 exit(-1); 1336 } 1337 1338 m = mm_find_module(name); 1339 if (!m) 1340 { 1341 fprintf(stderr, "ERROR: Module '%s' not found\n", name); 1342 exit(-1); 1343 } 1344 status = mm_get_module_status(m); 1345 if (status == MMMS_UNAVAILABLE) 1346 { 1347 fprintf(stderr, "ERROR: Module '%s' exists, but is not compatible with your UnrealIRCd version:\n" 1348 "Your UnrealIRCd version : %s\n" 1349 "Minimum version required : %s\n", 1350 name, 1351 VERSIONONLY, 1352 m->min_unrealircd_version); 1353 if (m->max_unrealircd_version) 1354 fprintf(stderr, "Maximum version : %s\n", m->max_unrealircd_version); 1355 exit(-1); 1356 } 1357 if (upgrade && (status == MMMS_INSTALLED)) 1358 { 1359 /* If updating, and we are already on latest version, then don't upgrade */ 1360 printf("Module %s is the latest version, no upgrade needed\n", m->name); 1361 } 1362 mm_install_module(m); 1363 mm_make_install(); 1364 if (m->post_install_text) 1365 { 1366 printf("Post-installation information for %s from the author:\n", m->name); 1367 printf("---\n"); 1368 for (l = m->post_install_text; l; l = l->next) 1369 printf(" %s\n", l->line); 1370 printf("---\n"); 1371 } else { 1372 printf("Don't forget to add a 'loadmodule' line for the module and rehash\n"); 1373 } 1374 } 1375 1376 void mm_uninstall(int argc, char *args[]) 1377 { 1378 ManagedModule *m; 1379 char *name = args[1]; 1380 1381 if (!name) 1382 { 1383 fprintf(stderr, "ERROR: Use: module uninstall third/name-of-module\n"); 1384 exit(-1); 1385 } 1386 1387 if (strncmp(name, "third/", 6)) 1388 { 1389 fprintf(stderr, "ERROR: Use: module uninstall third/name-of-module\nYou must prefix the modulename with third/\n"); 1390 exit(-1); 1391 } 1392 1393 mm_uninstall_module(name); 1394 mm_make_install(); 1395 exit(0); 1396 } 1397 1398 void mm_upgrade(int argc, char *args[]) 1399 { 1400 ManagedModule *m; 1401 char *name = args[1]; 1402 int upgraded = 0; 1403 int uptodate_already = 0; 1404 int update_unavailable = 0; 1405 int i = 1, n; 1406 1407 if (args[i] && !strcmp(args[i], "--no-install")) 1408 { 1409 no_make_install = 1; 1410 i++; 1411 } 1412 1413 name = args[i]; 1414 if (name) 1415 { 1416 // TODO: First check if it needs an upgrade? ;) 1417 mm_install(argc, args, 1); 1418 exit(0); 1419 } 1420 1421 /* Without arguments means: check all installed modules */ 1422 for (m = managed_modules; m; m = m->next) 1423 { 1424 int status = mm_get_module_status(m); 1425 if (status == (MMMS_INSTALLED|MMMS_UPGRADE_AVAILABLE)) 1426 { 1427 args[1] = m->name; 1428 mm_install(1, args, 1); 1429 upgraded++; 1430 } else 1431 if (status == MMMS_INSTALLED) 1432 { 1433 uptodate_already++; 1434 } else 1435 if (status == (MMMS_INSTALLED|MMMS_UNAVAILABLE)) 1436 { 1437 update_unavailable++; 1438 } 1439 } 1440 printf("All actions were successful. %d module(s) upgraded, %d already up-to-date\n", 1441 upgraded, uptodate_already); 1442 if (update_unavailable) 1443 printf("%d module(s) have updates but not for your UnrealIRCd version\n", update_unavailable); 1444 if ((n = count_unknown_modules())) 1445 printf("%d module(s) are unknown/untracked\n", n); 1446 1447 printf("For more details, you can always run ./unrealircd module list\n"); 1448 } 1449 1450 void mm_info(int argc, char *args[]) 1451 { 1452 ManagedModule *m; 1453 MultiLine *l; 1454 char *name = args[1]; 1455 1456 if (!name) 1457 { 1458 fprintf(stderr, "ERROR: Use: unrealircd module info name-of-module\n"); 1459 exit(-1); 1460 } 1461 1462 m = mm_find_module(name); 1463 if (!m) 1464 { 1465 // TODO: we should probably be a bit more specific if the module exists locally (UNAV) */ 1466 fprintf(stderr, "ERROR: Module '%s' not found in any repository\n", name); 1467 exit(-1); 1468 } 1469 printf("Name: %s\n" 1470 "Version: %s\n" 1471 "Description: %s\n" 1472 "Author: %s\n" 1473 "Documentation: %s\n" 1474 "Troubleshooting: %s\n" 1475 "Source: %s\n" 1476 "Min. UnrealIRCd version: %s\n", 1477 m->name, 1478 m->version, 1479 m->description, 1480 m->author, 1481 m->documentation, 1482 m->troubleshooting, 1483 m->source, 1484 m->min_unrealircd_version); 1485 if (m->max_unrealircd_version) 1486 printf("Min. UnrealIRCd version: %s\n", m->max_unrealircd_version); 1487 printf("Status: %s\n", mm_get_module_status_string_long(m)); 1488 if (m->post_install_text) 1489 { 1490 printf("------ Post-installation text ------\n"); 1491 for (l = m->post_install_text; l; l = l->next) 1492 printf(" %s\n", l->line); 1493 printf("------ End of post-install text ------\n"); 1494 } 1495 } 1496 1497 void mm_usage(void) 1498 { 1499 fprintf(stderr, "Use any of the following actions:\n" 1500 "unrealircd module list List all the available and installed modules\n" 1501 "unrealircd module info name-of-module Show more information about the module\n" 1502 "unrealircd module install name-of-module Install the specified module\n" 1503 "unrealircd module uninstall name-of-module Uninstall the specified module\n" 1504 "unrealircd module upgrade name-of-module Upgrade the specified module (if needed)\n" 1505 "unrealircd module upgrade Upgrade all modules (if needed)\n" 1506 "unrealircd module generate-repository Generate a repository index (you are\n" 1507 " unlikely to need this, only for repo admins)\n"); 1508 print_documentation(); 1509 exit(-1); 1510 } 1511 1512 void print_md_block(FILE *fdo, ManagedModule *m) 1513 { 1514 fprintf(fdo, "module \"%s\"\n{\n", m->name); 1515 fprintf(fdo, "\tdescription \"%s\";\n", unreal_add_quotes(m->description)); 1516 fprintf(fdo, "\tversion \"%s\";\n", unreal_add_quotes(m->version)); 1517 fprintf(fdo, "\tauthor \"%s\";\n", unreal_add_quotes(m->author)); 1518 fprintf(fdo, "\tdocumentation \"%s\";\n", unreal_add_quotes(m->documentation)); 1519 fprintf(fdo, "\ttroubleshooting \"%s\";\n", unreal_add_quotes(m->troubleshooting)); 1520 fprintf(fdo, "\tsource \"%s\";\n", unreal_add_quotes(m->source)); 1521 fprintf(fdo, "\tsha256sum \"%s\";\n", unreal_add_quotes(m->sha256sum)); 1522 fprintf(fdo, "\tmin-unrealircd-version \"%s\";\n", unreal_add_quotes(m->min_unrealircd_version)); 1523 if (m->max_unrealircd_version) 1524 fprintf(fdo, "\tmax-unrealircd-version \"%s\";\n", unreal_add_quotes(m->max_unrealircd_version)); 1525 if (m->post_install_text) 1526 { 1527 MultiLine *l; 1528 fprintf(fdo, "\tpost-install-text\n" 1529 "\t{\n"); 1530 for (l = m->post_install_text; l; l = l->next) 1531 fprintf(fdo, "\t\t\"%s\";\n", unreal_add_quotes(l->line)); 1532 fprintf(fdo, "\t}\n"); 1533 } 1534 fprintf(fdo, "}\n\n"); 1535 } 1536 1537 void mm_generate_repository_usage(void) 1538 { 1539 fprintf(stderr, "Usage: ./unrealircd module generate-repository <url base path> <directory-with-modules> <name of output file> [optional-minimum-version-filter]\n"); 1540 fprintf(stderr, "For example: ./unrealircd module generate-repository https://www.unrealircd.org/modules/ src/modules/third modules.lst\n"); 1541 } 1542 1543 void mm_generate_repository(int argc, char *args[]) 1544 { 1545 DIR *fd; 1546 struct dirent *dir; 1547 int count = 0; 1548 char *urlbasepath; 1549 char *dirname; 1550 char *outputfile; 1551 char *minversion; 1552 char modname[128]; 1553 char fullname[512]; 1554 ManagedModule *m; 1555 FILE *fdo; 1556 1557 urlbasepath = args[1]; 1558 dirname = args[2]; 1559 outputfile = args[3]; 1560 minversion = args[4]; 1561 1562 if (!urlbasepath || !dirname || !outputfile) 1563 { 1564 mm_generate_repository_usage(); 1565 exit(-1); 1566 } 1567 1568 if ((strlen(urlbasepath) < 2) || (urlbasepath[strlen(urlbasepath)-1] != '/')) 1569 { 1570 fprintf(stderr, "Error: the URL base path must end with a slash\n"); 1571 mm_generate_repository_usage(); 1572 exit(-1); 1573 } 1574 1575 fd = opendir(dirname); 1576 if (!fd) 1577 { 1578 fprintf(stderr, "Cannot open directory '%s': %s\n", dirname, strerror(errno)); 1579 exit(-1); 1580 } 1581 1582 fdo = fopen(outputfile, "w"); 1583 if (!fdo) 1584 { 1585 fprintf(stderr, "Could not open file '%s' for writing: %s\n", outputfile, strerror(errno)); 1586 exit(-1); 1587 } 1588 1589 while ((dir = readdir(fd))) 1590 { 1591 char *fname = dir->d_name; 1592 if (filename_has_suffix(fname, ".c")) 1593 { 1594 int hide = 0; 1595 snprintf(fullname, sizeof(fullname), "%s/%s", dirname, fname); 1596 snprintf(modname, sizeof(modname), "third/%s", filename_strip_suffix(fname, ".c")); 1597 printf("Processing: %s\n", modname); 1598 m = mm_parse_module_c_file(modname, fullname); 1599 if (!m) 1600 { 1601 fprintf(stderr, "WARNING: Skipping module '%s' due to errors\n", modname); 1602 continue; 1603 } 1604 m->sha256sum = strdup(sha256sum_file(fullname)); 1605 m->source = safe_alloc(512); 1606 snprintf(m->source, 512, "%s%s.c", urlbasepath, modname + 6); 1607 /* filter */ 1608 if (minversion && m->min_unrealircd_version && strncmp(minversion, m->min_unrealircd_version, strlen(minversion))) 1609 hide = 1; 1610 /* /filter */ 1611 if (!hide) 1612 print_md_block(fdo, m); 1613 free_managed_module(m); 1614 m = NULL; 1615 } 1616 } 1617 closedir(fd); 1618 fclose(fdo); 1619 } 1620 1621 void mm_parse_c_file(int argc, char *args[]) 1622 { 1623 char *fullname = args[1]; 1624 const char *basename; 1625 char modname[256]; 1626 ManagedModule *m; 1627 1628 if (!fullname) 1629 { 1630 fprintf(stderr, "Usage: ./unrealircd module parse-c-file path/to/file.c\n"); 1631 exit(-1); 1632 } 1633 if (!file_exists(fullname)) 1634 { 1635 fprintf(stderr, "ERROR: Unable to open C file '%s'\n", fullname); 1636 exit(-1); 1637 } 1638 basename = unreal_getfilename(fullname); 1639 1640 snprintf(modname, sizeof(modname), "third/%s", filename_strip_suffix(basename, ".c")); 1641 printf("Processing: %s\n", modname); 1642 m = mm_parse_module_c_file(modname, fullname); 1643 if (!m) 1644 { 1645 fprintf(stderr, "Errors encountered. See above\n"); 1646 exit(-1); 1647 } 1648 m->sha256sum = strdup(sha256sum_file(fullname)); 1649 m->source = strdup("..."); 1650 print_md_block(stdout, m); 1651 free_managed_module(m); 1652 exit(0); 1653 } 1654 1655 int mm_detect_make_is_gmake(void) 1656 { 1657 FILE *fd; 1658 char buf[512], *s; 1659 1660 fd = popen("$MAKE --version 2>&1", "r"); 1661 if (fd) 1662 { 1663 *buf = '\0'; 1664 s = fgets(buf, sizeof(buf), fd); 1665 pclose(fd); 1666 if (s && strstr(s, "GNU Make")) 1667 return 1; /* Good! We are done. */ 1668 } 1669 return 0; 1670 } 1671 1672 void mm_detect_make(void) 1673 { 1674 FILE *fd; 1675 char *s; 1676 char buf[512]; 1677 1678 /* Get or set $MAKE */ 1679 s = getenv("MAKE"); 1680 if (!s) 1681 setenv("MAKE", "make", 1); 1682 1683 if (mm_detect_make_is_gmake()) 1684 return; 1685 1686 /* Try again with MAKE=gmake */ 1687 setenv("MAKE", "gmake", 1); 1688 if (mm_detect_make_is_gmake()) 1689 return; 1690 1691 fprintf(stderr, "ERROR: GNU Make is not found as 'make' or 'gmake'\n"); 1692 exit(-1); 1693 } 1694 1695 void mm_self_test(void) 1696 { 1697 char name[512]; 1698 1699 if (!file_exists(BUILDDIR)) 1700 { 1701 fprintf(stderr, "ERROR: Directory %s does not exist.\n" 1702 "The UnrealIRCd source is required for the module manager to work!\n", 1703 BUILDDIR); 1704 exit(-1); 1705 } else { 1706 snprintf(name, sizeof(name), "%s/src/modules/third/Makefile", BUILDDIR); 1707 if (!file_exists(name)) 1708 { 1709 fprintf(stderr, "ERROR: Directory %s exists, but your UnrealIRCd is not compiled yet.\n" 1710 "You must compile your UnrealIRCd first (run './Config', then 'make install')\n", 1711 BUILDDIR); 1712 exit(-1); 1713 } 1714 } 1715 mm_detect_make(); 1716 } 1717 1718 void modulemanager(int argc, char *args[]) 1719 { 1720 if (!args[0]) 1721 mm_usage(); 1722 1723 mm_self_test(); 1724 1725 /* The following operations do not require reading 1726 * of the repository list and are always available: 1727 */ 1728 if (!strcasecmp(args[0], "uninstall") || 1729 !strcasecmp(args[0], "remove")) 1730 { 1731 mm_uninstall(argc, args); 1732 exit(0); 1733 } 1734 else if (!strcasecmp(args[0], "generate-repository")) 1735 { 1736 mm_generate_repository(argc, args); 1737 exit(0); 1738 } 1739 else if (!strcasecmp(args[0], "parse-c-file")) 1740 { 1741 mm_parse_c_file(argc, args); 1742 exit(0); 1743 } 1744 1745 /* Fetch the repository list */ 1746 if (!mm_refresh_repository()) 1747 { 1748 fprintf(stderr, "Fatal error encountered\n"); 1749 exit(-1); 1750 } 1751 1752 if (!strcasecmp(args[0], "list")) 1753 mm_list(args[1]); 1754 else if (!strcasecmp(args[0], "info")) 1755 mm_info(argc, args); 1756 else if (!strcasecmp(args[0], "install")) 1757 { 1758 mm_install(argc, args, 0); 1759 fprintf(stderr, "All actions were successful.\n"); 1760 } 1761 else if (!strcasecmp(args[0], "upgrade")) 1762 mm_upgrade(argc, args); 1763 else 1764 mm_usage(); 1765 } 1766 #endif