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