mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-30 14:46:38 +02:00
35e5d99e32
even when multiple modules were upgraded. Actually not sure about the cause and how this is possible, but running 'make install' only once at the end is the solution, which is something that should be done that way anyway. Reported by westor in https://bugs.unrealircd.org/view.php?id=5919
1771 lines
46 KiB
C
1771 lines
46 KiB
C
/* UnrealIRCd module manager.
|
|
* (C) Copyright 2019 Bram Matthys ("Syzop") and the UnrealIRCd Team.
|
|
* License: GPLv2 or later
|
|
* See https://www.unrealircd.org/docs/Module_manager for user documentation.
|
|
*/
|
|
|
|
#include "unrealircd.h"
|
|
#ifndef _WIN32
|
|
|
|
#define MODULEMANAGER_CONNECT_TIMEOUT 7
|
|
#define MODULEMANAGER_READ_TIMEOUT 20
|
|
|
|
typedef struct ManagedModule ManagedModule;
|
|
|
|
struct ManagedModule
|
|
{
|
|
ManagedModule *prev, *next;
|
|
char *repo_url;
|
|
char *name;
|
|
char *author;
|
|
char *troubleshooting;
|
|
char *documentation;
|
|
char *version;
|
|
char *source;
|
|
char *sha256sum;
|
|
char *min_unrealircd_version;
|
|
char *max_unrealircd_version;
|
|
char *description;
|
|
MultiLine *post_install_text;
|
|
};
|
|
|
|
static ManagedModule *managed_modules = NULL;
|
|
|
|
/* We normally do a 'make install' after upgrading a module.
|
|
* However we will skip it if --no-install is done.
|
|
* This is only done during 'make', as it is unexpected to
|
|
* already install modules at the final location before
|
|
* 'make install' was issued.
|
|
*/
|
|
static int no_make_install = 0;
|
|
|
|
/* Forward declarations */
|
|
int mm_valid_module_name(char *name);
|
|
void free_managed_module(ManagedModule *m);
|
|
|
|
|
|
SSL_CTX *mm_init_tls(void)
|
|
{
|
|
SSL_CTX *ctx_client;
|
|
char buf1[512], buf2[512];
|
|
char *curl_ca_bundle = buf1;
|
|
|
|
SSL_load_error_strings();
|
|
SSLeay_add_ssl_algorithms();
|
|
|
|
ctx_client = SSL_CTX_new(SSLv23_client_method());
|
|
if (!ctx_client)
|
|
return NULL;
|
|
#ifdef HAS_SSL_CTX_SET_MIN_PROTO_VERSION
|
|
SSL_CTX_set_min_proto_version(ctx_client, TLS1_2_VERSION);
|
|
#endif
|
|
SSL_CTX_set_options(ctx_client, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1);
|
|
|
|
/* Verify peer certificate */
|
|
snprintf(buf1, sizeof(buf1), "%s/tls/curl-ca-bundle.crt", CONFDIR);
|
|
if (!file_exists(buf1))
|
|
{
|
|
snprintf(buf2, sizeof(buf2), "%s/doc/conf/tls/curl-ca-bundle.crt", BUILDDIR);
|
|
if (!file_exists(buf2))
|
|
{
|
|
fprintf(stderr, "ERROR: Neither %s nor %s exist.\n"
|
|
"Cannot use module manager without curl-ca-bundle.crt\n",
|
|
buf1, buf2);
|
|
exit(-1);
|
|
}
|
|
curl_ca_bundle = buf2;
|
|
}
|
|
SSL_CTX_load_verify_locations(ctx_client, curl_ca_bundle, NULL);
|
|
SSL_CTX_set_verify(ctx_client, SSL_VERIFY_PEER, NULL);
|
|
|
|
/* Limit ciphers as well */
|
|
SSL_CTX_set_cipher_list(ctx_client, UNREALIRCD_DEFAULT_CIPHERS);
|
|
|
|
return ctx_client;
|
|
}
|
|
|
|
int parse_url(const char *url, char **host, int *port, char **document)
|
|
{
|
|
char *p;
|
|
static char hostbuf[256];
|
|
static char documentbuf[512];
|
|
|
|
if (!str_starts_with_case_sensitive(url, "https://"))
|
|
{
|
|
fprintf(stderr, "ERROR: URL Must start with https! URL: %s\n", url);
|
|
return 0;
|
|
}
|
|
url += 8; /* skip over https:// part */
|
|
|
|
p = strchr(url, '/');
|
|
if (!p)
|
|
return 0;
|
|
|
|
strlncpy(hostbuf, url, sizeof(hostbuf), p - url);
|
|
|
|
strlcpy(documentbuf, p, sizeof(documentbuf));
|
|
|
|
*host = hostbuf;
|
|
*document = documentbuf;
|
|
// TODO: parse port, rather than hardcode:
|
|
*port = 443;
|
|
return 1;
|
|
}
|
|
|
|
int mm_http_request(char *url, char *fname, int follow_redirects)
|
|
{
|
|
char *host = NULL;
|
|
int port = 0;
|
|
char *document = NULL;
|
|
char hostandport[256];
|
|
char buf[1024];
|
|
int n;
|
|
FILE *fd;
|
|
SSL_CTX *ctx_client;
|
|
SSL *ssl = NULL;
|
|
BIO *socket = NULL;
|
|
char *errstr = NULL;
|
|
int got_data = 0, first_line = 1;
|
|
int http_redirect = 0;
|
|
|
|
if (!parse_url(url, &host, &port, &document))
|
|
return 0;
|
|
|
|
snprintf(hostandport, sizeof(hostandport), "%s:%d", host, port);
|
|
|
|
ctx_client = mm_init_tls();
|
|
if (!ctx_client)
|
|
{
|
|
fprintf(stderr, "ERROR: TLS initalization failure (I)\n");
|
|
return 0;
|
|
}
|
|
|
|
alarm(MODULEMANAGER_CONNECT_TIMEOUT);
|
|
|
|
socket = BIO_new_ssl_connect(ctx_client);
|
|
if (!socket)
|
|
{
|
|
fprintf(stderr, "ERROR: TLS initalization failure (II)\n");
|
|
goto out1;
|
|
}
|
|
|
|
BIO_get_ssl(socket, &ssl);
|
|
if (!ssl)
|
|
{
|
|
fprintf(stderr, "ERROR: Could not get TLS connection from BIO -- strange\n");
|
|
goto out2;
|
|
}
|
|
|
|
BIO_set_conn_hostname(socket, hostandport);
|
|
SSL_set_tlsext_host_name(ssl, host);
|
|
|
|
if (BIO_do_connect(socket) != 1)
|
|
{
|
|
fprintf(stderr, "ERROR: Could not connect to %s\n", hostandport);
|
|
//config_report_ssl_error(); FIXME?
|
|
goto out2;
|
|
}
|
|
|
|
if (BIO_do_handshake(socket) != 1)
|
|
{
|
|
fprintf(stderr, "ERROR: Could not connect to %s (TLS handshake failed)\n", hostandport);
|
|
//config_report_ssl_error(); FIXME?
|
|
goto out2;
|
|
}
|
|
|
|
if (!verify_certificate(ssl, host, &errstr))
|
|
{
|
|
fprintf(stderr, "Certificate problem for %s: %s\n", host, errstr);
|
|
goto out2;
|
|
}
|
|
|
|
snprintf(buf, sizeof(buf), "GET %s HTTP/1.1\r\n"
|
|
"User-Agent: UnrealIRCd %s\r\n"
|
|
"Host: %s\r\n"
|
|
"Connection: close\r\n"
|
|
"\r\n",
|
|
document,
|
|
VERSIONONLY,
|
|
hostandport);
|
|
|
|
BIO_puts(socket, buf);
|
|
|
|
fd = fopen(fname, "wb");
|
|
if (!fd)
|
|
{
|
|
fprintf(stderr, "Could not write to temporary file '%s': %s\n",
|
|
fname, strerror(errno));
|
|
goto out2;
|
|
}
|
|
|
|
alarm(MODULEMANAGER_READ_TIMEOUT);
|
|
while ((n = BIO_read(socket, buf, sizeof(buf)-1)) > 0)
|
|
{
|
|
buf[n] = '\0';
|
|
if (got_data)
|
|
{
|
|
fwrite(buf, n, 1, fd); // TODO: check for write errors
|
|
} else {
|
|
/* Still need to parse header */
|
|
char *line, *p = NULL;
|
|
|
|
for (line = strtoken(&p, buf, "\n"); line; line = strtoken(&p, NULL, "\n"))
|
|
{
|
|
if (first_line)
|
|
{
|
|
if (http_redirect)
|
|
{
|
|
if (str_starts_with_case_sensitive(line, "Location: "))
|
|
{
|
|
line += 10;
|
|
stripcrlf(line);
|
|
fclose(fd);
|
|
BIO_free_all(socket);
|
|
SSL_CTX_free(ctx_client);
|
|
if (!str_starts_with_case_sensitive(line, "https://"))
|
|
{
|
|
fprintf(stderr, "Invalid HTTP Redirect to '%s' -- must start with https://\n", line);
|
|
return 0;
|
|
}
|
|
printf("Following redirect to %s\n", line);
|
|
return mm_http_request(line, fname, 0);
|
|
}
|
|
continue;
|
|
}
|
|
if (str_starts_with_case_sensitive(line, "HTTP/1.1 301") ||
|
|
str_starts_with_case_sensitive(line, "HTTP/1.1 302") ||
|
|
str_starts_with_case_sensitive(line, "HTTP/1.1 303") ||
|
|
str_starts_with_case_sensitive(line, "HTTP/1.1 307") ||
|
|
str_starts_with_case_sensitive(line, "HTTP/1.1 308"))
|
|
{
|
|
if (!follow_redirects)
|
|
{
|
|
fprintf(stderr, "ERROR: received HTTP(S) redirect while already following another HTTP(S) redirect.\n");
|
|
goto out3;
|
|
}
|
|
http_redirect = 1;
|
|
continue;
|
|
}
|
|
if (!str_starts_with_case_sensitive(line, "HTTP/1.1 200"))
|
|
{
|
|
stripcrlf(line);
|
|
if (strlen(line) > 128)
|
|
line[128] = '\0';
|
|
fprintf(stderr, "Error while fetching %s: %s\n", url, line);
|
|
goto out3;
|
|
}
|
|
first_line = 0;
|
|
}
|
|
if (!*line || !strcmp(line, "\r"))
|
|
{
|
|
int remaining_bytes;
|
|
|
|
got_data = 1;
|
|
/* Bit of a hack here to write part of the data
|
|
* that is not part of the header but the data response..
|
|
* We need to jump over the NUL byte and then check
|
|
* to see if we can access it.. since it could be either
|
|
* a NUL byte due to the strtoken() or a NUL byte simply
|
|
* because that was all the data from BIO_read() at this point.
|
|
*/
|
|
if (*line == '\0')
|
|
line += 1; /* jump over \0 */
|
|
else
|
|
line += 2; /* jump over \r\0 */
|
|
remaining_bytes = n - (line - buf);
|
|
if (remaining_bytes > 0)
|
|
fwrite(line, remaining_bytes, 1, fd);
|
|
break; /* must break the for loop here */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!got_data)
|
|
{
|
|
fprintf(stderr, "Error while fetching %s: unable to retrieve data\n", url);
|
|
goto out3;
|
|
}
|
|
|
|
fclose(fd);
|
|
BIO_free_all(socket);
|
|
SSL_CTX_free(ctx_client);
|
|
return 1;
|
|
out3:
|
|
fclose(fd);
|
|
out2:
|
|
BIO_free_all(socket);
|
|
out1:
|
|
SSL_CTX_free(ctx_client);
|
|
alarm(0);
|
|
return 0;
|
|
}
|
|
|
|
typedef enum ParseModuleHeaderStage {
|
|
PMH_STAGE_LOOKING = 0,
|
|
PMH_STAGE_MODULEHEADER = 1,
|
|
PMH_STAGE_MOD_HEADER = 2,
|
|
PMH_STAGE_GOT_NAME = 3,
|
|
PMH_STAGE_GOT_VERSION = 4,
|
|
PMH_STAGE_GOT_DESCRIPTION = 5,
|
|
PMH_STAGE_GOT_AUTHOR = 6,
|
|
PMT_STAGE_DONE = 7,
|
|
} ParseModuleHeaderStage;
|
|
|
|
typedef enum ParseModuleConfigStage {
|
|
PMC_STAGE_LOOKING = 0,
|
|
PMC_STAGE_STARTED = 1,
|
|
PMC_STAGE_FINISHED = 2,
|
|
} ParseModuleConfigStage;
|
|
|
|
int parse_quoted_string(char *buf, char *dest, size_t destlen)
|
|
{
|
|
char *p, *p2;
|
|
size_t max;
|
|
char *i, *o;
|
|
|
|
p = strchr(buf, '"');
|
|
if (!p)
|
|
return 0;
|
|
p2 = strrchr(p+1, '"');
|
|
if (!p2)
|
|
return 0;
|
|
max = p2 - p;
|
|
if (max > destlen)
|
|
max = destlen;
|
|
strlcpy(dest, p+1, max);
|
|
unreal_del_quotes(dest);
|
|
return 1;
|
|
}
|
|
|
|
#undef CheckNull
|
|
#define CheckNull(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", m->name, (x)->line_number); return 0; }
|
|
|
|
/** Parse a module { } line from a module (not repo!!) */
|
|
int mm_module_file_config(ManagedModule *m, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
|
|
if (ce->value)
|
|
{
|
|
config_error("%s:%d: module { } block should not have a name.",
|
|
m->name, ce->line_number);
|
|
return 0;
|
|
}
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "source") ||
|
|
!strcmp(cep->name, "version") ||
|
|
!strcmp(cep->name, "author") ||
|
|
!strcmp(cep->name, "sha256sum") ||
|
|
!strcmp(cep->name, "description")
|
|
)
|
|
{
|
|
config_error("%s:%d: module::%s should not be in here (it only exists in repository entries)",
|
|
m->name, cep->line_number, cep->name);
|
|
return 0;
|
|
}
|
|
else if (!strcmp(cep->name, "troubleshooting"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->troubleshooting, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "documentation"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->documentation, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "min-unrealircd-version"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->min_unrealircd_version, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "max-unrealircd-version"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->max_unrealircd_version, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "post-install-text"))
|
|
{
|
|
if (cep->items)
|
|
{
|
|
ConfigEntry *cepp;
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
addmultiline(&m->post_install_text, cepp->name);
|
|
} else {
|
|
CheckNull(cep);
|
|
addmultiline(&m->post_install_text, cep->value);
|
|
}
|
|
}
|
|
/* unknown items are silently ignored for future compatibility */
|
|
}
|
|
|
|
if (!m->documentation)
|
|
{
|
|
config_error("%s:%d: module::documentation missing", m->name, ce->line_number);
|
|
return 0;
|
|
}
|
|
|
|
if (!m->troubleshooting)
|
|
{
|
|
config_error("%s:%d: module::troubleshooting missing", m->name, ce->line_number);
|
|
return 0;
|
|
}
|
|
|
|
if (!m->min_unrealircd_version)
|
|
{
|
|
config_error("%s:%d: module::min-unrealircd-version missing", m->name, ce->line_number);
|
|
return 0;
|
|
}
|
|
|
|
/* max_unrealircd_version is optional */
|
|
|
|
/* post_install_text is optional */
|
|
|
|
return 1;
|
|
}
|
|
|
|
#undef CheckNull
|
|
|
|
int mm_parse_module_file(ManagedModule *m, char *buf, unsigned int line_offset)
|
|
{
|
|
ConfigFile *cf;
|
|
ConfigEntry *ce;
|
|
|
|
cf = config_parse_with_offset(m->name, buf, line_offset);
|
|
if (!cf)
|
|
return 0; /* eg: parse errors */
|
|
|
|
/* Parse the module { } block (only one!) */
|
|
for (ce = cf->items; ce; ce = ce->next)
|
|
{
|
|
if (!strcmp(ce->name, "module"))
|
|
{
|
|
int n = mm_module_file_config(m, ce);
|
|
config_free(cf);
|
|
return n;
|
|
}
|
|
}
|
|
|
|
config_free(cf);
|
|
config_error("No module block found within module source file. Contact author.\n");
|
|
return 1;
|
|
}
|
|
|
|
#define MODULECONFIGBUFFER 16384
|
|
ManagedModule *mm_parse_module_c_file(char *modulename, char *fname)
|
|
{
|
|
char buf[1024];
|
|
FILE *fd;
|
|
ParseModuleHeaderStage parse_module_header = PMH_STAGE_LOOKING;
|
|
ParseModuleConfigStage parse_module_config = PMC_STAGE_LOOKING;
|
|
char *moduleconfig = NULL;
|
|
int linenr = 0, module_config_start_line = 0;
|
|
char module_header_name[128];
|
|
char module_header_version[64];
|
|
char module_header_description[256];
|
|
char module_header_author[128];
|
|
ManagedModule *m = NULL;
|
|
|
|
*module_header_name = *module_header_version = *module_header_description = *module_header_author = '\0';
|
|
|
|
if (!mm_valid_module_name(modulename))
|
|
{
|
|
fprintf(stderr, "Module file '%s' contains forbidden characters\n", modulename);
|
|
return NULL;
|
|
}
|
|
|
|
fd = fopen(fname, "r");
|
|
if (!fd)
|
|
{
|
|
fprintf(stderr, "Unable to open module '%s', file '%s': %s\n",
|
|
modulename, fname, strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
moduleconfig = safe_alloc(MODULECONFIGBUFFER); /* should be sufficient */
|
|
while ((fgets(buf, sizeof(buf), fd)))
|
|
{
|
|
linenr++;
|
|
stripcrlf(buf);
|
|
/* parse module header stuff: */
|
|
switch (parse_module_header)
|
|
{
|
|
case PMH_STAGE_LOOKING:
|
|
if (strstr(buf, "ModuleHeader"))
|
|
parse_module_header = PMH_STAGE_MODULEHEADER;
|
|
else
|
|
break;
|
|
/*fallthrough*/
|
|
case PMH_STAGE_MODULEHEADER:
|
|
if (strstr(buf, "MOD_HEADER"))
|
|
parse_module_header = PMH_STAGE_MOD_HEADER;
|
|
break;
|
|
case PMH_STAGE_MOD_HEADER:
|
|
if (parse_quoted_string(buf, module_header_name, sizeof(module_header_name)))
|
|
parse_module_header = PMH_STAGE_GOT_NAME;
|
|
break;
|
|
case PMH_STAGE_GOT_NAME:
|
|
if (parse_quoted_string(buf, module_header_version, sizeof(module_header_version)))
|
|
parse_module_header = PMH_STAGE_GOT_VERSION;
|
|
break;
|
|
case PMH_STAGE_GOT_VERSION:
|
|
if (parse_quoted_string(buf, module_header_description, sizeof(module_header_description)))
|
|
parse_module_header = PMH_STAGE_GOT_DESCRIPTION;
|
|
break;
|
|
case PMH_STAGE_GOT_DESCRIPTION:
|
|
if (parse_quoted_string(buf, module_header_author, sizeof(module_header_author)))
|
|
parse_module_header = PMH_STAGE_GOT_AUTHOR;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
/* parse module config stuff: */
|
|
switch (parse_module_config)
|
|
{
|
|
case PMC_STAGE_LOOKING:
|
|
if (strstr(buf, "<<<MODULE MANAGER START>>>")){
|
|
module_config_start_line = linenr;
|
|
parse_module_config = PMC_STAGE_STARTED;
|
|
}
|
|
break;
|
|
case PMC_STAGE_STARTED:
|
|
if (!strstr(buf, "<<<MODULE MANAGER END>>>"))
|
|
{
|
|
strlcat(moduleconfig, buf, MODULECONFIGBUFFER);
|
|
strlcat(moduleconfig, "\n", MODULECONFIGBUFFER);
|
|
} else
|
|
{
|
|
parse_module_config = PMC_STAGE_FINISHED;
|
|
}
|
|
break;
|
|
default:
|
|
/* Nothing to be done anymore */
|
|
break;
|
|
}
|
|
}
|
|
fclose(fd);
|
|
|
|
if (!*module_header_name || !*module_header_version ||
|
|
!*module_header_description || !*module_header_author)
|
|
{
|
|
fprintf(stderr, "Error parsing module header in %s\n", modulename);
|
|
safe_free(moduleconfig);
|
|
return NULL;
|
|
}
|
|
|
|
if (strcmp(module_header_name, modulename))
|
|
{
|
|
fprintf(stderr, "ERROR: Mismatch in module name in header (%s) and filename (%s)\n",
|
|
module_header_name, modulename);
|
|
safe_free(moduleconfig);
|
|
return NULL;
|
|
}
|
|
|
|
if (!*moduleconfig)
|
|
{
|
|
fprintf(stderr, "ERROR: Module does not contain module config data (<<<MODULE MANAGER START>>>)\n"
|
|
"This means it is not meant to be managed by the module manager\n");
|
|
safe_free(moduleconfig);
|
|
return NULL;
|
|
}
|
|
|
|
/* Fill in the fields from MOD_HEADER() */
|
|
m = safe_alloc(sizeof(ManagedModule));
|
|
safe_strdup(m->name, module_header_name);
|
|
safe_strdup(m->version, module_header_version);
|
|
safe_strdup(m->description, module_header_description);
|
|
safe_strdup(m->author, module_header_author);
|
|
|
|
if (!mm_parse_module_file(m, moduleconfig, module_config_start_line))
|
|
{
|
|
fprintf(stderr, "ERROR: Problem with module manager data block within the %s module C source file.\n"
|
|
"You are suggested to contact the module author and paste the above to him/her\n",
|
|
m->name);
|
|
free_managed_module(m);
|
|
safe_free(moduleconfig);
|
|
return NULL;
|
|
}
|
|
|
|
safe_free(moduleconfig);
|
|
return m;
|
|
}
|
|
|
|
void print_documentation(void)
|
|
{
|
|
fprintf(stderr, "See https://www.unrealircd.org/docs/Module_manager for more information.\n");
|
|
}
|
|
|
|
char *mm_sourceslist_file(void)
|
|
{
|
|
static char buf1[512], buf2[512];
|
|
snprintf(buf1, sizeof(buf1), "%s/modules.sources.list", CONFDIR);
|
|
if (!file_exists(buf1))
|
|
{
|
|
/* Possibly UnrealIRCd is not installed yet, so use this one */
|
|
snprintf(buf2, sizeof(buf2), "%s/doc/conf/modules.sources.list", BUILDDIR);
|
|
if (!file_exists(buf2))
|
|
{
|
|
fprintf(stderr, "ERROR: Neither '%s' nor '%s' exist.\n"
|
|
"No module repositories configured.\n",
|
|
buf1, buf2);
|
|
print_documentation();
|
|
exit(-1);
|
|
}
|
|
return buf2;
|
|
}
|
|
return buf1;
|
|
}
|
|
|
|
/** Free a managed module struct */
|
|
void free_managed_module(ManagedModule *m)
|
|
{
|
|
safe_free(m->repo_url);
|
|
safe_free(m->name);
|
|
safe_free(m->source);
|
|
safe_free(m->sha256sum);
|
|
safe_free(m->version);
|
|
safe_free(m->author);
|
|
safe_free(m->troubleshooting);
|
|
safe_free(m->documentation);
|
|
safe_free(m->min_unrealircd_version);
|
|
safe_free(m->max_unrealircd_version);
|
|
safe_free(m->description);
|
|
freemultiline(m->post_install_text);
|
|
safe_free(m);
|
|
}
|
|
|
|
/** Check for valid module name */
|
|
int mm_valid_module_name(char *name)
|
|
{
|
|
char *p;
|
|
|
|
if (!str_starts_with_case_sensitive(name, "third/"))
|
|
return 0;
|
|
name += 6;
|
|
if (strstr(name, ".."))
|
|
return 0;
|
|
for (p = name; *p; p++)
|
|
if (!isalnum(*p) && !strchr("._-", *p))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
#undef CheckNull
|
|
#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; }
|
|
|
|
/** Parse a module { } line from a repository */
|
|
ManagedModule *mm_repo_module_config(char *repo_url, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
ManagedModule *m = safe_alloc(sizeof(ManagedModule));
|
|
|
|
if (!ce->value)
|
|
{
|
|
config_error("%s:%d: module { } with no name",
|
|
repo_url, ce->line_number);
|
|
goto fail_mm_repo_module_config;
|
|
}
|
|
if (!str_starts_with_case_sensitive(ce->value, "third/"))
|
|
{
|
|
config_error("%s:%d: module { } name must start with: third/",
|
|
repo_url, ce->line_number);
|
|
goto fail_mm_repo_module_config;
|
|
}
|
|
if (!mm_valid_module_name(ce->value))
|
|
{
|
|
config_error("%s:%d: module { } with illegal name: %s",
|
|
repo_url, ce->line_number, ce->value);
|
|
goto fail_mm_repo_module_config;
|
|
}
|
|
safe_strdup(m->name, ce->value);
|
|
safe_strdup(m->repo_url, repo_url);
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "source"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->source, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "sha256sum"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->sha256sum, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "version"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->version, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "author"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->author, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "troubleshooting"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->troubleshooting, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "documentation"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->documentation, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "min-unrealircd-version"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->min_unrealircd_version, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "max-unrealircd-version"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->max_unrealircd_version, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "description"))
|
|
{
|
|
CheckNull(cep);
|
|
safe_strdup(m->description, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "post-install-text"))
|
|
{
|
|
if (cep->items)
|
|
{
|
|
ConfigEntry *cepp;
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
addmultiline(&m->post_install_text, cepp->name);
|
|
} else {
|
|
CheckNull(cep);
|
|
addmultiline(&m->post_install_text, cep->value);
|
|
}
|
|
}
|
|
/* unknown items are silently ignored for future compatibility */
|
|
}
|
|
|
|
if (!m->source)
|
|
{
|
|
config_error("%s:%d: module::source missing", repo_url, ce->line_number);
|
|
goto fail_mm_repo_module_config;
|
|
}
|
|
if (!m->sha256sum)
|
|
{
|
|
config_error("%s:%d: module::sha256sum missing", repo_url, ce->line_number);
|
|
goto fail_mm_repo_module_config;
|
|
}
|
|
if (!m->version)
|
|
{
|
|
config_error("%s:%d: module::version missing", repo_url, ce->line_number);
|
|
goto fail_mm_repo_module_config;
|
|
}
|
|
if (!m->author)
|
|
{
|
|
config_error("%s:%d: module::author missing", repo_url, ce->line_number);
|
|
goto fail_mm_repo_module_config;
|
|
}
|
|
if (!m->documentation)
|
|
{
|
|
config_error("%s:%d: module::documentation missing", repo_url, ce->line_number);
|
|
goto fail_mm_repo_module_config;
|
|
}
|
|
if (!m->troubleshooting)
|
|
{
|
|
config_error("%s:%d: module::troubleshooting missing", repo_url, ce->line_number);
|
|
goto fail_mm_repo_module_config;
|
|
}
|
|
if (!m->min_unrealircd_version)
|
|
{
|
|
config_error("%s:%d: module::min-unrealircd-version missing", repo_url, ce->line_number);
|
|
goto fail_mm_repo_module_config;
|
|
}
|
|
/* max_unrealircd_version is optional */
|
|
if (!m->description)
|
|
{
|
|
config_error("%s:%d: module::description missing", repo_url, ce->line_number);
|
|
goto fail_mm_repo_module_config;
|
|
}
|
|
/* post_install_text is optional */
|
|
|
|
return m;
|
|
|
|
fail_mm_repo_module_config:
|
|
free_managed_module(m);
|
|
return NULL;
|
|
}
|
|
|
|
#undef CheckNull
|
|
|
|
int mm_parse_repo_db(char *url, char *filename)
|
|
{
|
|
ConfigFile *cf;
|
|
ConfigEntry *ce;
|
|
ManagedModule *m;
|
|
|
|
cf = config_load(filename, url);
|
|
if (!cf)
|
|
return 0; /* eg: parse errors */
|
|
|
|
for (ce = cf->items; ce; ce = ce->next)
|
|
{
|
|
if (!strcmp(ce->name, "module"))
|
|
{
|
|
m = mm_repo_module_config(url, ce);
|
|
if (!m)
|
|
{
|
|
config_free(cf);
|
|
return 0;
|
|
}
|
|
AddListItem(m, managed_modules);
|
|
}
|
|
}
|
|
config_free(cf);
|
|
return 1;
|
|
}
|
|
|
|
int mm_refresh_repository(void)
|
|
{
|
|
char *sourceslist = mm_sourceslist_file();
|
|
FILE *fd;
|
|
char buf[512];
|
|
char *tmpfile;
|
|
int linenr = 0;
|
|
int success = 0;
|
|
int numrepos = 0;
|
|
|
|
if (!file_exists(TMPDIR))
|
|
{
|
|
(void)mkdir(TMPDIR, S_IRUSR|S_IWUSR|S_IXUSR); /* Create the tmp dir, if it doesn't exist */
|
|
if (!file_exists(TMPDIR))
|
|
{
|
|
/* This is possible if the directory structure does not exist,
|
|
* eg if ~/unrealircd does not exist at all then ~/unrealircd/tmp
|
|
* cannot be mkdir'ed either.
|
|
*/
|
|
fprintf(stderr, "ERROR: %s does not exist (yet?), cannot use module manager\n", TMPDIR);
|
|
fprintf(stderr, " This can only happen if you did not use ./Config or if you rm -rf'ed after running ./Config.\n");
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
printf("Reading module repository list from '%s'...\n", mm_sourceslist_file());
|
|
fd = fopen(sourceslist, "r");
|
|
if (!fd)
|
|
{
|
|
fprintf(stderr, "ERROR: Could not open '%s': %s\n", sourceslist, strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
while ((fgets(buf, sizeof(buf), fd)))
|
|
{
|
|
char *line = buf;
|
|
linenr++;
|
|
stripcrlf(line);
|
|
/* Skip whitespace */
|
|
while (*line == ' ')
|
|
line++;
|
|
/* Skip empty lines and ones that start with a hash mark (#) */
|
|
if (!*line || (*line == '#'))
|
|
continue;
|
|
if (!str_starts_with_case_sensitive(line, "https://"))
|
|
{
|
|
fprintf(stderr, "ERROR in %s on line %d: URL should start with https://",
|
|
sourceslist, linenr);
|
|
fclose(fd);
|
|
return 0;
|
|
}
|
|
printf("Checking module repository %s...\n", line);
|
|
numrepos++;
|
|
tmpfile = unreal_mktemp(TMPDIR, "mm");
|
|
if (mm_http_request(line, tmpfile, 1))
|
|
{
|
|
if (!mm_parse_repo_db(line, tmpfile))
|
|
{
|
|
fclose(fd);
|
|
return 0;
|
|
}
|
|
success++;
|
|
}
|
|
}
|
|
fclose(fd);
|
|
|
|
if (numrepos == 0)
|
|
{
|
|
fprintf(stderr, "ERROR: No repositories listed in module repository list. "
|
|
"Did you remove the default UnrealIRCd repository?\n"
|
|
"All commands, except for './unrealircd module uninstall third/name-of-module', are unavailable.\n");
|
|
return 0;
|
|
}
|
|
|
|
return success ? 1 : 0;
|
|
}
|
|
|
|
#define COLUMN_STATUS 0
|
|
#define COLUMN_NAME 1
|
|
#define COLUMN_VERSION 2
|
|
|
|
void mm_list_print(char *status, char *name, char *version, char *description, int largest_column[3])
|
|
{
|
|
int padstatus = MAX(largest_column[COLUMN_STATUS] - strlen(status), 0);
|
|
int padname = MAX(largest_column[COLUMN_NAME] - strlen(name), 0);
|
|
int padversion = MAX(largest_column[COLUMN_VERSION] - strlen(version), 0);
|
|
|
|
printf("| %s%*s | %s%*s | %s%*s | %s\n",
|
|
status,
|
|
padstatus, "",
|
|
name,
|
|
padname, "",
|
|
version,
|
|
padversion, "",
|
|
description);
|
|
}
|
|
|
|
int mm_check_module_compatibility(ManagedModule *m)
|
|
{
|
|
if (strchr(m->min_unrealircd_version, '*'))
|
|
{
|
|
/* By wildcard, eg: "5.*" */
|
|
if (!match_simple(m->min_unrealircd_version, VERSIONONLY))
|
|
return 0;
|
|
} else
|
|
{
|
|
/* By strcmp, eg: "5.0.0" */
|
|
if (strnatcasecmp(m->min_unrealircd_version, VERSIONONLY) > 0)
|
|
return 0;
|
|
}
|
|
if (m->max_unrealircd_version)
|
|
{
|
|
if (strchr(m->max_unrealircd_version, '*'))
|
|
{
|
|
/* By wildcard, eg: "5.*" */
|
|
if (!match_simple(m->max_unrealircd_version, VERSIONONLY))
|
|
return 0;
|
|
} else
|
|
{
|
|
/* By strcmp, eg: "5.0.5" */
|
|
if (strnatcasecmp(m->max_unrealircd_version, VERSIONONLY) <= 0)
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#define MMMS_INSTALLED 0x0001
|
|
#define MMMS_UPGRADE_AVAILABLE 0x0002
|
|
#define MMMS_UNAVAILABLE 0x0004
|
|
|
|
int mm_get_module_status(ManagedModule *m)
|
|
{
|
|
FILE *fd;
|
|
char fname[512];
|
|
const char *our_sha256sum;
|
|
|
|
snprintf(fname, sizeof(fname), "%s/src/modules/%s.c", BUILDDIR, m->name);
|
|
if (!file_exists(fname))
|
|
{
|
|
if (!mm_check_module_compatibility(m))
|
|
return MMMS_UNAVAILABLE;
|
|
return 0;
|
|
}
|
|
|
|
our_sha256sum = sha256sum_file(fname);
|
|
if (!strcasecmp(our_sha256sum, m->sha256sum))
|
|
{
|
|
return MMMS_INSTALLED;
|
|
} else {
|
|
if (!mm_check_module_compatibility(m))
|
|
return MMMS_INSTALLED|MMMS_UNAVAILABLE;
|
|
return MMMS_INSTALLED|MMMS_UPGRADE_AVAILABLE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char *mm_get_module_status_string(ManagedModule *m)
|
|
{
|
|
int status = mm_get_module_status(m);
|
|
if (status == 0)
|
|
return "";
|
|
else if (status == MMMS_UNAVAILABLE)
|
|
return "unav";
|
|
else if (status == MMMS_INSTALLED)
|
|
return "inst";
|
|
else if (status == (MMMS_INSTALLED|MMMS_UNAVAILABLE))
|
|
return "inst/UNAV";
|
|
else if (status == (MMMS_INSTALLED|MMMS_UPGRADE_AVAILABLE))
|
|
return "inst/UPD";
|
|
return "UNKNOWN?";
|
|
}
|
|
|
|
char *mm_get_module_status_string_long(ManagedModule *m)
|
|
{
|
|
int status = mm_get_module_status(m);
|
|
if (status == 0)
|
|
return "Not installed";
|
|
else if (status == MMMS_UNAVAILABLE)
|
|
return "Unavailable for your UnrealIRCd version";
|
|
else if (status == MMMS_INSTALLED)
|
|
return "Installed and up to date";
|
|
else if (status == (MMMS_INSTALLED|MMMS_UNAVAILABLE))
|
|
return "Installed, an upgrade is available but not for your UnrealIRCd version";
|
|
else if (status == (MMMS_INSTALLED|MMMS_UPGRADE_AVAILABLE))
|
|
return "Installed, upgrade available";
|
|
return "UNKNOWN?";
|
|
}
|
|
|
|
/** Find a module by name, return NULL if not found. */
|
|
ManagedModule *mm_find_module(char *name)
|
|
{
|
|
ManagedModule *m;
|
|
|
|
for (m = managed_modules; m; m = m->next)
|
|
if (!strcasecmp(name, m->name))
|
|
return m;
|
|
return NULL;
|
|
}
|
|
|
|
/** Count the unknown modules (untracked modules) */
|
|
int count_unknown_modules(void)
|
|
{
|
|
DIR *fd;
|
|
struct dirent *dir;
|
|
int count = 0;
|
|
char dirname[512];
|
|
|
|
snprintf(dirname, sizeof(dirname), "%s/src/modules/third", BUILDDIR);
|
|
|
|
fd = opendir(dirname);
|
|
if (fd)
|
|
{
|
|
while ((dir = readdir(fd)))
|
|
{
|
|
char *fname = dir->d_name;
|
|
if (filename_has_suffix(fname, ".c"))
|
|
{
|
|
char modname[512], *p;
|
|
snprintf(modname, sizeof(modname), "third/%s", filename_strip_suffix(fname, ".c"));
|
|
if (!mm_find_module(modname))
|
|
count++;
|
|
}
|
|
}
|
|
closedir(fd);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void mm_list(char *searchname)
|
|
{
|
|
ManagedModule *m;
|
|
int largest_column[3];
|
|
int padname;
|
|
int padversion;
|
|
struct dirent *dir;
|
|
DIR *fd;
|
|
char dirname[512];
|
|
char *status;
|
|
int first_unknown = 1;
|
|
|
|
if (searchname)
|
|
printf("Searching for '%s' in names of all available modules...\n", searchname);
|
|
|
|
memset(&largest_column, 0, sizeof(largest_column));
|
|
largest_column[COLUMN_STATUS] = strlen("inst/UNAV");
|
|
largest_column[COLUMN_NAME] = strlen("Name:");
|
|
largest_column[COLUMN_VERSION] = strlen("Version:");
|
|
|
|
for (m = managed_modules; m; m = m->next)
|
|
{
|
|
if (strlen(m->name) > largest_column[COLUMN_NAME])
|
|
largest_column[COLUMN_NAME] = strlen(m->name);
|
|
if (strlen(m->version) > largest_column[COLUMN_VERSION])
|
|
largest_column[COLUMN_VERSION] = strlen(m->version);
|
|
}
|
|
/* We try to produce neat output, but not at all costs */
|
|
if (largest_column[COLUMN_NAME] > 32)
|
|
largest_column[COLUMN_NAME] = 32;
|
|
if (largest_column[COLUMN_VERSION] > 16)
|
|
largest_column[COLUMN_VERSION] = 16;
|
|
|
|
mm_list_print("Status:", "Name:", "Version:", "Description:", largest_column);
|
|
printf("|=======================================================================================\n");
|
|
|
|
for (m = managed_modules; m; m = m->next)
|
|
{
|
|
if (searchname && !strstr(m->name, searchname))
|
|
continue;
|
|
|
|
status = mm_get_module_status_string(m);
|
|
mm_list_print(status, m->name, m->version, m->description, largest_column);
|
|
}
|
|
|
|
snprintf(dirname, sizeof(dirname), "%s/src/modules/third", BUILDDIR);
|
|
fd = opendir(dirname);
|
|
if (fd)
|
|
{
|
|
while ((dir = readdir(fd)))
|
|
{
|
|
char *fname = dir->d_name;
|
|
if (filename_has_suffix(fname, ".c"))
|
|
{
|
|
char modname[512], *p;
|
|
snprintf(modname, sizeof(modname), "third/%s", filename_strip_suffix(fname, ".c"));
|
|
if (searchname && !strstr(searchname, modname))
|
|
continue;
|
|
if (!mm_find_module(modname))
|
|
{
|
|
if (first_unknown)
|
|
{
|
|
printf("|---------------------------------------------------------------------------------------\n");
|
|
first_unknown = 0;
|
|
}
|
|
mm_list_print("UNKNOWN", modname, "", "", largest_column);
|
|
}
|
|
}
|
|
}
|
|
closedir(fd);
|
|
}
|
|
|
|
printf("|=======================================================================================\n");
|
|
|
|
printf("\nStatus column legend:\n"
|
|
" : not installed\n"
|
|
"inst : module installed\n"
|
|
"inst/UPD : module installed, upgrade available (latest version differs from yours)\n"
|
|
"unav : module not available for your UnrealIRCd version\n"
|
|
"inst/UNAV : module installed, upgrade exists but is not available for your UnrealIRCd version (too old UnrealIRCd version?)\n"
|
|
"UNKNOWN : module does not exist in any repository (perhaps you installed it manually?), module will be left untouched\n");
|
|
|
|
printf("\nFor more information about a particular module, use './unrealircd module info name-of-module'\n\n");
|
|
print_documentation();
|
|
}
|
|
|
|
int mm_compile(ManagedModule *m, char *tmpfile, int test)
|
|
{
|
|
char newpath[512];
|
|
char cmd[512];
|
|
const char *basename;
|
|
char *p;
|
|
FILE *fd;
|
|
char buf[512];
|
|
int n;
|
|
|
|
if (test)
|
|
printf("Test compiling %s...\n", m->name);
|
|
else
|
|
printf("Compiling %s...\n", m->name);
|
|
|
|
basename = unreal_getfilename(test ? tmpfile : m->name);
|
|
snprintf(newpath, sizeof(newpath), "%s/src/modules/third/%s%s", BUILDDIR, basename, test ? "" : ".c");
|
|
if (!test)
|
|
{
|
|
/* If the file already exists then we are upgrading.
|
|
* It's a good idea to backup the file rather than
|
|
* just delete it. Perhaps the user had local changes
|
|
* he/she wished to preserve and accidently went
|
|
* through the upgrade procedure.
|
|
*/
|
|
char backupfile[512];
|
|
snprintf(backupfile, sizeof(backupfile), "%s.bak", newpath);
|
|
unlink(backupfile);
|
|
(void)rename(newpath, backupfile);
|
|
}
|
|
if (!unreal_copyfileex(tmpfile, newpath, 0))
|
|
return 0;
|
|
|
|
snprintf(cmd, sizeof(cmd),
|
|
"cd \"%s\"; $MAKE custommodule MODULEFILE=\"%s\"",
|
|
BUILDDIR,
|
|
filename_strip_suffix(basename, ".c")
|
|
);
|
|
fd = popen(cmd, "r");
|
|
if (!fd)
|
|
{
|
|
fprintf(stderr, "ERROR: Could not issue command: %s\n", cmd);
|
|
unlink(newpath);
|
|
return 0;
|
|
}
|
|
while((fgets(buf, sizeof(buf), fd)))
|
|
{
|
|
printf("%s", buf);
|
|
}
|
|
n = pclose(fd);
|
|
|
|
if (test)
|
|
{
|
|
/* Remove the XXXXXXX.modname.c file */
|
|
unlink(newpath);
|
|
/* Remove the XXXXXXX.modname.so file */
|
|
newpath[strlen(newpath)-2] = '\0'; // cut off .c
|
|
strlcat(newpath, ".so", sizeof(newpath));
|
|
unlink(newpath);
|
|
}
|
|
|
|
if (WIFEXITED(n) && (WEXITSTATUS(n) == 0))
|
|
return 1;
|
|
|
|
fprintf(stderr, "ERROR: Compile errors encountered while compiling module '%s'\n"
|
|
"You are suggested to contact the author (%s) of this module:\n%s\n",
|
|
m->name, m->author, m->troubleshooting);
|
|
return 0;
|
|
}
|
|
|
|
/** Actually download and install the module.
|
|
* This assumes compatibility checks have already been done.
|
|
*/
|
|
void mm_install_module(ManagedModule *m)
|
|
{
|
|
const char *basename = unreal_getfilename(m->source);
|
|
char *tmpfile;
|
|
const char *sha256;
|
|
|
|
if (!basename)
|
|
basename = "mod.c";
|
|
tmpfile = unreal_mktemp(TMPDIR, basename);
|
|
|
|
printf("Downloading %s from %s...\n", m->name, m->source);
|
|
if (!mm_http_request(m->source, tmpfile, 1))
|
|
{
|
|
fprintf(stderr, "Repository %s seems to list a module file that cannot be retrieved (%s).\n", m->repo_url, m->source);
|
|
fprintf(stderr, "Fatal error encountered. Contact %s: %s\n", m->author, m->troubleshooting);
|
|
exit(-1);
|
|
}
|
|
|
|
sha256 = sha256sum_file(tmpfile);
|
|
if (!sha256)
|
|
{
|
|
fprintf(stderr, "ERROR: Temporary file '%s' has disappeared -- strange\n", tmpfile);
|
|
fprintf(stderr, "Fatal error encountered. Check for errors above. Perhaps try running the command again?\n");
|
|
exit(-1);
|
|
}
|
|
if (strcasecmp(sha256, m->sha256sum))
|
|
{
|
|
fprintf(stderr, "ERROR: SHA256 Checksum mismatch\n"
|
|
"Expected (value in repository list): %s\n"
|
|
"Received (value of downloaded file): %s\n",
|
|
m->sha256sum, sha256);
|
|
fprintf(stderr, "Fatal error encountered, see above. Try running the command again in 5-10 minutes.\n"
|
|
"If the issue persists, contact the repository manager of %s\n",
|
|
m->repo_url);
|
|
exit(-1);
|
|
}
|
|
if (!mm_compile(m, tmpfile, 1))
|
|
{
|
|
fprintf(stderr, "Fatal error encountered, see above.\n");
|
|
exit(-1);
|
|
}
|
|
|
|
if (!mm_compile(m, tmpfile, 0))
|
|
{
|
|
fprintf(stderr, "The test compile went OK earlier, but the final compile did not. BAD!!\n");
|
|
exit(-1);
|
|
}
|
|
printf("Module %s compiled successfully\n", m->name);
|
|
}
|
|
|
|
/** Uninstall a module.
|
|
* This function takes a string rather than a ManagedModule
|
|
* because it also allows uninstalling of unmanaged (local) modules.
|
|
*/
|
|
void mm_uninstall_module(char *modulename)
|
|
{
|
|
struct dirent *dir;
|
|
DIR *fd;
|
|
char dirname[512], fullname[512];
|
|
int found = 0;
|
|
|
|
snprintf(dirname, sizeof(dirname), "%s/src/modules/third", BUILDDIR);
|
|
fd = opendir(dirname);
|
|
if (fd)
|
|
{
|
|
while ((dir = readdir(fd)))
|
|
{
|
|
char *fname = dir->d_name;
|
|
if (filename_has_suffix(fname, ".c") || filename_has_suffix(fname, ".so") || filename_has_suffix(fname, ".dll"))
|
|
{
|
|
char modname[512], *p;
|
|
snprintf(modname, sizeof(modname), "third/%s", filename_strip_suffix(fname, NULL));
|
|
if (!strcasecmp(modname, modulename))
|
|
{
|
|
found = 1;
|
|
snprintf(fullname, sizeof(fullname), "%s/%s", dirname, fname);
|
|
//printf("Deleting '%s'\n", fullname);
|
|
unlink(fullname);
|
|
}
|
|
}
|
|
}
|
|
closedir(fd);
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
fprintf(stderr, "ERROR: Module '%s' is not installed, so can't uninstall.\n", modulename);
|
|
exit(-1);
|
|
}
|
|
|
|
printf("Module '%s' uninstalled successfully\n", modulename);
|
|
}
|
|
|
|
void mm_make_install(void)
|
|
{
|
|
char cmd[512];
|
|
int n;
|
|
if (no_make_install)
|
|
return;
|
|
printf("Running 'make install'...\n");
|
|
snprintf(cmd, sizeof(cmd), "cd \"%s\"; $MAKE install 1>/dev/null 2>&1", BUILDDIR);
|
|
n = system(cmd);
|
|
}
|
|
|
|
void mm_install(int argc, char *args[], int upgrade)
|
|
{
|
|
ManagedModule *m;
|
|
MultiLine *l;
|
|
char *name = args[1];
|
|
int status;
|
|
|
|
if (!name)
|
|
{
|
|
fprintf(stderr, "ERROR: Use: module install third/name-of-module\n");
|
|
exit(-1);
|
|
}
|
|
|
|
if (!str_starts_with_case_sensitive(name, "third/"))
|
|
{
|
|
fprintf(stderr, "ERROR: Use: module install third/name-of-module\nYou must prefix the modulename with third/\n");
|
|
exit(-1);
|
|
}
|
|
|
|
m = mm_find_module(name);
|
|
if (!m)
|
|
{
|
|
fprintf(stderr, "ERROR: Module '%s' not found\n", name);
|
|
exit(-1);
|
|
}
|
|
status = mm_get_module_status(m);
|
|
if (status == MMMS_UNAVAILABLE)
|
|
{
|
|
fprintf(stderr, "ERROR: Module '%s' exists, but is not compatible with your UnrealIRCd version:\n"
|
|
"Your UnrealIRCd version : %s\n"
|
|
"Minimum version required : %s\n",
|
|
name,
|
|
VERSIONONLY,
|
|
m->min_unrealircd_version);
|
|
if (m->max_unrealircd_version)
|
|
fprintf(stderr, "Maximum version : %s\n", m->max_unrealircd_version);
|
|
exit(-1);
|
|
}
|
|
if (upgrade && (status == MMMS_INSTALLED))
|
|
{
|
|
/* If updating, and we are already on latest version, then don't upgrade */
|
|
printf("Module %s is the latest version, no upgrade needed\n", m->name);
|
|
}
|
|
mm_install_module(m);
|
|
mm_make_install();
|
|
if (m->post_install_text)
|
|
{
|
|
printf("Post-installation information for %s from the author:\n", m->name);
|
|
printf("---\n");
|
|
for (l = m->post_install_text; l; l = l->next)
|
|
printf(" %s\n", l->line);
|
|
printf("---\n");
|
|
} else {
|
|
printf("Don't forget to add a 'loadmodule' line for the module and rehash\n");
|
|
}
|
|
}
|
|
|
|
void mm_uninstall(int argc, char *args[])
|
|
{
|
|
ManagedModule *m;
|
|
char *name = args[1];
|
|
|
|
if (!name)
|
|
{
|
|
fprintf(stderr, "ERROR: Use: module uninstall third/name-of-module\n");
|
|
exit(-1);
|
|
}
|
|
|
|
if (!str_starts_with_case_sensitive(name, "third/"))
|
|
{
|
|
fprintf(stderr, "ERROR: Use: module uninstall third/name-of-module\nYou must prefix the modulename with third/\n");
|
|
exit(-1);
|
|
}
|
|
|
|
mm_uninstall_module(name);
|
|
mm_make_install();
|
|
exit(0);
|
|
}
|
|
|
|
void mm_upgrade(int argc, char *args[])
|
|
{
|
|
ManagedModule *m;
|
|
char *name = args[1];
|
|
int upgraded = 0;
|
|
int uptodate_already = 0;
|
|
int update_unavailable = 0;
|
|
int i = 1, n;
|
|
|
|
if (args[i] && !strcmp(args[i], "--no-install"))
|
|
{
|
|
no_make_install = 1;
|
|
i++;
|
|
}
|
|
|
|
name = args[i];
|
|
if (name)
|
|
{
|
|
// TODO: First check if it needs an upgrade? ;)
|
|
mm_install(argc, args, 1);
|
|
exit(0);
|
|
}
|
|
|
|
/* Without arguments means: check all installed modules */
|
|
no_make_install = 1;
|
|
for (m = managed_modules; m; m = m->next)
|
|
{
|
|
int status = mm_get_module_status(m);
|
|
if (status == (MMMS_INSTALLED|MMMS_UPGRADE_AVAILABLE))
|
|
{
|
|
args[1] = m->name;
|
|
mm_install(1, args, 1);
|
|
upgraded++;
|
|
} else
|
|
if (status == MMMS_INSTALLED)
|
|
{
|
|
uptodate_already++;
|
|
} else
|
|
if (status == (MMMS_INSTALLED|MMMS_UNAVAILABLE))
|
|
{
|
|
update_unavailable++;
|
|
}
|
|
}
|
|
no_make_install = 0;
|
|
if (upgraded)
|
|
mm_make_install();
|
|
printf("All actions were successful. %d module(s) upgraded, %d already up-to-date\n",
|
|
upgraded, uptodate_already);
|
|
if (update_unavailable)
|
|
printf("%d module(s) have updates but not for your UnrealIRCd version\n", update_unavailable);
|
|
if ((n = count_unknown_modules()))
|
|
printf("%d module(s) are unknown/untracked\n", n);
|
|
|
|
printf("For more details, you can always run ./unrealircd module list\n");
|
|
}
|
|
|
|
void mm_info(int argc, char *args[])
|
|
{
|
|
ManagedModule *m;
|
|
MultiLine *l;
|
|
char *name = args[1];
|
|
|
|
if (!name)
|
|
{
|
|
fprintf(stderr, "ERROR: Use: unrealircd module info name-of-module\n");
|
|
exit(-1);
|
|
}
|
|
|
|
m = mm_find_module(name);
|
|
if (!m)
|
|
{
|
|
// TODO: we should probably be a bit more specific if the module exists locally (UNAV) */
|
|
fprintf(stderr, "ERROR: Module '%s' not found in any repository\n", name);
|
|
exit(-1);
|
|
}
|
|
printf("Name: %s\n"
|
|
"Version: %s\n"
|
|
"Description: %s\n"
|
|
"Author: %s\n"
|
|
"Documentation: %s\n"
|
|
"Troubleshooting: %s\n"
|
|
"Source: %s\n"
|
|
"Min. UnrealIRCd version: %s\n",
|
|
m->name,
|
|
m->version,
|
|
m->description,
|
|
m->author,
|
|
m->documentation,
|
|
m->troubleshooting,
|
|
m->source,
|
|
m->min_unrealircd_version);
|
|
if (m->max_unrealircd_version)
|
|
printf("Min. UnrealIRCd version: %s\n", m->max_unrealircd_version);
|
|
printf("Status: %s\n", mm_get_module_status_string_long(m));
|
|
if (m->post_install_text)
|
|
{
|
|
printf("------ Post-installation text ------\n");
|
|
for (l = m->post_install_text; l; l = l->next)
|
|
printf(" %s\n", l->line);
|
|
printf("------ End of post-install text ------\n");
|
|
}
|
|
}
|
|
|
|
void mm_usage(void)
|
|
{
|
|
fprintf(stderr, "Use any of the following actions:\n"
|
|
"unrealircd module list List all the available and installed modules\n"
|
|
"unrealircd module info name-of-module Show more information about the module\n"
|
|
"unrealircd module install name-of-module Install the specified module\n"
|
|
"unrealircd module uninstall name-of-module Uninstall the specified module\n"
|
|
"unrealircd module upgrade name-of-module Upgrade the specified module (if needed)\n"
|
|
"unrealircd module upgrade Upgrade all modules (if needed)\n"
|
|
"unrealircd module generate-repository Generate a repository index (you are\n"
|
|
" unlikely to need this, only for repo admins)\n");
|
|
print_documentation();
|
|
exit(-1);
|
|
}
|
|
|
|
void print_md_block(FILE *fdo, ManagedModule *m)
|
|
{
|
|
fprintf(fdo, "module \"%s\"\n{\n", m->name);
|
|
fprintf(fdo, "\tdescription \"%s\";\n", unreal_add_quotes(m->description));
|
|
fprintf(fdo, "\tversion \"%s\";\n", unreal_add_quotes(m->version));
|
|
fprintf(fdo, "\tauthor \"%s\";\n", unreal_add_quotes(m->author));
|
|
fprintf(fdo, "\tdocumentation \"%s\";\n", unreal_add_quotes(m->documentation));
|
|
fprintf(fdo, "\ttroubleshooting \"%s\";\n", unreal_add_quotes(m->troubleshooting));
|
|
fprintf(fdo, "\tsource \"%s\";\n", unreal_add_quotes(m->source));
|
|
fprintf(fdo, "\tsha256sum \"%s\";\n", unreal_add_quotes(m->sha256sum));
|
|
fprintf(fdo, "\tmin-unrealircd-version \"%s\";\n", unreal_add_quotes(m->min_unrealircd_version));
|
|
if (m->max_unrealircd_version)
|
|
fprintf(fdo, "\tmax-unrealircd-version \"%s\";\n", unreal_add_quotes(m->max_unrealircd_version));
|
|
if (m->post_install_text)
|
|
{
|
|
MultiLine *l;
|
|
fprintf(fdo, "\tpost-install-text\n"
|
|
"\t{\n");
|
|
for (l = m->post_install_text; l; l = l->next)
|
|
fprintf(fdo, "\t\t\"%s\";\n", unreal_add_quotes(l->line));
|
|
fprintf(fdo, "\t}\n");
|
|
}
|
|
fprintf(fdo, "}\n\n");
|
|
}
|
|
|
|
void mm_generate_repository_usage(void)
|
|
{
|
|
fprintf(stderr, "Usage: ./unrealircd module generate-repository <url base path> <directory-with-modules> <name of output file> [optional-minimum-version-filter]\n");
|
|
fprintf(stderr, "For example: ./unrealircd module generate-repository https://www.unrealircd.org/modules/ src/modules/third modules.lst\n");
|
|
}
|
|
|
|
void mm_generate_repository(int argc, char *args[])
|
|
{
|
|
DIR *fd;
|
|
struct dirent *dir;
|
|
int count = 0;
|
|
char *urlbasepath;
|
|
char *dirname;
|
|
char *outputfile;
|
|
char *minversion;
|
|
char modname[128];
|
|
char fullname[512];
|
|
ManagedModule *m;
|
|
FILE *fdo;
|
|
|
|
urlbasepath = args[1];
|
|
dirname = args[2];
|
|
outputfile = args[3];
|
|
minversion = args[4];
|
|
|
|
if (!urlbasepath || !dirname || !outputfile)
|
|
{
|
|
mm_generate_repository_usage();
|
|
exit(-1);
|
|
}
|
|
|
|
if ((strlen(urlbasepath) < 2) || (urlbasepath[strlen(urlbasepath)-1] != '/'))
|
|
{
|
|
fprintf(stderr, "Error: the URL base path must end with a slash\n");
|
|
mm_generate_repository_usage();
|
|
exit(-1);
|
|
}
|
|
|
|
fd = opendir(dirname);
|
|
if (!fd)
|
|
{
|
|
fprintf(stderr, "Cannot open directory '%s': %s\n", dirname, strerror(errno));
|
|
exit(-1);
|
|
}
|
|
|
|
fdo = fopen(outputfile, "w");
|
|
if (!fdo)
|
|
{
|
|
fprintf(stderr, "Could not open file '%s' for writing: %s\n", outputfile, strerror(errno));
|
|
exit(-1);
|
|
}
|
|
|
|
while ((dir = readdir(fd)))
|
|
{
|
|
char *fname = dir->d_name;
|
|
if (filename_has_suffix(fname, ".c"))
|
|
{
|
|
int hide = 0;
|
|
snprintf(fullname, sizeof(fullname), "%s/%s", dirname, fname);
|
|
snprintf(modname, sizeof(modname), "third/%s", filename_strip_suffix(fname, ".c"));
|
|
printf("Processing: %s\n", modname);
|
|
m = mm_parse_module_c_file(modname, fullname);
|
|
if (!m)
|
|
{
|
|
fprintf(stderr, "WARNING: Skipping module '%s' due to errors\n", modname);
|
|
continue;
|
|
}
|
|
m->sha256sum = strdup(sha256sum_file(fullname));
|
|
m->source = safe_alloc(512);
|
|
snprintf(m->source, 512, "%s%s.c", urlbasepath, modname + 6);
|
|
/* filter */
|
|
if (minversion && m->min_unrealircd_version && strncmp(minversion, m->min_unrealircd_version, strlen(minversion)))
|
|
hide = 1;
|
|
/* /filter */
|
|
if (!hide)
|
|
print_md_block(fdo, m);
|
|
free_managed_module(m);
|
|
m = NULL;
|
|
}
|
|
}
|
|
closedir(fd);
|
|
fclose(fdo);
|
|
}
|
|
|
|
void mm_parse_c_file(int argc, char *args[])
|
|
{
|
|
char *fullname = args[1];
|
|
const char *basename;
|
|
char modname[256];
|
|
ManagedModule *m;
|
|
|
|
if (!fullname)
|
|
{
|
|
fprintf(stderr, "Usage: ./unrealircd module parse-c-file path/to/file.c\n");
|
|
exit(-1);
|
|
}
|
|
if (!file_exists(fullname))
|
|
{
|
|
fprintf(stderr, "ERROR: Unable to open C file '%s'\n", fullname);
|
|
exit(-1);
|
|
}
|
|
basename = unreal_getfilename(fullname);
|
|
|
|
snprintf(modname, sizeof(modname), "third/%s", filename_strip_suffix(basename, ".c"));
|
|
printf("Processing: %s\n", modname);
|
|
m = mm_parse_module_c_file(modname, fullname);
|
|
if (!m)
|
|
{
|
|
fprintf(stderr, "Errors encountered. See above\n");
|
|
exit(-1);
|
|
}
|
|
m->sha256sum = strdup(sha256sum_file(fullname));
|
|
m->source = strdup("...");
|
|
print_md_block(stdout, m);
|
|
free_managed_module(m);
|
|
exit(0);
|
|
}
|
|
|
|
int mm_detect_make_is_gmake(void)
|
|
{
|
|
FILE *fd;
|
|
char buf[512], *s;
|
|
|
|
fd = popen("$MAKE --version 2>&1", "r");
|
|
if (fd)
|
|
{
|
|
*buf = '\0';
|
|
s = fgets(buf, sizeof(buf), fd);
|
|
pclose(fd);
|
|
if (s && strstr(s, "GNU Make"))
|
|
return 1; /* Good! We are done. */
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void mm_detect_make(void)
|
|
{
|
|
FILE *fd;
|
|
char *s;
|
|
char buf[512];
|
|
|
|
/* Get or set $MAKE */
|
|
s = getenv("MAKE");
|
|
if (!s)
|
|
setenv("MAKE", "make", 1);
|
|
|
|
if (mm_detect_make_is_gmake())
|
|
return;
|
|
|
|
/* Try again with MAKE=gmake */
|
|
setenv("MAKE", "gmake", 1);
|
|
if (mm_detect_make_is_gmake())
|
|
return;
|
|
|
|
fprintf(stderr, "ERROR: GNU Make is not found as 'make' or 'gmake'\n");
|
|
exit(-1);
|
|
}
|
|
|
|
void mm_self_test(void)
|
|
{
|
|
char name[512];
|
|
|
|
if (!file_exists(BUILDDIR))
|
|
{
|
|
fprintf(stderr, "ERROR: Directory %s does not exist.\n"
|
|
"The UnrealIRCd source is required for the module manager to work!\n",
|
|
BUILDDIR);
|
|
exit(-1);
|
|
} else {
|
|
snprintf(name, sizeof(name), "%s/src/modules/third/Makefile", BUILDDIR);
|
|
if (!file_exists(name))
|
|
{
|
|
fprintf(stderr, "ERROR: Directory %s exists, but your UnrealIRCd is not compiled yet.\n"
|
|
"You must compile your UnrealIRCd first (run './Config', then 'make install')\n",
|
|
BUILDDIR);
|
|
exit(-1);
|
|
}
|
|
}
|
|
mm_detect_make();
|
|
}
|
|
|
|
void modulemanager(int argc, char *args[])
|
|
{
|
|
if (!args[0])
|
|
mm_usage();
|
|
|
|
mm_self_test();
|
|
|
|
/* The following operations do not require reading
|
|
* of the repository list and are always available:
|
|
*/
|
|
if (!strcasecmp(args[0], "uninstall") ||
|
|
!strcasecmp(args[0], "remove"))
|
|
{
|
|
mm_uninstall(argc, args);
|
|
exit(0);
|
|
}
|
|
else if (!strcasecmp(args[0], "generate-repository"))
|
|
{
|
|
mm_generate_repository(argc, args);
|
|
exit(0);
|
|
}
|
|
else if (!strcasecmp(args[0], "parse-c-file"))
|
|
{
|
|
mm_parse_c_file(argc, args);
|
|
exit(0);
|
|
}
|
|
|
|
/* Fetch the repository list */
|
|
if (!mm_refresh_repository())
|
|
{
|
|
fprintf(stderr, "Fatal error encountered\n");
|
|
exit(-1);
|
|
}
|
|
|
|
if (!strcasecmp(args[0], "list"))
|
|
mm_list(args[1]);
|
|
else if (!strcasecmp(args[0], "info"))
|
|
mm_info(argc, args);
|
|
else if (!strcasecmp(args[0], "install"))
|
|
{
|
|
mm_install(argc, args, 0);
|
|
fprintf(stderr, "All actions were successful.\n");
|
|
}
|
|
else if (!strcasecmp(args[0], "upgrade"))
|
|
mm_upgrade(argc, args);
|
|
else
|
|
mm_usage();
|
|
}
|
|
#endif
|