pissircd/src/modulemanager.c

1523 lines
40 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_TRANSFER_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);
#define safe_free_managed_module(x) do { free_managed_module(x); x = NULL; } while(0)
void free_managed_module(ManagedModule *m);
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);
safe_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:
safe_free_managed_module(m);
return NULL;
}
#undef CheckNull
int mm_parse_repo_db(char *url, const 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];
const 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 = synchronous_http_request(line, 1, MODULEMANAGER_CONNECT_TIMEOUT, MODULEMANAGER_TRANSFER_TIMEOUT);
if (tmpfile)
{
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, const 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.
*/
int mm_install_module(ManagedModule *m)
{
const char *sha256;
const char *tmpfile;
printf("Downloading %s from %s...\n", m->name, m->source);
tmpfile = synchronous_http_request(m->source, 1, MODULEMANAGER_CONNECT_TIMEOUT, MODULEMANAGER_TRANSFER_TIMEOUT);
if (!tmpfile)
{
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);
return 0;
}
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");
return 0;
}
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);
return 0;
}
if (!mm_compile(m, tmpfile, 1))
{
fprintf(stderr, "Fatal error encountered, see above.\n");
return 0;
}
if (!mm_compile(m, tmpfile, 0))
{
fprintf(stderr, "The test compile went OK earlier, but the final compile did not. BAD!!\n");
return 0;
}
printf("Module %s compiled successfully\n", m->name);
return 1;
}
/** 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);
}
int 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");
return 0;
}
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");
return 0;
}
m = mm_find_module(name);
if (!m)
{
fprintf(stderr, "ERROR: Module '%s' not found\n", name);
return 0;
}
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);
return 0;
}
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);
}
if (!mm_install_module(m))
return 0;
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");
}
return 1;
}
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 failed = 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;
if (mm_install(1, args, 1))
upgraded++;
else
failed++;
} 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("\n\n");
if (failed)
{
printf("There was %d FAILED module upgrade. %d module(s) upgraded, %d already up-to-date\n",
failed, upgraded, uptodate_already);
} else {
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);
safe_free_managed_module(m);
}
}
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);
safe_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"))
{
if (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