From dfa83aa6e5609947e39b6e2c0b2122d22a14842d Mon Sep 17 00:00:00 2001 From: Bram Matthys Date: Mon, 28 Oct 2019 13:15:17 +0100 Subject: [PATCH] Add module manager. See https://www.unrealircd.org/docs/Module_manager Also update release notes and some unrelated changes. BIG commits. Lots of work. Requires more testing. --- Makefile.in | 15 +- configure | 27 +- configure.ac | 11 +- doc/RELEASE-NOTES.md | 15 +- doc/conf/modules.sources.list | 21 + include/h.h | 17 +- include/setup.h.in | 3 + include/struct.h | 9 + include/sys.h | 2 +- src/Makefile.in | 10 +- src/buildmod | 9 +- src/conf.c | 67 +- src/crashreport.c | 4 +- src/ircd.c | 5 + src/misc.c | 109 +++ src/modulemanager.c | 1644 +++++++++++++++++++++++++++++++++ src/modules/authprompt.c | 30 - src/send.c | 6 + src/support.c | 13 + src/updconf.c | 55 +- unrealircd.in | 20 +- 21 files changed, 1983 insertions(+), 109 deletions(-) create mode 100644 doc/conf/modules.sources.list create mode 100644 src/modulemanager.c diff --git a/Makefile.in b/Makefile.in index 70e9da0c3..85f979958 100644 --- a/Makefile.in +++ b/Makefile.in @@ -171,6 +171,9 @@ install: all $(INSTALL) -m 0700 -d @CONFDIR@ $(INSTALL) -m 0600 doc/conf/*.default.conf @CONFDIR@ $(INSTALL) -m 0600 doc/conf/*.optional.conf @CONFDIR@ + -@if [ ! -f "@CONFDIR@/modules.sources.list" ] ; then \ + $(INSTALL) -m 0600 doc/conf/modules.sources.list @CONFDIR@ ; \ + fi -@if [ ! -f "@CONFDIR@/spamfilter.conf" ] ; then \ $(INSTALL) -m 0600 doc/conf/spamfilter.conf @CONFDIR@ ; \ fi @@ -189,14 +192,19 @@ install: all $(INSTALL) -m 0600 doc/conf/examples/*.conf @CONFDIR@/examples $(INSTALL) -m 0700 unrealircd @SCRIPTDIR@ $(INSTALL) -m 0700 -d @MODULESDIR@ + @rm -f @MODULESDIR@/*.so 1>/dev/null 2>&1 $(INSTALL) -m 0700 src/modules/*.so @MODULESDIR@ $(INSTALL) -m 0700 -d @MODULESDIR@/usermodes + @rm -f @MODULESDIR@/usermodes/*.so 1>/dev/null 2>&1 $(INSTALL) -m 0700 src/modules/usermodes/*.so @MODULESDIR@/usermodes $(INSTALL) -m 0700 -d @MODULESDIR@/chanmodes + @rm -f @MODULESDIR@/chanmodes/*.so 1>/dev/null 2>&1 $(INSTALL) -m 0700 src/modules/chanmodes/*.so @MODULESDIR@/chanmodes $(INSTALL) -m 0700 -d @MODULESDIR@/snomasks + @rm -f @MODULESDIR@/snomasks/*.so 1>/dev/null 2>&1 $(INSTALL) -m 0700 src/modules/snomasks/*.so @MODULESDIR@/snomasks $(INSTALL) -m 0700 -d @MODULESDIR@/extbans + @rm -f @MODULESDIR@/extbans/*.so 1>/dev/null 2>&1 $(INSTALL) -m 0700 src/modules/extbans/*.so @MODULESDIR@/extbans @#If the conf/ssl directory exists then rename it here to conf/tls @#and add a symlink for backwards compatibility (so that f.e. certbot @@ -210,10 +218,9 @@ install: all @# delete modules/cap directory, to avoid confusing with U4 to U5 upgrades: rm -rf @MODULESDIR@/cap $(INSTALL) -m 0700 -d @MODULESDIR@/third - @#Ugly stuff to detect 0 files in this directory: - @+for f in src/modules/third/*.so; do \ - [ -e $f ] && $(INSTALL) -m 0700 src/modules/third/*.so @MODULESDIR@/third || echo; \ - done + @rm -f @MODULESDIR@/third/*.so 1>/dev/null 2>&1 + @#This step can fail with zero files, so we ignore exit status: + -$(INSTALL) -m 0700 src/modules/third/*.so @MODULESDIR@/third $(INSTALL) -m 0700 -d @TMPDIR@ $(INSTALL) -m 0700 -d @CACHEDIR@ $(INSTALL) -m 0700 -d @PERMDATADIR@ diff --git a/configure b/configure index abb146998..4203debd9 100755 --- a/configure +++ b/configure @@ -659,6 +659,7 @@ MODULESDIR CONFDIR SCRIPTDIR BINDIR +BUILDDIR DYNAMIC_LDFLAGS MODULEFLAGS CRYPTOLIB @@ -685,7 +686,6 @@ OPENSSLPATH TOUCH CP RM -BUILDDIR target_alias host_alias build_alias @@ -734,6 +734,7 @@ with_permissions with_bindir with_scriptdir with_confdir +with_builddir with_modulesdir with_logdir with_cachedir @@ -1426,6 +1427,7 @@ Optional Packages: script --with-confdir=path Specify the directory where configuration files are stored + --with-builddir=path Specify the build directory --with-modulesdir=path Specify the directory for loadable modules --with-logdir=path Specify the directory where log files are stored --with-cachedir=path Specify the directory where cached files are stored @@ -2300,8 +2302,7 @@ fi orig_cflags="$CFLAGS" -BUILDDIR="`pwd`" - +BUILDDIR_NOW="`pwd`" # Generation version number (e.g.: X in X.Y.Z) UNREAL_VERSION_GENERATION="5" @@ -5894,6 +5895,25 @@ fi +# Check whether --with-builddir was given. +if test "${with_builddir+set}" = set; then : + withval=$with_builddir; +cat >>confdefs.h <<_ACEOF +#define BUILDDIR "$withval" +_ACEOF + + BUILDDIR="$withval" +else + +cat >>confdefs.h <<_ACEOF +#define BUILDDIR "$BUILDDIR_NOW" +_ACEOF + + BUILDDIR="$BUILDDIR_NOW" +fi + + + # Check whether --with-modulesdir was given. if test "${with_modulesdir+set}" = set; then : withval=$with_modulesdir; @@ -6067,6 +6087,7 @@ fi + # Check whether --with-maxconnections was given. if test "${with_maxconnections+set}" = set; then : withval=$with_maxconnections; ac_fd=$withval diff --git a/configure.ac b/configure.ac index 1deaf6f37..0bd378a57 100644 --- a/configure.ac +++ b/configure.ac @@ -22,8 +22,7 @@ dnl Save CFLAGS, use this when building the libraries like c-ares orig_cflags="$CFLAGS" dnl Save build directory early on (used in our m4 macros too) -BUILDDIR="`pwd`" -AC_SUBST(BUILDDIR) +BUILDDIR_NOW="`pwd`" dnl Calculate the versions. Perhaps the use of expr is a little too extravagant # Generation version number (e.g.: X in X.Y.Z) @@ -384,6 +383,13 @@ AC_ARG_WITH(confdir, [AS_HELP_STRING([--with-confdir=path],[Specify the director [AC_DEFINE_UNQUOTED([CONFDIR], ["$HOME/unrealircd/conf"], [Define the location of the configuration files]) CONFDIR="$HOME/unrealircd/conf"]) +dnl We have to pass the builddir as well, for the module manager +AC_ARG_WITH(builddir, [AS_HELP_STRING([--with-builddir=path],[Specify the build directory])], + [AC_DEFINE_UNQUOTED([BUILDDIR], ["$withval"], [Define the build directory]) + BUILDDIR="$withval"], + [AC_DEFINE_UNQUOTED([BUILDDIR], ["$BUILDDIR_NOW"], [Specify the build directory]) + BUILDDIR="$BUILDDIR_NOW"]) + AC_ARG_WITH(modulesdir, [AS_HELP_STRING([--with-modulesdir=path],[Specify the directory for loadable modules])], [AC_DEFINE_UNQUOTED([MODULESDIR], ["$withval"], [Define the location of the modules]) MODULESDIR="$withval"], @@ -443,6 +449,7 @@ AS_IF([test "x$PRIVATELIBDIR" = "x"], LDFLAGS="$LDFLAGS $LDFLAGS_PRIVATELIBS" export LDFLAGS]) +AC_SUBST(BUILDDIR) AC_SUBST(BINDIR) AC_SUBST(SCRIPTDIR) AC_SUBST(CONFDIR) diff --git a/doc/RELEASE-NOTES.md b/doc/RELEASE-NOTES.md index 981452985..6f3ce204f 100644 --- a/doc/RELEASE-NOTES.md +++ b/doc/RELEASE-NOTES.md @@ -11,8 +11,8 @@ the planned functionality is there. For those users who do dare to run it, feel free to report any issues you may find or comment on the many U5 features on https://bugs.unrealircd.org/. -WARNING: if you are using anope, then note that you need to apply the -following patch. It is pending since February 2019 unfortunately: +***WARNING:*** if you are using anope, then note that you need to apply the +following SASL patch to anope. It is pending since February 2019 unfortunately: https://github.com/anope/anope/commit/da6e2730c259d6d6356a0a948e85730ae34663ab.patch Summary @@ -93,6 +93,15 @@ Enhancements Also note that websockets require nick names and channels to consist of UTF8 characters only, due to [WebSocket being incompatible with non-UTF8](https://www.unrealircd.org/docs/WebSocket_support#Problems_with_websockets_and_non-UTF8) +* There's now a [Module manager](https://www.unrealircd.org/docs/Module_manager) + which allows you to install and upgrade 3rd party modules in an easy way: + * ```./unrealircd module list``` - to list all available 3rd party modules + * ```./unrealircd module install third/something``` - to install the specified module. +* You can now test for configuration errors without actually starting the + IRC server. This is ideal if you are upgrading UnrealIRCd to a newer + version: simply run ```./unrealircd configtest``` to make sure it passes + the configuration test, and then you can safely restart the server for + the upgrade (in this example case). * Channel mode +L now kicks in for any rejected join, so not just for +l but also for +b, +i, +O, +z, +R and +k. If, for example, the channel is +L #insecure and also +z then, when an insecure user ties to join, they @@ -244,6 +253,8 @@ Changed old compilers / systems). * We now default to system libs (eg: ```--with-system-pcre2``` is assumed) * Spamfilter should catch some more spam evasion techniques. +* All /DCCDENY and deny dcc { } parsing and checking is now moved to + the 'dccdeny' module. Minor issues fixed ------------------- diff --git a/doc/conf/modules.sources.list b/doc/conf/modules.sources.list new file mode 100644 index 000000000..f4c4b8189 --- /dev/null +++ b/doc/conf/modules.sources.list @@ -0,0 +1,21 @@ +# +# This file contains the list of repositories that are used +# by the './unrealircd module' command. +# Note that 3rd party modules are NOT written by the UnrealIRCd team. +# Use such modules at your own risk. In case of problems, contact +# the module author. For more information, see: +# https://www.unrealircd.org/docs/Module_manager +# + +# +# This is the unrealircd-contrib repository which is added by default in +# UnrealIRCd 5 to make it easy for users to install 3rd party modules. +# If you are a module coder and want to add your module to this repository +# as well, then read the rules and procedure at: +# https://www.unrealircd.org/docs/Rules_for_3rd_party_modules_in_unrealircd-contrib +# +https://modules.unrealircd.org/modules.list + +# You can add more repositories here. However, do note that all +# URLs MUST start with https:// + diff --git a/include/h.h b/include/h.h index cf636b22f..589694be2 100644 --- a/include/h.h +++ b/include/h.h @@ -100,6 +100,7 @@ extern EVENT(e_clean_out_throttling_buckets); extern void module_loadall(void); extern long set_usermode(char *umode); extern char *get_usermode_string_raw(long umodes); +extern ConfigFile *config_parse(char *filename, char *confdata); extern void config_error(FORMAT_STRING(const char *format), ...) __attribute__((format(printf,1,2))); extern void config_warn(FORMAT_STRING(const char *format), ...) __attribute__((format(printf,1,2))); extern void config_error_missing(const char *filename, int line, const char *entry); @@ -114,7 +115,9 @@ extern int config_is_blankorempty(ConfigEntry *cep, const char *block); extern MODVAR int config_verbose; extern void config_progress(FORMAT_STRING(const char *format), ...) __attribute__((format(printf,1,2))); extern void config_entry_free(ConfigEntry *ce); -extern void ipport_seperate(char *string, char **ip, char **port); +extern ConfigFile *config_load(char *filename, char *displayname); +extern void config_free(ConfigFile *cfptr); +extern void ipport_seperate(char *string, char **ip, char **port); extern ConfigItem_class *find_class(char *name); extern ConfigItem_deny_dcc *find_deny_dcc(char *name); extern ConfigItem_oper *find_oper(char *name); @@ -831,6 +834,7 @@ extern int client_starttls(Client *acptr); extern void start_server_handshake(Client *cptr); extern void reject_insecure_server(Client *cptr); extern void report_crash(void); +extern void modulemanager(int argc, char *argv[]); extern int inet_pton4(const char *src, unsigned char *dst); extern int inet_pton6(const char *src, unsigned char *dst); extern int unreal_bind(int fd, char *ip, int port, int ipv6); @@ -864,6 +868,7 @@ extern CMD_FUNC(cmd_restart); extern void cmd_alias(Client *client, MessageTag *recv_mtags, int parc, char *parv[], char *cmd); /* special! */ extern void ban_flooder(Client *cptr); extern char *pcre2_version(void); +extern int get_terminal_width(void); extern int has_common_channels(Client *c1, Client *c2); extern int user_can_see_member(Client *user, Client *target, Channel *channel); extern int invisible_user_in_channel(Client *target, Channel *channel); @@ -945,3 +950,13 @@ extern void send_invalid_channelname(Client *client, char *channelname); extern int is_extended_ban(const char *str); extern int valid_sid(char *name); extern void parse_client_queued(Client *client); +extern char *sha256sum_file(const char *fname); +extern char *filename_strip_suffix(const char *fname, const char *suffix); +extern char *filename_add_suffix(const char *fname, const char *suffix); +extern int filename_has_suffix(const char *fname, const char *suffix); +extern void addmultiline(MultiLine **l, char *line); +extern void freemultiline(MultiLine *l); +extern void send_multinotice(Client *client, MultiLine *m); +extern void unreal_del_quotes(char *i); +extern char *unreal_add_quotes(char *str); +extern int unreal_add_quotes_r(char *i, char *o, size_t len); diff --git a/include/setup.h.in b/include/setup.h.in index da36ebb07..492bf4333 100644 --- a/include/setup.h.in +++ b/include/setup.h.in @@ -3,6 +3,9 @@ /* Define the directory where the unrealircd binary is located */ #undef BINDIR +/* Specify the build directory */ +#undef BUILDDIR + /* Define the location of the cached remote include files */ #undef CACHEDIR diff --git a/include/struct.h b/include/struct.h index c1064d724..a035e0c54 100644 --- a/include/struct.h +++ b/include/struct.h @@ -676,6 +676,15 @@ struct NameList { /** @} */ +typedef struct MultiLine MultiLine; +/** Multi-line list. + * @see addmultiline(), freemultiline(), sendnotice_multiline() + */ +struct MultiLine { + MultiLine *prev, *next; + char *line; +}; + #ifdef USE_LIBCURL struct MOTDDownload { diff --git a/include/sys.h b/include/sys.h index 2b3d062ad..b3baf25c5 100644 --- a/include/sys.h +++ b/include/sys.h @@ -196,7 +196,7 @@ extern char OSName[256]; * It's silly but this works: */ #ifdef _WIN32 - #define abort() do { char *crash = NULL; *crash = 'x'; } while(0) + #define abort() do { char *crash = NULL; *crash = 'x'; exit(1); } while(0) #endif #ifndef SOMAXCONN diff --git a/src/Makefile.in b/src/Makefile.in index 5d083d1a3..29508bcbf 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -31,7 +31,7 @@ OBJS=dns.o auth.o channel.o crule.o dbuf.o \ api-moddata.o api-extban.o api-isupport.o api-command.o \ api-clicap.o api-messagetag.o api-history-backend.o api-efunctions.o \ api-event.o \ - crypt_blowfish.o updconf.o crashreport.o \ + crypt_blowfish.o updconf.o crashreport.o modulemanager.o \ utf8.o \ openssl_hostname_validation.o $(URL) @@ -57,7 +57,10 @@ INCLUDES = ../include/channel.h \ all: build -build: ircd mods +build: + # Force build of 'ircd', before we start building any modules: + $(MAKE) ircd + $(MAKE) mods custommodule: +cd modules/third; $(MAKE) MODULEFILE=$(MODULEFILE) 'EXLIBS=$(EXLIBS)' custommodule @@ -227,6 +230,9 @@ updconf.o: updconf.c $(INCLUDES) crashreport.o: crashreport.c $(INCLUDES) $(CC) $(CFLAGS) $(BINCFLAGS) -c crashreport.c +modulemanager.o: modulemanager.c $(INCLUDES) + $(CC) $(CFLAGS) $(BINCFLAGS) -c modulemanager.c + utf8.o: utf8.c $(INCLUDES) $(CC) $(CFLAGS) $(BINCFLAGS) -c utf8.c diff --git a/src/buildmod b/src/buildmod index 2d54d6390..6fdfe228d 100755 --- a/src/buildmod +++ b/src/buildmod @@ -1,5 +1,12 @@ #!/bin/sh -echo Building all third party modules... +echo "" +echo "Checking for updates for third party modules..." +# We can't use the "unrealircd" script, since possibly the ircd +# has not been installed to it's final location.. yet. +# So this is basically "unrealircd module upgrade --no-install": +../../ircd -m upgrade --no-install +echo "" +echo "Building all third party modules..." for x in *.c do if [ "$x" != "*.c" ]; then diff --git a/src/conf.c b/src/conf.c index 5a1e0dd6b..7a34972da 100644 --- a/src/conf.c +++ b/src/conf.c @@ -194,9 +194,9 @@ long config_checkval(char *value, unsigned short flags); * Parser */ -ConfigFile *config_load(char *filename); +ConfigFile *config_load(char *filename, char *displayname); void config_free(ConfigFile *cfptr); -static ConfigFile *config_parse(char *filename, char *confdata); +ConfigFile *config_parse(char *filename, char *confdata); ConfigEntry *config_find_entry(ConfigEntry *ce, char *name); extern void add_entropy_configfile(struct stat *st, char *buf); @@ -746,7 +746,7 @@ char *allowed_channelchars_valtostr(AllowedChannelChars v) } } -ConfigFile *config_load(char *filename) +ConfigFile *config_load(char *filename, char *displayname) { struct stat sb; int fd; @@ -754,6 +754,9 @@ ConfigFile *config_load(char *filename) char *buf = NULL; ConfigFile *cfptr; + if (!displayname) + displayname = filename; + #ifndef _WIN32 fd = open(filename, O_RDONLY); #else @@ -797,7 +800,7 @@ ConfigFile *config_load(char *filename) buf[ret] = '\0'; close(fd); add_entropy_configfile(&sb, buf); - cfptr = config_parse(filename, buf); + cfptr = config_parse(displayname, buf); safe_free(buf); return cfptr; } @@ -817,7 +820,7 @@ void config_free(ConfigFile *cfptr) } /** Remove quotes so that 'hello \"all\" \\ lala' becomes 'hello "all" \ lala' */ -void unreal_delquotes(char *i) +void unreal_del_quotes(char *i) { char *o; @@ -837,10 +840,56 @@ void unreal_delquotes(char *i) *o = '\0'; } +/** Add quotes to a line, eg some"thing becomes some\"thing - extended version */ +int unreal_add_quotes_r(char *i, char *o, size_t len) +{ + if (len == 0) + return 0; + + len--; /* reserve room for nul byte */ + + if (len == 0) + { + *o = '\0'; + return 0; + } + + for (; *i; i++) + { + if ((*i == '"') || (*i == '\\')) /* only " and \ need to be quoted */ + { + if (len < 2) + break; + *o++ = '\\'; + *o++ = *i; + len -= 2; + } else + { + if (len == 0) + break; + *o++ = *i; + len--; + } + } + *o = '\0'; + + return 1; +} + +/** Add quotes to a line, eg some"thing becomes some\"thing */ +char *unreal_add_quotes(char *str) +{ + static char qbuf[2048]; + + *qbuf = '\0'; + unreal_add_quotes_r(str, qbuf, sizeof(qbuf)); + return qbuf; +} + /* This is the internal parser, made by Chris Behrens & Fred Jacobs <2005. * Enhanced (or mutilated) by Bram Matthys over the years (2015-2019). */ -static ConfigFile *config_parse(char *filename, char *confdata) +ConfigFile *config_parse(char *filename, char *confdata) { char *ptr; char *start; @@ -1034,7 +1083,7 @@ static ConfigFile *config_parse(char *filename, char *confdata) { safe_strldup(curce->ce_vardata, start, ptr-start+1); preprocessor_replace_defines(&curce->ce_vardata, curce); - unreal_delquotes(curce->ce_vardata); + unreal_del_quotes(curce->ce_vardata); } } else @@ -1046,7 +1095,7 @@ static ConfigFile *config_parse(char *filename, char *confdata) curce->ce_fileposstart = (start - confdata); safe_strldup(curce->ce_varname, start, ptr-start+1); preprocessor_replace_defines(&curce->ce_varname, curce); - unreal_delquotes(curce->ce_varname); + unreal_del_quotes(curce->ce_varname); preprocessor_cc_duplicate_list(cc_list, &curce->ce_cond); } break; @@ -2051,7 +2100,7 @@ int load_conf(char *filename, const char *original_path) } /* end include recursion checking code */ - if ((cfptr = config_load(filename))) + if ((cfptr = config_load(filename, NULL))) { for (cfptr3 = &conf, cfptr2 = conf; cfptr2; cfptr2 = cfptr2->cf_next) cfptr3 = &cfptr2->cf_next; diff --git a/src/crashreport.c b/src/crashreport.c index ab6e4976f..d17bca87a 100644 --- a/src/crashreport.c +++ b/src/crashreport.c @@ -40,9 +40,7 @@ char *find_best_coredump(void) while ((dir = readdir(fd))) { char *fname = dir->d_name; - if (strstr(fname, "core") && !strstr(fname, ".so") && - !strstr(fname, ".conf") && !strstr(fname, ".txt") && - !strstr(fname, ".done")) + if (filename_has_suffix(fname, ".core")) { char buf[512]; diff --git a/src/ircd.c b/src/ircd.c index 717cfdc37..9ad0e0d48 100644 --- a/src/ircd.c +++ b/src/ircd.c @@ -1152,6 +1152,11 @@ int InitUnrealIRCd(int argc, char *argv[]) case 'R': report_crash(); exit(0); +#ifndef _WIN32 + case 'm': + modulemanager(argc, argv); + exit(0); +#endif case '8': utf8_test(); exit(0); diff --git a/src/misc.c b/src/misc.c index 0eadff97e..562e82799 100644 --- a/src/misc.c +++ b/src/misc.c @@ -1798,6 +1798,7 @@ void DoMD5(char *mdout, const char *src, unsigned long n) char *md5hash(char *dst, const char *src, unsigned long n) { char tmp[16]; + SHA256_CTX hash; DoMD5(tmp, src, n); sprintf(dst, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", @@ -1806,3 +1807,111 @@ char *md5hash(char *dst, const char *src, unsigned long n) return dst; } + +/** Convert binary 'data' of size 'len' to a hexadecimal string 'str'. + * The caller is responsible to ensure that 'str' is sufficiently large. + */ +void binarytohex(void *data, size_t len, char *str) +{ + const char hexchars[16] = "0123456789abcdef"; + char *datastr = (char *)data; + int i, n = 0; + + for (i=0; i> 4) & 0xF]; + str[n++] = hexchars[datastr[i] & 0xF]; + } + str[n] = '\0'; +} + +/** Calculate the SHA256 checksum of a file */ +char *sha256sum_file(const char *fname) +{ + FILE *fd; + char buf[2048]; + SHA256_CTX hash; + char binaryhash[SHA256_DIGEST_LENGTH]; + static char hexhash[SHA256_DIGEST_LENGTH*2+1]; + int n; + + fd = fopen(fname, "rb"); + if (!fd) + return NULL; + + SHA256_Init(&hash); + while ((n = fread(buf, 1, sizeof(buf), fd)) > 0) + { + SHA256_Update(&hash, buf, n); + } + fclose(fd); + SHA256_Final(binaryhash, &hash); + binarytohex(binaryhash, sizeof(binaryhash), hexhash); + return hexhash; +} + +/** Remove a suffix from a filename, eg ".c" (if it is present) */ +char *filename_strip_suffix(const char *fname, const char *suffix) +{ + static char buf[512]; + + strlcpy(buf, fname, sizeof(buf)); + + if (suffix) + { + int buf_len = strlen(buf); + int suffix_len = strlen(suffix); + if (buf_len >= suffix_len) + { + if (!strncmp(buf+buf_len-suffix_len, suffix, suffix_len)) + buf[buf_len-suffix_len] = '\0'; + } + } else { + char *p = strrchr(buf, '.'); + if (p) + *p = '\0'; + } + return buf; +} + +/** Add a suffix to a filename, eg ".c" */ +char *filename_add_suffix(const char *fname, const char *suffix) +{ + static char buf[512]; + snprintf(buf, sizeof(buf), "%s%s", fname, suffix); + return buf; +} + +/* Returns 1 if the filename has the suffix, eg ".c" */ +int filename_has_suffix(const char *fname, const char *suffix) +{ + char buf[256]; + char *p; + strlcpy(buf, fname, sizeof(buf)); + p = strrchr(buf, '.'); + if (!p) + return 0; + if (!strcmp(p, suffix)) + return 1; + return 0; +} + +/** Add a line to a MultiLine list */ +void addmultiline(MultiLine **l, char *line) +{ + MultiLine *m = safe_alloc(sizeof(MultiLine)); + safe_strdup(m->line, line); + append_ListItem((ListStruct *)m, (ListStruct **)l); +} + +/** Free an entire MultiLine list */ +void freemultiline(MultiLine *l) +{ + MultiLine *l_next; + for (; l; l = l_next) + { + l_next = l->next; + safe_free(l->line); + safe_free(l); + } +} diff --git a/src/modulemanager.c b/src/modulemanager.c new file mode 100644 index 000000000..c30b509cf --- /dev/null +++ b/src/modulemanager.c @@ -0,0 +1,1644 @@ +/* UnrealIRCd module manager. + * (C) Copyright 2019 Bram Matthys ("Syzop") and the UnrealIRCd Team. + * License: GPLv2 + * See https://www.unrealircd.org/docs/Module_manager for user documentation. + */ + +#include "unrealircd.h" +#ifndef _WIN32 +#include + +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_ssl(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 (strncmp(url, "https://", 8)) + { + 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; + + *hostbuf = '\0'; + strlncat(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_ssl(); + if (!ctx_client) + { + fprintf(stderr, "ERROR: TLS initalization failure (I)\n"); + return 0; + } + + socket = BIO_new_ssl_connect(ctx_client); + if (!socket) + { + fprintf(stderr, "ERROR: TLS initalization failure (II)\n"); + goto out1; + } + + BIO_set_conn_hostname(socket, hostandport); + + if (BIO_do_connect(socket) != 1) + { + fprintf(stderr, "ERROR: Could not connect to %s\n", hostandport); + goto out2; + } + + if (BIO_do_handshake(socket) != 1) + { + fprintf(stderr, "ERROR: Could not connect to %s (TLS handshake failed)\n", hostandport); + goto out2; + } + + BIO_get_ssl(socket, &ssl); + if (!ssl) + { + fprintf(stderr, "ERROR: Could not get TLS connection from BIO -- strange\n"); + 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; + } + + // TODO: set alarm? 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 (!strncmp(line, "Location: ", 10)) + { + line += 10; + stripcrlf(line); + fclose(fd); + BIO_free_all(socket); + SSL_CTX_free(ctx_client); + if (strncmp(line, "https://", 8)) + { + 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 (!strncmp(line, "HTTP/1.1 301", 12) || + !strncmp(line, "HTTP/1.1 302", 12) || + !strncmp(line, "HTTP/1.1 303", 12) || + !strncmp(line, "HTTP/1.1 307", 12) || + !strncmp(line, "HTTP/1.1 308", 12)) + { + 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 (strncmp(line, "HTTP/1.1 200", 12)) + { + 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 */ + } + } + } + } + + fclose(fd); + BIO_free_all(socket); + SSL_CTX_free(ctx_client); + + if (!got_data) + { + fprintf(stderr, "Error while fetching %s: unable to retrieve data\n", url); + goto out3; + } + + return 1; +out3: + fclose(fd); +out2: + BIO_free_all(socket); +out1: + SSL_CTX_free(ctx_client); + 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; +} + +#define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", m->name, (x)->ce_varlinenum); return 0; } + +/** Parse a module { } line from a module (not repo!!) */ +int mm_module_file_config(ManagedModule *m, ConfigEntry *ce) +{ + ConfigEntry *cep; + + if (ce->ce_vardata) + { + config_error("%s:%d: module { } block should not have a name.", + m->name, ce->ce_varlinenum); + return 0; + } + + for (cep = ce->ce_entries; cep; cep = cep->ce_next) + { + if (!strcmp(cep->ce_varname, "source") || + !strcmp(cep->ce_varname, "version") || + !strcmp(cep->ce_varname, "author") || + !strcmp(cep->ce_varname, "sha256sum") || + !strcmp(cep->ce_varname, "description") + ) + { + config_error("%s:%d: module::%s should not be in here (it only exists in repository entries)", + m->name, cep->ce_varlinenum, cep->ce_varname); + return 0; + } + else if (!strcmp(cep->ce_varname, "troubleshooting")) + { + CheckNull(cep); + safe_strdup(m->troubleshooting, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "documentation")) + { + CheckNull(cep); + safe_strdup(m->documentation, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "min-unrealircd-version")) + { + CheckNull(cep); + safe_strdup(m->min_unrealircd_version, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "max-unrealircd-version")) + { + CheckNull(cep); + safe_strdup(m->max_unrealircd_version, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "post-install-text")) + { + if (cep->ce_entries) + { + ConfigEntry *cepp; + for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) + addmultiline(&m->post_install_text, cepp->ce_varname); + } else { + CheckNull(cep); + addmultiline(&m->post_install_text, cep->ce_vardata); + } + } + /* unknown items are silently ignored for future compatibility */ + } + + if (!m->documentation) + { + config_error("%s:%d: module::documentation missing", m->name, ce->ce_varlinenum); + return 0; + } + + if (!m->troubleshooting) + { + config_error("%s:%d: module::troubleshooting missing", m->name, ce->ce_varlinenum); + return 0; + } + + if (!m->min_unrealircd_version) + { + config_error("%s:%d: module::min-unrealircd-version missing", m->name, ce->ce_varlinenum); + 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) +{ + ConfigFile *cf; + ConfigEntry *ce; + + cf = config_parse(m->name, buf); + if (!cf) + return 0; /* eg: parse errors */ + + /* Parse the module { } block (only one!) */ + for (ce = cf->cf_entries; ce; ce = ce->ce_next) + { + if (!strcmp(ce->ce_varname, "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; + 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, "<<>>")) + parse_module_config = PMC_STAGE_STARTED; + break; + case PMC_STAGE_STARTED: + if (!strstr(buf, "<<>>")) + { + strlcat(moduleconfig, buf, MODULECONFIGBUFFER); + strlcat(moduleconfig, "\n", MODULECONFIGBUFFER); + } else + { + parse_module_config = PMC_STAGE_FINISHED; + } + break; + default: + /* Nothing to be done anymore */ + break; + } + } + + 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 (<<>>)\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)) + { + fprintf(stderr, "ERROR: Unable to parse module manager data in the %s module.\n" + "-- configuration block within %s --\n" + "%s\n" + "-- end of configiguration within %s --\n" + "You are suggested to contact the module author and paste the above to him/her\n", + m->name, + m->name, + moduleconfig, + 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 (strncmp(name, "third/", 6)) + return 0; + name += 6; + if (strstr(name, "..")) + return 0; + for (p = name; *p; p++) + if (!isalnum(*p) && !strchr("._-", *p)) + return 0; + return 1; +} + +#define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", repo_url, (x)->ce_varlinenum); 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->ce_vardata) + { + config_error("%s:%d: module { } with no name", + repo_url, ce->ce_varlinenum); + goto fail_mm_repo_module_config; + } + if (strncmp(ce->ce_vardata, "third/", 6)) + { + config_error("%s:%d: module { } name must start with: third/", + repo_url, ce->ce_varlinenum); + goto fail_mm_repo_module_config; + } + if (!mm_valid_module_name(ce->ce_vardata)) + { + config_error("%s:%d: module { } with illegal name: %s", + repo_url, ce->ce_varlinenum, ce->ce_vardata); + goto fail_mm_repo_module_config; + } + safe_strdup(m->name, ce->ce_vardata); + safe_strdup(m->repo_url, repo_url); + + for (cep = ce->ce_entries; cep; cep = cep->ce_next) + { + if (!strcmp(cep->ce_varname, "source")) + { + CheckNull(cep); + safe_strdup(m->source, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "sha256sum")) + { + CheckNull(cep); + safe_strdup(m->sha256sum, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "version")) + { + CheckNull(cep); + safe_strdup(m->version, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "author")) + { + CheckNull(cep); + safe_strdup(m->author, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "troubleshooting")) + { + CheckNull(cep); + safe_strdup(m->troubleshooting, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "documentation")) + { + CheckNull(cep); + safe_strdup(m->documentation, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "min-unrealircd-version")) + { + CheckNull(cep); + safe_strdup(m->min_unrealircd_version, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "max-unrealircd-version")) + { + CheckNull(cep); + safe_strdup(m->max_unrealircd_version, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "description")) + { + CheckNull(cep); + safe_strdup(m->description, cep->ce_vardata); + } + else if (!strcmp(cep->ce_varname, "post-install-text")) + { + if (cep->ce_entries) + { + ConfigEntry *cepp; + for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) + addmultiline(&m->post_install_text, cepp->ce_varname); + } else { + CheckNull(cep); + addmultiline(&m->post_install_text, cep->ce_vardata); + } + } + /* unknown items are silently ignored for future compatibility */ + } + + if (!m->source) + { + config_error("%s:%d: module::source missing", repo_url, ce->ce_varlinenum); + goto fail_mm_repo_module_config; + } + if (!m->sha256sum) + { + config_error("%s:%d: module::sha256sum missing", repo_url, ce->ce_varlinenum); + goto fail_mm_repo_module_config; + } + if (!m->version) + { + config_error("%s:%d: module::version missing", repo_url, ce->ce_varlinenum); + goto fail_mm_repo_module_config; + } + if (!m->author) + { + config_error("%s:%d: module::author missing", repo_url, ce->ce_varlinenum); + goto fail_mm_repo_module_config; + } + if (!m->documentation) + { + config_error("%s:%d: module::documentation missing", repo_url, ce->ce_varlinenum); + goto fail_mm_repo_module_config; + } + if (!m->troubleshooting) + { + config_error("%s:%d: module::troubleshooting missing", repo_url, ce->ce_varlinenum); + goto fail_mm_repo_module_config; + } + if (!m->min_unrealircd_version) + { + config_error("%s:%d: module::min-unrealircd-version missing", repo_url, ce->ce_varlinenum); + goto fail_mm_repo_module_config; + } + /* max_unrealircd_version is optional */ + if (!m->description) + { + config_error("%s:%d: module::description missing", repo_url, ce->ce_varlinenum); + 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->cf_entries; ce; ce = ce->ce_next) + { + if (!strcmp(ce->ce_varname, "module")) + { + m = mm_repo_module_config(url, ce); + if (!m) + 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; + + if (!file_exists(TMPDIR)) + { + 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 (strncmp(line, "https://", 8)) + { + 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); + tmpfile = unreal_mktemp(TMPDIR, "mm"); + if (mm_http_request(line, tmpfile, 1)) + { + if (!mm_parse_repo_db(line, tmpfile)) + { + fclose(fd); + return 0; + } + } + } + fclose(fd); + return 1; +} + +#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]; + 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"); + 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]; + 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); + 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 1; +} + +/** Actually download and install the module. + * This assumes compatibility checks have already been done. + */ +void mm_install_module(ManagedModule *m) +{ + char *basename = unreal_getfilename(m->source); + char *tmpfile; + 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 (strncmp(name, "third/", 6)) + { + 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 (strncmp(name, "third/", 6)) + { + 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 */ + 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++; + } + } + 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 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 \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 modname[128]; + char fullname[512]; + ManagedModule *m; + FILE *fdo; + + urlbasepath = args[1]; + dirname = args[2]; + outputfile = args[3]; + 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")) + { + 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); + print_md_block(fdo, m); + free_managed_module(m); + m = NULL; + } + } + closedir(fd); +} + +void mm_parse_c_file(int argc, char *args[]) +{ + char *fullname = args[1]; + char *basename; + char modname[256]; + ManagedModule *m; + + if (!fullname || !file_exists(fullname)) + { + fprintf(stderr, "Usage: ./unrealircd module parse-c-file path/to/file.c\n"); + 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); +} + +void modulemanager(int argc, char *args[]) +{ + if (!args[0]) + mm_usage(); + 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], "uninstall")) + mm_uninstall(argc, args); + else if (!strcasecmp(args[0], "upgrade")) + mm_upgrade(argc, args); + else if (!strcasecmp(args[0], "generate-repository")) + mm_generate_repository(argc, args); + else if (!strcasecmp(args[0], "parse-c-file")) + mm_parse_c_file(argc, args); + else + mm_usage(); +} +#endif diff --git a/src/modules/authprompt.c b/src/modules/authprompt.c index c1d7ec79f..37566fcf0 100644 --- a/src/modules/authprompt.c +++ b/src/modules/authprompt.c @@ -28,12 +28,6 @@ ModuleHeader MOD_HEADER "unrealircd-5", }; -typedef struct MultiLine MultiLine; -struct MultiLine { - MultiLine *prev, *next; - char *line; -}; - /** Configuration settings */ struct { int enabled; @@ -128,24 +122,6 @@ static void init_config(void) cfg.enabled = 0; } -static void addmultiline(MultiLine **l, char *line) -{ - MultiLine *m = safe_alloc(sizeof(MultiLine)); - safe_strdup(m->line, line); - append_ListItem((ListStruct *)m, (ListStruct **)l); -} - -static void freemultiline(MultiLine *l) -{ - MultiLine *l_next; - for (; l; l = l_next) - { - l_next = l->next; - safe_free(l->line); - safe_free(l); - } -} - static void config_postdefaults(void) { if (!cfg.message) @@ -376,12 +352,6 @@ CMD_FUNC(cmd_auth) send_first_auth(client); } -void send_multinotice(Client *client, MultiLine *m) -{ - for (; m; m = m->next) - sendnotice(client, "%s", m->line); -} - void authprompt_tag_as_auth_required(Client *client) { /* Allocate, and therefore indicate, that we are going to handle SASL for this user */ diff --git a/src/send.c b/src/send.c index 8810c157b..6bd72d5c7 100644 --- a/src/send.c +++ b/src/send.c @@ -1104,6 +1104,12 @@ void sendnotice(Client *to, FORMAT_STRING(const char *pattern), ...) va_end(vl); } +/** Send MultiLine list as a notice, one for each line */ +void sendnotice_multiline(Client *client, MultiLine *m) +{ + for (; m; m = m->next) + sendnotice(client, "%s", m->line); +} void sendtxtnumeric(Client *to, FORMAT_STRING(const char *pattern), ...) { static char realpattern[1024]; diff --git a/src/support.c b/src/support.c index 6819c10bd..a4196a7ba 100644 --- a/src/support.c +++ b/src/support.c @@ -944,6 +944,7 @@ int unreal_copyfile(const char *src, const char *dest) /** Same as unreal_copyfile, but with an option to try hardlinking first */ int unreal_copyfileex(const char *src, const char *dest, int tryhardlink) { + unlink(dest); #ifndef _WIN32 /* Try a hardlink first... */ if (tryhardlink && !link(src, dest)) @@ -1291,3 +1292,15 @@ int gettimeofday(struct timeval *tp, void *tzp) return 0; } #endif + +/** Get the numer of characters per line that fit on the terminal (the width) */ +int get_terminal_width(void) +{ +#if defined(_WIN32) || !defined(TIOCGWINSZ) + return 80; +#else + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + return w.ws_col; +#endif +} diff --git a/src/updconf.c b/src/updconf.c index 664911280..fd0c3f641 100644 --- a/src/updconf.c +++ b/src/updconf.c @@ -7,7 +7,6 @@ #include "unrealircd.h" -extern ConfigFile *config_load(char *filename); extern void config_free(ConfigFile *cfptr); char configfiletmp[512]; @@ -207,50 +206,6 @@ void replace_section(ConfigEntry *ce, char *buf) static char buf[8192]; -int updconf_addquotes_r(char *i, char *o, size_t len) -{ - if (len == 0) - return 0; - - len--; /* reserve room for nul byte */ - - if (len == 0) - { - *o = '\0'; - return 0; - } - - for (; *i; i++) - { - if ((*i == '"') || (*i == '\\')) /* only " and \ need to be quoted */ - { - if (len < 2) - break; - *o++ = '\\'; - *o++ = *i; - len -= 2; - } else - { - if (len == 0) - break; - *o++ = *i; - len--; - } - } - *o = '\0'; - - return 1; -} - -char *updconf_addquotes(char *str) -{ - static char qbuf[2048]; - - *qbuf = '\0'; - updconf_addquotes_r(str, qbuf, sizeof(qbuf)); - return qbuf; -} - int upgrade_me_block(ConfigEntry *ce) { ConfigEntry *cep; @@ -725,13 +680,13 @@ int upgrade_spamfilter_block(ConfigEntry *ce) "\ttarget %s;\n" "\taction %s;\n", match_type, - updconf_addquotes(regex), + unreal_add_quotes(regex), targets, action); /* optional: reason */ if (reason) - snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\treason \"%s\";\n", updconf_addquotes(reason)); + snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\treason \"%s\";\n", unreal_add_quotes(reason)); /* optional: ban-time */ if (ban_time) @@ -1389,7 +1344,7 @@ void update_read_settings(char *cfgfile) ConfigFile *cf = NULL; ConfigEntry *ce = NULL, *cep, *cepp; - cf = config_load(cfgfile); + cf = config_load(cfgfile, NULL); if (!cf) return; @@ -1461,7 +1416,7 @@ again: cf = NULL; } - cf = config_load(configfiletmp); + cf = config_load(configfiletmp, NULL); if (!cf) { config_error("could not load configuration file '%s'", configfile); @@ -1613,7 +1568,7 @@ void build_include_list_ex(char *fname, ConfigFile **cf_list) add_include_list(fname, cf_list); - cf = config_load(fname); + cf = config_load(fname, NULL); if (!cf) return; diff --git a/unrealircd.in b/unrealircd.in index dba2b8aad..159802f76 100644 --- a/unrealircd.in +++ b/unrealircd.in @@ -110,6 +110,9 @@ elif [ "$1" = "croncheck" ] ; then $0 start elif [ "$1" = "configtest" ] ; then @BINDIR@/unrealircd -c +elif [ "$1" = "module" ] ; then + shift + @BINDIR@/unrealircd -m $* elif [ "$1" = "reloadtls" ] ; then echo "Reloading SSL/TLS certificates" if [ ! -r $PID_FILE ] ; then @@ -242,5 +245,20 @@ elif [ "$1" = "spki" -o "$1" = "spkifp" ] ; then echo "password \"$HASH\" { spkifp; };" echo "" else - echo "Usage: unrealircd start|stop|configtest|rehash|restart|mkpasswd|version|croncheck|gencloak|reloadtls|spkifp|upgrade-conf" + echo "This script expects a parameter. Use:" + echo "unrealircd configtest Test the configuration file" + echo "unrealircd start Start the IRC Server" + echo "unrealircd stop Stop (kill) the IRC Server" + echo "unrealircd rehash Reload the configuration file" + echo "unrealircd reloadtls Reload the SSL/TLS certificate and settings" + echo "unrealircd restart Restart the IRC Server (stop+start)" + echo "unrealircd mkpasswd Hash a password" + echo "unrealircd version Display the UnrealIRCd version" + echo "unrealircd module Install and uninstall 3rd party modules" + echo "unrealircd croncheck For use in crontab: this checks if the server" + echo " is running. If not, the server is started." + echo "unrealircd gencloak Display 3 random cloak keys" + echo "unrealircd spkifp Display SPKI Fingerprint" + echo "unrealircd upgrade-conf Upgrade the configuration file from UnrealIRCd" + echo " 3.2.x/4.x to 5.x format" fi