From 39688517b0d8ac427778a6a386d80791eef3c5bc Mon Sep 17 00:00:00 2001 From: Bram Matthys Date: Sun, 2 Jan 2022 20:10:52 +0100 Subject: [PATCH] Make "./unrealircd rehash" show output on the terminal, same for "./unrealircd reloadtls" and there is now also a "./unrealircd status" The output is colorized if the terminal supports it (just like on the boot screen) and also the exit status is 0 for success and non-0 for failure. The purpose of all this is that you can easily detect rehash errors on the command line. These three commands communicate to UnrealIRCd via the new control UNIX socket, which is in ~/data/unrealircd.ctl. This also does a lot of other stuff because we now have an internal tool called bin/unrealircdctl which is called by ./unrealircd for some of the commands to communicate to the unrealircd.ctl socket. Later on more of the existing functionality may be moved to that tool and we may also provide it on Windows in CLI mode so people have more of the same functionality as on *NIX. --- Makefile.in | 1 + configure | 23 ++++ configure.ac | 7 ++ include/h.h | 16 ++- include/setup.h.in | 3 + include/struct.h | 13 ++- src/Makefile.in | 18 ++-- src/api-command.c | 31 ++++-- src/api-event.c | 18 ---- src/conf.c | 18 +++- src/ircd.c | 248 +++++-------------------------------------- src/ircd_vars.c | 31 ++++++ src/list.c | 2 + src/log.c | 24 ++++- src/misc.c | 173 +++++++++++++++++++++++++++++- src/parse.c | 12 ++- src/proc_io_client.c | 181 +++++++++++++++++++++++++++++++ src/proc_io_server.c | 133 +++++++++++++++++++++++ src/send.c | 5 +- src/socket.c | 130 +++++++++++++++++------ src/tls.c | 14 +-- src/unrealircdctl.c | 90 ++++++++++++++++ unrealircd.in | 27 ++--- 23 files changed, 893 insertions(+), 325 deletions(-) create mode 100644 src/ircd_vars.c create mode 100644 src/proc_io_client.c create mode 100644 src/proc_io_server.c create mode 100644 src/unrealircdctl.c diff --git a/Makefile.in b/Makefile.in index a1f644cf7..df3b20d80 100644 --- a/Makefile.in +++ b/Makefile.in @@ -179,6 +179,7 @@ depend: install: all $(INSTALL) -m 0700 -d $(DESTDIR)@BINDIR@ $(INSTALL) -m 0700 src/ircd $(DESTDIR)@BINDIR@/unrealircd + $(INSTALL) -m 0700 src/unrealircdctl $(DESTDIR)@BINDIR@/unrealircdctl $(INSTALL) -m 0700 extras/unrealircd-upgrade-script $(DESTDIR)@BINDIR@/unrealircd-upgrade-script $(INSTALL) -m 0700 -d $(DESTDIR)@DOCDIR@ $(INSTALL) -m 0600 doc/Authors doc/coding-guidelines doc/tao.of.irc doc/KEYS doc/RELEASE-NOTES.md $(DESTDIR)@DOCDIR@ diff --git a/configure b/configure index 7c98be2b2..52e63d4fe 100755 --- a/configure +++ b/configure @@ -659,6 +659,7 @@ PKG_CONFIG_LIBDIR PKG_CONFIG_PATH PKG_CONFIG LDFLAGS_PRIVATELIBS +CONTROLFILE PIDFILE DOCDIR PERMDATADIR @@ -752,6 +753,7 @@ with_tmpdir with_datadir with_docdir with_pidfile +with_controlfile with_privatelibdir with_maxconnections with_no_operoverride @@ -1460,6 +1462,7 @@ Optional Packages: --with-datadir=path Specify the directory where permanent data is stored --with-docdir=path Specify the directory where documentation is stored --with-pidfile=path Specify the path of the pid file + --with-controlfile=path Specify the path of the control socket --with-privatelibdir=path Specify the directory where private libraries are stored. Disable when building a package for a distro @@ -6548,6 +6551,25 @@ fi +# Check whether --with-controlfile was given. +if test "${with_controlfile+set}" = set; then : + withval=$with_controlfile; +cat >>confdefs.h <<_ACEOF +#define CONTROLFILE "$withval" +_ACEOF + + CONTROLFILE="$withval" +else + +cat >>confdefs.h <<_ACEOF +#define CONTROLFILE "$HOME/unrealircd/data/unrealircd.ctl" +_ACEOF + + CONTROLFILE="$HOME/unrealircd/data/unrealircd.ctl" +fi + + + # Check whether --with-privatelibdir was given. if test "${with_privatelibdir+set}" = set; then : withval=$with_privatelibdir; @@ -6589,6 +6611,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 7cbfc82d2..491ccc9bc 100644 --- a/configure.ac +++ b/configure.ac @@ -484,6 +484,12 @@ AC_ARG_WITH(pidfile, [AS_HELP_STRING([--with-pidfile=path],[Specify the path of [AC_DEFINE_UNQUOTED([PIDFILE], ["$HOME/unrealircd/data/unrealircd.pid"], [Define the path of the pid file]) PIDFILE="$HOME/unrealircd/data/unrealircd.pid"]) +AC_ARG_WITH(controlfile, [AS_HELP_STRING([--with-controlfile=path],[Specify the path of the control socket])], + [AC_DEFINE_UNQUOTED([CONTROLFILE], ["$withval"], [Define the path of the control socket]) + CONTROLFILE="$withval"], + [AC_DEFINE_UNQUOTED([CONTROLFILE], ["$HOME/unrealircd/data/unrealircd.ctl"], [Define the path of the control socket]) + CONTROLFILE="$HOME/unrealircd/data/unrealircd.ctl"]) + dnl Ensure that this “feature” can be disabled as it makes it harder to package unrealircd. dnl Users have always been able to specify “./configure LDFLAGS=-Wl,-rpath,/path/to/blah”—binki AC_ARG_WITH(privatelibdir, [AS_HELP_STRING([--with-privatelibdir=path],[Specify the directory where private libraries are stored. Disable when building a package for a distro])], @@ -514,6 +520,7 @@ dnl well, Because DATADIR conflicts with the Windows SDK header files.. amazing. AC_SUBST(PERMDATADIR) AC_SUBST(DOCDIR) AC_SUBST(PIDFILE) +AC_SUBST(CONTROLFILE) AC_SUBST(LDFLAGS_PRIVATELIBS) AC_ARG_WITH(maxconnections, [AS_HELP_STRING([--with-maxconnections=size], [Specify the max file descriptors to use])], diff --git a/include/h.h b/include/h.h index ec0301a09..9c744cdf1 100644 --- a/include/h.h +++ b/include/h.h @@ -28,6 +28,12 @@ #include "setup.h" #include "fdlist.h" +extern int dorehash, dorestart, doreloadcert; +#ifndef _WIN32 +extern char **myargv; +#else +extern LPCSTR cmdLine; +#endif extern MODVAR char *extraflags; extern MODVAR int tainted; extern MODVAR Member *freemember; @@ -153,6 +159,7 @@ extern MODVAR struct list_head lclient_list; extern MODVAR struct list_head server_list; extern MODVAR struct list_head oper_list; extern MODVAR struct list_head unknown_list; +extern MODVAR struct list_head control_list; extern MODVAR struct list_head global_server_list; extern MODVAR struct list_head dead_list; extern RealCommand *find_command(const char *cmd, int flags); @@ -930,7 +937,7 @@ extern Cmode_t get_extmode_bitbychar(char m); extern long find_user_mode(char mode); extern void start_listeners(void); extern void buildvarstring(const char *inbuf, char *outbuf, size_t len, const char *name[], const char *value[]); -extern void reinit_tls(void); +extern int reinit_tls(void); extern CMD_FUNC(cmd_error); extern CMD_FUNC(cmd_dns); extern CMD_FUNC(cmd_info); @@ -1136,6 +1143,8 @@ extern void flood_limit_exceeded_log(Client *client, const char *floodname); /* logging */ extern int config_test_log(ConfigFile *conf, ConfigEntry *ce); extern int config_run_log(ConfigFile *conf, ConfigEntry *ce); +extern const char *log_level_terminal_color(LogLevel loglevel); +#define TERMINAL_COLOR_RESET "\033[0m" extern LogType log_type_stringtoval(const char *str); extern const char *log_type_valtostring(LogType v); #ifdef DEBUGMODE @@ -1209,3 +1218,8 @@ extern void make_umodestr(void); extern void initwhowas(void); extern void uid_init(void); extern const char *uid_get(void); +/* proc i/o */ +extern void add_proc_io_server(void); +extern void procio_post_rehash(int failure); +/* end of proc i/o */ +extern int minimum_msec_since_last_run(struct timeval *tv_old, long minimum); diff --git a/include/setup.h.in b/include/setup.h.in index 18f69081d..16b097c34 100644 --- a/include/setup.h.in +++ b/include/setup.h.in @@ -12,6 +12,9 @@ /* Define the location of the configuration files */ #undef CONFDIR +/* Define the path of the control socket */ +#undef CONTROLFILE + /* Define the location of permanent data files */ #undef DATADIR diff --git a/include/struct.h b/include/struct.h index 5b55df9bf..43f2b7e11 100644 --- a/include/struct.h +++ b/include/struct.h @@ -323,6 +323,7 @@ typedef enum LogDestination { LOG_DEST_SNOMASK=0, LOG_DEST_OPER=1, LOG_DEST_REMO * @{ */ typedef enum ClientStatus { + CLIENT_STATUS_CONTROL = -8, /**< Client is on the control channel */ CLIENT_STATUS_LOG = -7, /**< Client is a log file */ CLIENT_STATUS_TLS_STARTTLS_HANDSHAKE = -8, /**< Client is doing a STARTTLS handshake */ CLIENT_STATUS_CONNECTING = -6, /**< Client is an outgoing connect */ @@ -345,6 +346,7 @@ typedef enum ClientStatus { /** Client is not fully registered yet. May become a user or a server, we don't know yet. */ #define IsUnknown(x) (((x)->status == CLIENT_STATUS_UNKNOWN) || ((x)->status == CLIENT_STATUS_TLS_STARTTLS_HANDSHAKE)) #define IsServer(x) ((x)->status == CLIENT_STATUS_SERVER) /**< Is a server that has completed the connection handshake */ +#define IsControl(x) ((x)->status == CLIENT_STATUS_CONTROL) /**< Is on the control channel (not on IRC) */ #define IsLog(x) ((x)->status == CLIENT_STATUS_LOG) /**< Is a log file, not a user or server */ #define IsStartTLSHandshake(x) ((x)->status == CLIENT_STATUS_TLS_STARTTLS_HANDSHAKE) /**< Currently doing a STARTTLS handshake */ #define IsTLSAcceptHandshake(x) ((x)->status == CLIENT_STATUS_TLS_ACCEPT_HANDSHAKE) /**< Currently doing a TLS handshake - incoming */ @@ -361,6 +363,8 @@ typedef enum ClientStatus { #define SetServer(x) ((x)->status = CLIENT_STATUS_SERVER) #define SetUser(x) ((x)->status = CLIENT_STATUS_USER) #define SetLog(x) ((x)->status = CLIENT_STATUS_LOG) +#define SetControl(x) ((x)->status = CLIENT_STATUS_CONTROL) +#define SetUser(x) ((x)->status = CLIENT_STATUS_USER) /** @} */ @@ -372,6 +376,7 @@ typedef enum ClientStatus { #define CLIENT_FLAG_DEAD 0x00000002 /**< Client is dead: already quit/exited and removed from all lists -- Remaining part will soon be freed in main loop */ #define CLIENT_FLAG_DEADSOCKET 0x00000004 /**< Local socket is dead but otherwise the client still exists fully -- Will soon exit in main loop */ #define CLIENT_FLAG_KILLED 0x00000008 /**< Prevents "QUIT" from being sent for this */ +#define CLIENT_FLAG_MONITOR_REHASH 0x00000010 /**< Client is monitoring rehash output */ #define CLIENT_FLAG_OUTGOING 0x00000020 /**< Outgoing connection (do not touch cptr->listener->clients) */ #define CLIENT_FLAG_CLOSING 0x00000040 /**< Set when closing to suppress errors */ #define CLIENT_FLAG_LISTEN 0x00000080 /**< Used to mark clients which we listen() on */ @@ -470,6 +475,7 @@ typedef enum ClientStatus { #define IsEAuth(x) ((x)->flags & CLIENT_FLAG_EAUTH) #define IsIdentSuccess(x) ((x)->flags & CLIENT_FLAG_IDENTSUCCESS) #define IsKilled(x) ((x)->flags & CLIENT_FLAG_KILLED) +#define IsMonitorRehash(x) ((x)->flags & CLIENT_FLAG_MONITOR_REHASH) #define IsListening(x) ((x)->flags & CLIENT_FLAG_LISTEN) #define IsLocalhost(x) ((x)->flags & CLIENT_FLAG_LOCALHOST) #define IsMap(x) ((x)->flags & CLIENT_FLAG_MAP) @@ -501,6 +507,7 @@ typedef enum ClientStatus { #define SetEAuth(x) do { (x)->flags |= CLIENT_FLAG_EAUTH; } while(0) #define SetIdentSuccess(x) do { (x)->flags |= CLIENT_FLAG_IDENTSUCCESS; } while(0) #define SetKilled(x) do { (x)->flags |= CLIENT_FLAG_KILLED; } while(0) +#define SetMonitorRehash(x) do { (x)->flags |= CLIENT_FLAG_MONITOR_REHASH; } while(0) #define SetListening(x) do { (x)->flags |= CLIENT_FLAG_LISTEN; } while(0) #define SetLocalhost(x) do { (x)->flags |= CLIENT_FLAG_LOCALHOST; } while(0) #define SetMap(x) do { (x)->flags |= CLIENT_FLAG_MAP; } while(0) @@ -530,6 +537,7 @@ typedef enum ClientStatus { #define ClearEAuth(x) do { (x)->flags &= ~CLIENT_FLAG_EAUTH; } while(0) #define ClearIdentSuccess(x) do { (x)->flags &= ~CLIENT_FLAG_IDENTSUCCESS; } while(0) #define ClearKilled(x) do { (x)->flags &= ~CLIENT_FLAG_KILLED; } while(0) +#define ClearMonitorRehash(x) do { (x)->flags &= ~CLIENT_FLAG_MONITOR_REHASH; } while(0) #define ClearListening(x) do { (x)->flags &= ~CLIENT_FLAG_LISTEN; } while(0) #define ClearLocalhost(x) do { (x)->flags &= ~CLIENT_FLAG_LOCALHOST; } while(0) #define ClearMap(x) do { (x)->flags &= ~CLIENT_FLAG_MAP; } while(0) @@ -784,11 +792,11 @@ struct LoopStruct { unsigned do_bancheck : 1; /* perform *line bancheck? */ unsigned do_bancheck_spamf_user : 1; /* perform 'user' spamfilter bancheck */ unsigned do_bancheck_spamf_away : 1; /* perform 'away' spamfilter bancheck */ - unsigned rehashing : 1; unsigned terminating : 1; unsigned config_load_failed : 1; unsigned rehash_download_busy : 1; /* don't return "all downloads complete", needed for race condition */ unsigned tainted : 1; + int rehashing; Client *rehash_save_client; void (*boot_function)(); }; @@ -856,6 +864,8 @@ struct SWhois { #define CMD_VIRUS 0x0080 /** Command requires IRCOp privileges */ #define CMD_OPER 0x0200 +/** Command is for control channel only (unrealircd.ctl socket) */ +#define CMD_CONTROL 0x0400 /** Command function - used by all command handlers. * This is used in the code like
CMD_FUNC(cmd_yourcmd)
as a function definition. @@ -1217,6 +1227,7 @@ extern void unload_all_unused_moddata(void); #define LISTENER_TLS 0x000010 #define LISTENER_BOUND 0x000020 #define LISTENER_DEFER_ACCEPT 0x000040 +#define LISTENER_CONTROL 0x000080 /**< Control channel */ #define IsServersOnlyListener(x) ((x) && ((x)->options & LISTENER_SERVERSONLY)) diff --git a/src/Makefile.in b/src/Makefile.in index 62d43881d..589556100 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -21,10 +21,10 @@ CC = "==== DO NOT RUN MAKE FROM THIS DIRECTORY ====" -OBJS=dns.o auth.o channel.o crule.o dbuf.o \ - fdlist.o hash.o ircd.o ircsprintf.o list.o \ +OBJS=ircd_vars.o dns.o auth.o channel.o crule.o dbuf.o \ + fdlist.o hash.o ircsprintf.o list.o \ match.o modules.o parse.o mempool.o operclass.o \ - conf_preprocessor.o conf.o debug.o dispatch.o \ + conf_preprocessor.o conf.o proc_io_server.o debug.o dispatch.o \ misc.o serv.o aliases.o socket.o \ tls.o user.o scache.o send.o support.o \ version.o whowas.o random.o api-usermode.o api-channelmode.o \ @@ -62,22 +62,22 @@ all: build build: # Force build of 'ircd', before we start building any modules: - $(MAKE) ircd + $(MAKE) ircd unrealircdctl $(MAKE) mods custommodule: +cd modules/third; $(MAKE) MODULEFILE=$(MODULEFILE) 'EXLIBS=$(EXLIBS)' custommodule -ircd: $(OBJS) - $(CC) $(CFLAGS) $(BINCFLAGS) $(CRYPTOLIB) -o ircd $(OBJS) $(LDFLAGS) $(BINLDFLAGS) $(IRCDLIBS) $(CRYPTOLIB) +ircd: $(OBJS) ircd.o + $(CC) $(CFLAGS) $(BINCFLAGS) $(CRYPTOLIB) -o ircd ircd.o $(OBJS) $(LDFLAGS) $(BINLDFLAGS) $(IRCDLIBS) $(CRYPTOLIB) + +unrealircdctl: $(OBJS) unrealircdctl.o proc_io_client.o + $(CC) $(CFLAGS) $(BINCFLAGS) $(CRYPTOLIB) -o unrealircdctl unrealircdctl.o proc_io_client.o $(OBJS) $(LDFLAGS) $(BINLDFLAGS) $(IRCDLIBS) $(CRYPTOLIB) mods: @if [ ! -r include ] ; then \ ln -s ../include include; \ fi - @if [ ! -r modules ] ; then \ - echo "You havent done cvs update -P -d"; \ - fi +cd modules; $(MAKE) all version.c: version.c.SH diff --git a/src/api-command.c b/src/api-command.c index f61e2bf52..065a4eb16 100644 --- a/src/api-command.c +++ b/src/api-command.c @@ -89,7 +89,7 @@ static Command *CommandAddInternal(Module *module, const char *cmd, CmdFunc func Command *command = NULL; RealCommand *c; - if (find_command_simple(cmd)) + if ((c = find_command(cmd, flags)) && (c->flags == flags)) { if (module) module->errorcode = MODERR_EXISTS; @@ -253,15 +253,26 @@ static RealCommand *add_Command_backend(const char *cmd) RealCommand *find_command(const char *cmd, int flags) { RealCommand *p; - for (p = CommandHash[toupper(*cmd)]; p; p = p->next) { - if ((flags & CMD_UNREGISTERED) && !(p->flags & CMD_UNREGISTERED)) - continue; - if ((flags & CMD_SHUN) && !(p->flags & CMD_SHUN)) - continue; - if ((flags & CMD_VIRUS) && !(p->flags & CMD_VIRUS)) - continue; - if ((flags & CMD_ALIAS) && !(p->flags & CMD_ALIAS)) - continue; + for (p = CommandHash[toupper(*cmd)]; p; p = p->next) + { + if (flags & CMD_CONTROL) + { + if (!(p->flags & CMD_CONTROL)) + continue; + } else + { + if ((flags & CMD_UNREGISTERED) && !(p->flags & CMD_UNREGISTERED)) + continue; + if ((flags & CMD_SHUN) && !(p->flags & CMD_SHUN)) + continue; + if ((flags & CMD_VIRUS) && !(p->flags & CMD_VIRUS)) + continue; + if ((flags & CMD_ALIAS) && !(p->flags & CMD_ALIAS)) + continue; + if (p->flags & CMD_CONTROL) + continue; /* important to also filter it this way ;) */ + } + if (!strcasecmp(p->cmd, cmd)) return p; } diff --git a/src/api-event.c b/src/api-event.c index 799edb750..1ad24f669 100644 --- a/src/api-event.c +++ b/src/api-event.c @@ -27,9 +27,6 @@ ID_Copyright("(C) Carsten Munk 2001"); MODVAR Event *events = NULL; -extern EVENT(unrealdns_removeoldrecords); -extern EVENT(unrealdb_expire_secret_cache); - /** Add an event, a function that will run at regular intervals. * @param module Module that this event belongs to * @param name Name of the event @@ -211,18 +208,3 @@ void DoEvents(void) CleanupEvents(); } - -void SetupEvents(void) -{ - /* Start events */ - EventAdd(NULL, "tunefile", save_tunefile, NULL, 300*1000, 0); - EventAdd(NULL, "garbage", garbage_collect, NULL, GARBAGE_COLLECT_EVERY*1000, 0); - EventAdd(NULL, "loop", loop_event, NULL, 1000, 0); - EventAdd(NULL, "unrealdns_removeoldrecords", unrealdns_removeoldrecords, NULL, 15000, 0); - EventAdd(NULL, "check_pings", check_pings, NULL, 1000, 0); - EventAdd(NULL, "check_deadsockets", check_deadsockets, NULL, 1000, 0); - EventAdd(NULL, "handshake_timeout", handshake_timeout, NULL, 1000, 0); - EventAdd(NULL, "tls_check_expiry", tls_check_expiry, NULL, (86400/2)*1000, 0); - EventAdd(NULL, "unrealdb_expire_secret_cache", unrealdb_expire_secret_cache, NULL, 61000, 0); - EventAdd(NULL, "throttling_check_expire", throttling_check_expire, NULL, 1000, 0); -} diff --git a/src/conf.c b/src/conf.c index 72d052eb6..dc0b0fedf 100644 --- a/src/conf.c +++ b/src/conf.c @@ -2412,7 +2412,8 @@ void config_rehash() } for (listen_ptr = conf_listen; listen_ptr; listen_ptr = listen_ptr->next) { - listen_ptr->flag.temporary = 1; + if (!(listen_ptr->options & LISTENER_CONTROL)) + listen_ptr->flag.temporary = 1; } for (tld_ptr = conf_tld; tld_ptr; tld_ptr = (ConfigItem_tld *) next) { @@ -2945,7 +2946,9 @@ ConfigItem_listen *find_listen(const char *ipmask, int port, SocketType socket_t for (e = conf_listen; e; e = e->next) { - if (socket_type == SOCKET_TYPE_UNIX) + if (e->socket_type != socket_type) + continue; + if (e->socket_type == SOCKET_TYPE_UNIX) { if (!strcmp(e->file, ipmask)) return e; @@ -9352,6 +9355,7 @@ void config_run(void) { extcmodes_check_for_changes(); start_listeners(); + add_proc_io_server(); free_all_config_resources(); } @@ -10624,13 +10628,16 @@ void request_rehash(Client *client) int rehash_internal(Client *client) { + int failure; + /* Log it here if it is by a signal */ if (client == NULL) unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [./unrealircd rehash]"); - loop.rehashing = 1; /* double checking.. */ + loop.rehashing = 2; /* now doing the actual rehash */ - if (config_test() == 0) + failure = config_test(); + if (failure == 0) config_run(); /* TODO: uh.. are we supposed to do all this for a failed rehash too? maybe some but not all? */ reread_motdsandrules(); @@ -10641,8 +10648,11 @@ int rehash_internal(Client *client) // unload_all_unused_moddata(); -- this will crash umodes_check_for_changes(); charsys_check_for_changes(); + + /* Clear everything now that we are done */ loop.rehashing = 0; remote_rehash_client = NULL; + procio_post_rehash(failure); return 1; } diff --git a/src/ircd.c b/src/ircd.c index 4477bdfab..3fc7462bb 100644 --- a/src/ircd.c +++ b/src/ircd.c @@ -25,209 +25,33 @@ char *malloc_options = "h" MALLOC_FLAGS_EXTRA; #endif -#ifndef _WIN32 -extern char unreallogo[]; -#endif -int SVSNOOP = 0; +/* Externals */ extern MODVAR char *buildid; -time_t timeofday = 0; -struct timeval timeofday_tv; -int tainted = 0; -LoopStruct loop; -#ifndef _WIN32 -uid_t irc_uid = 0; -gid_t irc_gid = 0; -#endif - -MODVAR IRCCounts irccounts; -MODVAR Client me; /* That's me */ -MODVAR char *me_hash; extern char backupbuf[8192]; -#ifdef _WIN32 -extern SERVICE_STATUS_HANDLE IRCDStatusHandle; -extern SERVICE_STATUS IRCDStatus; -#endif - -MODVAR unsigned char conf_debuglevel = 0; - -void server_reboot(const char *); -void restart(const char *); -static void open_debugfile(), setup_signals(); +extern EVENT(unrealdns_removeoldrecords); +extern EVENT(unrealdb_expire_secret_cache); extern void init_glines(void); extern void tkl_init(void); extern void process_clients(void); extern void unrealdb_test(void); - -#ifndef _WIN32 -MODVAR char **myargv; -#else -LPCSTR cmdLine; -#endif -char *configfile = NULL; /* Server configuration file */ -int debuglevel = 0; /* Server debug level */ -int bootopt = 0; /* Server boot option flags */ -char *debugmode = ""; /* -"- -"- -"- */ -char *sbrk0; /* initial sbrk(0) */ -static int dorehash = 0, dorestart = 0, doreloadcert = 0; -MODVAR int booted = FALSE; - -void s_die() -{ -#ifdef _WIN32 - Client *client; - if (!IsService) - { - loop.terminating = 1; - unload_all_modules(); - - list_for_each_entry(client, &lclient_list, lclient_node) - (void) send_queued(client); - - exit(-1); - } - else { - SERVICE_STATUS status; - SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); - SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_STOP); - ControlService(hService, SERVICE_CONTROL_STOP, &status); - } -#else - loop.terminating = 1; - unload_all_modules(); - unlink(conf_files ? conf_files->pid_file : IRCD_PIDFILE); - exit(0); -#endif -} - -#ifndef _WIN32 -static void s_rehash() -{ - struct sigaction act; - dorehash = 1; - act.sa_handler = s_rehash; - act.sa_flags = 0; - (void)sigemptyset(&act.sa_mask); - (void)sigaddset(&act.sa_mask, SIGHUP); - (void)sigaction(SIGHUP, &act, NULL); -} - -static void s_reloadcert() -{ - struct sigaction act; - doreloadcert = 1; - act.sa_handler = s_reloadcert; - act.sa_flags = 0; - (void)sigemptyset(&act.sa_mask); - (void)sigaddset(&act.sa_mask, SIGUSR1); - (void)sigaction(SIGUSR1, &act, NULL); -} -#endif // #ifndef _WIN32 - -void restart(const char *mesg) -{ - server_reboot(mesg); -} - -void s_restart() -{ - dorestart = 1; -#if 0 - static int restarting = 0; - - if (restarting == 0) { - /* - * Send (or attempt to) a dying scream to oper if present - */ - - restarting = 1; - server_reboot("SIGINT"); - } -#endif -} - - +extern void ignore_this_signal(); +extern void s_rehash(); +extern void s_reloadcert(); +extern void s_restart(); +extern void s_die(); #ifndef _WIN32 -/** Signal handler for signals which we ignore, - * like SIGPIPE ("Broken pipe") and SIGWINCH (terminal window changed) etc. - */ -void ignore_this_signal() -{ - struct sigaction act; - - act.sa_handler = ignore_this_signal; - act.sa_flags = 0; - (void)sigemptyset(&act.sa_mask); - (void)sigaddset(&act.sa_mask, SIGALRM); - (void)sigaddset(&act.sa_mask, SIGPIPE); - (void)sigaction(SIGALRM, &act, (struct sigaction *)NULL); - (void)sigaction(SIGPIPE, &act, (struct sigaction *)NULL); -#ifdef SIGWINCH - (void)sigaddset(&act.sa_mask, SIGWINCH); - (void)sigaction(SIGWINCH, &act, (struct sigaction *)NULL); -#endif -} -#endif /* #ifndef _WIN32 */ - - -void server_reboot(const char *mesg) -{ - int i; - Client *client; - unreal_log(ULOG_INFO, "main", "UNREALIRCD_RESTARTING", NULL, - "Restarting server: $reason", - log_data_string("reason", mesg)); - - list_for_each_entry(client, &lclient_list, lclient_node) - (void) send_queued(client); - - /* - * ** fd 0 must be 'preserved' if either the -d or -i options have - * ** been passed to us before restarting. - */ -#ifdef HAVE_SYSLOG - (void)closelog(); -#endif -#ifndef _WIN32 - for (i = 3; i < MAXCONNECTIONS; i++) - (void)close(i); - if (!(bootopt & (BOOT_TTY | BOOT_DEBUG))) - (void)close(2); - (void)close(1); - (void)close(0); - (void)execv(MYNAME, myargv); +// nix specific +extern char unreallogo[]; #else - close_connections(); - if (!IsService) - { - CleanUp(); - WinExec(cmdLine, SW_SHOWDEFAULT); - } -#endif - unload_all_modules(); -#ifdef _WIN32 - if (IsService) - { - SERVICE_STATUS status; - PROCESS_INFORMATION pi; - STARTUPINFO si; - char fname[MAX_PATH]; - memset(&status, 0, sizeof(status)); - memset(&si, 0, sizeof(si)); - IRCDStatus.dwCurrentState = SERVICE_STOP_PENDING; - SetServiceStatus(IRCDStatusHandle, &IRCDStatus); - GetModuleFileName(GetModuleHandle(NULL), fname, MAX_PATH); - CreateProcess(fname, "restartsvc", NULL, NULL, FALSE, - 0, NULL, NULL, &si, &pi); - IRCDStatus.dwCurrentState = SERVICE_STOPPED; - SetServiceStatus(IRCDStatusHandle, &IRCDStatus); - ExitProcess(0); - } - else +// windows specific +extern SERVICE_STATUS_HANDLE IRCDStatusHandle; +extern SERVICE_STATUS IRCDStatus; #endif - exit(-1); -} -MODVAR char *areason; +/* Forward declarations */ +void server_reboot(const char *); +void restart(const char *); +static void open_debugfile(), setup_signals(); EVENT(loop_event) { @@ -677,30 +501,19 @@ void detect_timeshift_and_warn(void) oldtimeofday = timeofday; } -/** Check if at least 'minimum' seconds passed by since last run. - * @param tv_old Pointer to a timeval struct to keep track of things. - * @param minimum The time specified in milliseconds (eg: 1000 for 1 second) - * @returns When 'minimum' msec passed 1 is returned and the time is reset, otherwise 0 is returned. - */ -int minimum_msec_since_last_run(struct timeval *tv_old, long minimum) +void SetupEvents(void) { - long v; - - if (tv_old->tv_sec == 0) - { - /* First call ever */ - tv_old->tv_sec = timeofday_tv.tv_sec; - tv_old->tv_usec = timeofday_tv.tv_usec; - return 0; - } - v = ((timeofday_tv.tv_sec - tv_old->tv_sec) * 1000) + ((timeofday_tv.tv_usec - tv_old->tv_usec)/1000); - if (v >= minimum) - { - tv_old->tv_sec = timeofday_tv.tv_sec; - tv_old->tv_usec = timeofday_tv.tv_usec; - return 1; - } - return 0; + /* Start events */ + EventAdd(NULL, "tunefile", save_tunefile, NULL, 300*1000, 0); + EventAdd(NULL, "garbage", garbage_collect, NULL, GARBAGE_COLLECT_EVERY*1000, 0); + EventAdd(NULL, "loop", loop_event, NULL, 1000, 0); + EventAdd(NULL, "unrealdns_removeoldrecords", unrealdns_removeoldrecords, NULL, 15000, 0); + EventAdd(NULL, "check_pings", check_pings, NULL, 1000, 0); + EventAdd(NULL, "check_deadsockets", check_deadsockets, NULL, 1000, 0); + EventAdd(NULL, "handshake_timeout", handshake_timeout, NULL, 1000, 0); + EventAdd(NULL, "tls_check_expiry", tls_check_expiry, NULL, (86400/2)*1000, 0); + EventAdd(NULL, "unrealdb_expire_secret_cache", unrealdb_expire_secret_cache, NULL, 61000, 0); + EventAdd(NULL, "throttling_check_expire", throttling_check_expire, NULL, 1000, 0); } /** The main function. This will call SocketLoop() once the server is ready. */ @@ -755,7 +568,6 @@ int InitUnrealIRCd(int argc, char *argv[]) SetErrorMode(SEM_FAILCRITICALERRORS); #endif #if !defined(_WIN32) && !defined(_AMIGA) - sbrk0 = (char *)sbrk((size_t)0); uid = getuid(); euid = geteuid(); gid = getgid(); @@ -1032,7 +844,6 @@ int InitUnrealIRCd(int argc, char *argv[]) initstats(); if (!loop.config_test) DeleteTempModules(); - booted = FALSE; #if !defined(_WIN32) && !defined(_AMIGA) && !defined(OSXTIGER) && DEFAULT_PERMISSIONS != 0 /* Hack to stop people from being able to read the config file */ (void)chmod(CPATH, DEFAULT_PERMISSIONS); @@ -1061,7 +872,6 @@ int InitUnrealIRCd(int argc, char *argv[]) } if (config_test() < 0) exit(-1); - booted = TRUE; load_tunefile(); make_umodestr(); SetListening(&me); diff --git a/src/ircd_vars.c b/src/ircd_vars.c new file mode 100644 index 000000000..6dec95055 --- /dev/null +++ b/src/ircd_vars.c @@ -0,0 +1,31 @@ +/************************************************************************ + * UnrealIRCd - Unreal Internet Relay Chat Daemon - src/ircd_vars.c + * (c) 2021- Bram Matthys and The UnrealIRCd team + * License: GPLv2 + */ +#include "unrealircd.h" + +/** @file + * @brief UnrealIRCd global variables of the IRCd + */ + +int SVSNOOP = 0; +time_t timeofday = 0; +struct timeval timeofday_tv; +int tainted = 0; +LoopStruct loop; +MODVAR IRCCounts irccounts; +MODVAR Client me; /* That's me */ +MODVAR char *me_hash; +char *configfile = NULL; /* Server configuration file */ +int debuglevel = 0; /* Server debug level */ +int bootopt = 0; /* Server boot option flags */ +char *debugmode = ""; /* -"- -"- -"- */ +int dorehash = 0; /**< Rehash server on next socket loop */ +int dorestart = 0; /**< Restart server on next socket loop */ +int doreloadcert = 0; /**< Reload TLS certificate on next socket loop */ +#ifndef _WIN32 +char **myargv; +#else +LPCSTR cmdLine; +#endif diff --git a/src/list.c b/src/list.c index d752fb823..9435e7554 100644 --- a/src/list.c +++ b/src/list.c @@ -44,6 +44,7 @@ MODVAR int numclients = 0; // TODO: Document whether servers are included or excluded in these lists... MODVAR struct list_head unknown_list; /**< Local clients in handshake (may become a user or server later) */ +MODVAR struct list_head control_list; /**< Local "control channel" clients */ MODVAR struct list_head lclient_list; /**< Local clients (users only, right?) */ MODVAR struct list_head client_list; /**< All clients - local and remote (not in handshake) */ MODVAR struct list_head server_list; /**< Locally connected servers */ @@ -71,6 +72,7 @@ void initlists(void) INIT_LIST_HEAD(&server_list); INIT_LIST_HEAD(&oper_list); INIT_LIST_HEAD(&unknown_list); + INIT_LIST_HEAD(&control_list); INIT_LIST_HEAD(&global_server_list); INIT_LIST_HEAD(&dead_list); diff --git a/src/log.c b/src/log.c index bfddcddce..51787a3cf 100644 --- a/src/log.c +++ b/src/log.c @@ -1004,7 +1004,6 @@ static NameValue log_colors_terminal[] = { { ULOG_ERROR, "\033[91m" }, { ULOG_FATAL, "\033[95m" }, }; -#define TERMINAL_COLOR_RESET "\033[0m" const char *log_level_irc_color(LogLevel loglevel) { @@ -1635,6 +1634,25 @@ void do_unreal_log_remote(LogLevel loglevel, const char *subsystem, const char * do_unreal_log_remote_deliver(loglevel, subsystem, event_id, msg, json_serialized); } +/** Send server notices to control channel */ +void do_unreal_log_control(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server) +{ + Client *client; + MultiLine *m; + + if (!loop.booted) + return; + + /* Never send these */ + if (!strcmp(subsystem, "rawtraffic")) + return; + + list_for_each_entry(client, &control_list, lclient_node) + if (IsMonitorRehash(client)) + for (m = msg; m; m = m->next) + sendto_one(client, NULL, "REPLY [%s] %s", log_level_valtostring(loglevel), m->line); +} + void do_unreal_log_free_args(va_list vl) { LogData *d; @@ -1815,6 +1833,10 @@ void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char from_server = find_server(str, NULL); if (from_server == NULL) from_server = &me; + + if ((loop.rehashing == 2) || !strcmp(subsystem, "config")) + do_unreal_log_control(loglevel, subsystem, event_id, mmsg, json_serialized, from_server); + do_unreal_log_opers(loglevel, subsystem, event_id, mmsg, json_serialized, from_server); do_unreal_log_channels(loglevel, subsystem, event_id, mmsg, json_serialized, from_server); diff --git a/src/misc.c b/src/misc.c index 27c4f3631..8ebde1667 100644 --- a/src/misc.c +++ b/src/misc.c @@ -623,8 +623,8 @@ void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, cons if (client->local->fd >= 0 && !IsConnecting(client)) { - sendto_one(client, NULL, "ERROR :Closing Link: %s (%s)", - get_client_name(client, FALSE), comment); + if (!IsControl(client)) + sendto_one(client, NULL, "ERROR :Closing Link: %s (%s)", get_client_name(client, FALSE), comment); } close_connection(client); } @@ -2369,3 +2369,172 @@ void addlettertodynamicstringsorted(char **str, char letter) safe_free_raw(*str); *str = newbuf; } + +void s_die() +{ +#ifdef _WIN32 + Client *client; + if (!IsService) + { + loop.terminating = 1; + unload_all_modules(); + + list_for_each_entry(client, &lclient_list, lclient_node) + (void) send_queued(client); + + exit(-1); + } + else { + SERVICE_STATUS status; + SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_STOP); + ControlService(hService, SERVICE_CONTROL_STOP, &status); + } +#else + loop.terminating = 1; + unload_all_modules(); + unlink(conf_files ? conf_files->pid_file : IRCD_PIDFILE); + exit(0); +#endif +} + +#ifndef _WIN32 +void s_rehash() +{ + struct sigaction act; + dorehash = 1; + act.sa_handler = s_rehash; + act.sa_flags = 0; + (void)sigemptyset(&act.sa_mask); + (void)sigaddset(&act.sa_mask, SIGHUP); + (void)sigaction(SIGHUP, &act, NULL); +} + +void s_reloadcert() +{ + struct sigaction act; + doreloadcert = 1; + act.sa_handler = s_reloadcert; + act.sa_flags = 0; + (void)sigemptyset(&act.sa_mask); + (void)sigaddset(&act.sa_mask, SIGUSR1); + (void)sigaction(SIGUSR1, &act, NULL); +} +#endif // #ifndef _WIN32 + +void restart(const char *mesg) +{ + server_reboot(mesg); +} + +void s_restart() +{ + dorestart = 1; +} + +#ifndef _WIN32 +/** Signal handler for signals which we ignore, + * like SIGPIPE ("Broken pipe") and SIGWINCH (terminal window changed) etc. + */ +void ignore_this_signal() +{ + struct sigaction act; + + act.sa_handler = ignore_this_signal; + act.sa_flags = 0; + (void)sigemptyset(&act.sa_mask); + (void)sigaddset(&act.sa_mask, SIGALRM); + (void)sigaddset(&act.sa_mask, SIGPIPE); + (void)sigaction(SIGALRM, &act, (struct sigaction *)NULL); + (void)sigaction(SIGPIPE, &act, (struct sigaction *)NULL); +#ifdef SIGWINCH + (void)sigaddset(&act.sa_mask, SIGWINCH); + (void)sigaction(SIGWINCH, &act, (struct sigaction *)NULL); +#endif +} +#endif /* #ifndef _WIN32 */ + + +void server_reboot(const char *mesg) +{ + int i; + Client *client; + unreal_log(ULOG_INFO, "main", "UNREALIRCD_RESTARTING", NULL, + "Restarting server: $reason", + log_data_string("reason", mesg)); + + list_for_each_entry(client, &lclient_list, lclient_node) + (void) send_queued(client); + + /* + * ** fd 0 must be 'preserved' if either the -d or -i options have + * ** been passed to us before restarting. + */ +#ifdef HAVE_SYSLOG + (void)closelog(); +#endif +#ifndef _WIN32 + for (i = 3; i < MAXCONNECTIONS; i++) + (void)close(i); + if (!(bootopt & (BOOT_TTY | BOOT_DEBUG))) + (void)close(2); + (void)close(1); + (void)close(0); + (void)execv(MYNAME, myargv); +#else + close_connections(); + if (!IsService) + { + CleanUp(); + WinExec(cmdLine, SW_SHOWDEFAULT); + } +#endif + unload_all_modules(); +#ifdef _WIN32 + if (IsService) + { + SERVICE_STATUS status; + PROCESS_INFORMATION pi; + STARTUPINFO si; + char fname[MAX_PATH]; + memset(&status, 0, sizeof(status)); + memset(&si, 0, sizeof(si)); + IRCDStatus.dwCurrentState = SERVICE_STOP_PENDING; + SetServiceStatus(IRCDStatusHandle, &IRCDStatus); + GetModuleFileName(GetModuleHandle(NULL), fname, MAX_PATH); + CreateProcess(fname, "restartsvc", NULL, NULL, FALSE, + 0, NULL, NULL, &si, &pi); + IRCDStatus.dwCurrentState = SERVICE_STOPPED; + SetServiceStatus(IRCDStatusHandle, &IRCDStatus); + ExitProcess(0); + } + else +#endif + exit(-1); +} + +/** Check if at least 'minimum' seconds passed by since last run. + * @param tv_old Pointer to a timeval struct to keep track of things. + * @param minimum The time specified in milliseconds (eg: 1000 for 1 second) + * @returns When 'minimum' msec passed 1 is returned and the time is reset, otherwise 0 is returned. + */ +int minimum_msec_since_last_run(struct timeval *tv_old, long minimum) +{ + long v; + + if (tv_old->tv_sec == 0) + { + /* First call ever */ + tv_old->tv_sec = timeofday_tv.tv_sec; + tv_old->tv_usec = timeofday_tv.tv_usec; + return 0; + } + v = ((timeofday_tv.tv_sec - tv_old->tv_sec) * 1000) + ((timeofday_tv.tv_usec - tv_old->tv_usec)/1000); + if (v >= minimum) + { + tv_old->tv_sec = timeofday_tv.tv_sec; + tv_old->tv_usec = timeofday_tv.tv_usec; + return 1; + } + return 0; +} diff --git a/src/parse.c b/src/parse.c index 473453acf..2b9f884d7 100644 --- a/src/parse.c +++ b/src/parse.c @@ -104,7 +104,9 @@ void parse_client_queued(Client *client) return; /* we delay processing of data until identd has replied */ if (!IsUser(client) && !IsServer(client) && (iConf.handshake_delay > 0) && - !IsNoHandshakeDelay(client) && (TStime() - client->local->creationtime < iConf.handshake_delay)) + !IsNoHandshakeDelay(client) && + !IsControl(client) && + (TStime() - client->local->creationtime < iConf.handshake_delay)) { return; /* we delay processing of data until set::handshake-delay is reached */ } @@ -368,6 +370,8 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, int mtags_ flags |= CMD_VIRUS; if (IsOper(from)) flags |= CMD_OPER; + if (IsControl(from)) + flags |= CMD_CONTROL; cmptr = find_command(ch, flags); if (!cmptr || !(cmptr->flags & CMD_NOLAG)) { @@ -376,6 +380,12 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, int mtags_ } if (!cmptr) { + if (IsControl(from)) + { + sendto_one(from, NULL, "ERROR UNKNOWN_COMMAND: %s", ch); + sendto_one(from, NULL, "END 1"); + return; + } /* Don't send error messages in response to NOTICEs * in pre-connection state. */ diff --git a/src/proc_io_client.c b/src/proc_io_client.c new file mode 100644 index 000000000..538c02811 --- /dev/null +++ b/src/proc_io_client.c @@ -0,0 +1,181 @@ +/************************************************************************ + * UnrealIRCd - Unreal Internet Relay Chat Daemon - src/proc_io_client.c + * (c) 2022- Bram Matthys and The UnrealIRCd team + * + * See file AUTHORS in IRC package for additional names of + * the programmers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 1, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** @file + * @brief Inter-process I/O + */ +#include "unrealircd.h" + +int procio_client_connect(const char *file) +{ + int fd; + struct sockaddr_un addr; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strlcpy(addr.sun_path, file, sizeof(addr.sun_path)); + + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) + return -1; + + return fd; +} + +int procio_send(int fd, const char *command) +{ + char buf[512]; + int n; + snprintf(buf, sizeof(buf), "%s\r\n", command); + n = strlen(buf); + if (send(fd, buf, n, 0) != n) + return 0; + return 1; +} + +const char *recolor_logs(const char *str) +{ + static char retbuf[2048]; + char buf[2048], *p; + const char *color = NULL; + + strlcpy(buf, str, sizeof(buf)); + p = strchr(buf, ' '); + if ((*str != '[') || !p) + return str; + *p++ = '\0'; + + if (!strcmp(buf, "[debug]")) + color = log_level_terminal_color(ULOG_DEBUG); + else if (!strcmp(buf, "[info]")) + color = log_level_terminal_color(ULOG_INFO); + else if (!strcmp(buf, "[warning]")) + color = log_level_terminal_color(ULOG_WARNING); + else if (!strcmp(buf, "[error]")) + color = log_level_terminal_color(ULOG_ERROR); + else if (!strcmp(buf, "[fatal]")) + color = log_level_terminal_color(ULOG_FATAL); + else + color = log_level_terminal_color(ULOG_INVALID); + + snprintf(retbuf, sizeof(retbuf), "%s%s%s %s", + color, buf, TERMINAL_COLOR_RESET, p); + return retbuf; +} + +const char *recolor_split(const char *str) +{ + static char retbuf[2048]; + char buf[2048], *p; + const char *color = NULL; + + strlcpy(buf, str, sizeof(buf)); + p = strchr(buf, ' '); + if (!p) + return str; + *p++ = '\0'; + + snprintf(retbuf, sizeof(retbuf), "%s%s %s%s%s", + "\033[92m", buf, + "\033[93m", p, + TERMINAL_COLOR_RESET); + return retbuf; +} + +int procio_client(const char *command, int auto_color_logs) +{ + int fd; + char buf[READBUFSIZE]; + int n; + dbuf queue; + + if (auto_color_logs && !terminal_supports_color()) + auto_color_logs = 0; + + fd = procio_client_connect(CONTROLFILE); + if (fd < 0) + { + fprintf(stderr, "Could not connect to '%s': %s\n", + CONTROLFILE, strerror(errno)); + fprintf(stderr, "Maybe the IRC server is not running?\n"); + return -1; + } + + /* Expect the welcome message */ + memset(buf, 0, sizeof(buf)); + n = recv(fd, buf, sizeof(buf), 0); + if ((n < 0) || strncmp(buf, "READY", 4)) + { + fprintf(stderr, "Error while communicating to IRCd via '%s': %s\n" + "Maybe the IRC server is not running?\n", + CONTROLFILE, strerror(errno)); + return -1; + } + + if (!procio_send(fd, command)) + { + fprintf(stderr, "Error while sending command to IRCd via '%s'. Strange!\n", + CONTROLFILE); + return -1; + } + + *buf = '\0'; + dbuf_queue_init(&queue); + while(1) + { + n = recv(fd, buf, sizeof(buf)-1, 0); + if (n <= 0) + break; + buf[n] = '\0'; /* terminate the string */ + dbuf_put(&queue, buf, n); + + /* And try to read all complete lines: */ + do + { + n = dbuf_getmsg(&queue, buf); + if (n > 0) + { + if (!strncmp(buf, "REPLY ", 6)) + { + char *reply = buf+6; + if (auto_color_logs == 0) + printf("%s\n", reply); + else if (auto_color_logs == 1) + printf("%s\n", recolor_logs(reply)); + else + printf("%s\n", recolor_split(reply)); + } else + if (!strncmp(buf, "END ", 4)) + { + int exitcode = atoi(buf+4); + close(fd); + return exitcode; + } + } + } while(n > 0); + } + + return 0; /* zero is good */ +} diff --git a/src/proc_io_server.c b/src/proc_io_server.c new file mode 100644 index 000000000..d19065180 --- /dev/null +++ b/src/proc_io_server.c @@ -0,0 +1,133 @@ +/************************************************************************ + * UnrealIRCd - Unreal Internet Relay Chat Daemon - src/proc_io_server.c + * (c) 2022- Bram Matthys and The UnrealIRCd team + * + * See file AUTHORS in IRC package for additional names of + * the programmers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 1, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** @file + * @brief Inter-process I/O + */ +#include "unrealircd.h" +#include + +CMD_FUNC(procio_status); +CMD_FUNC(procio_rehash); +CMD_FUNC(procio_exit); +CMD_FUNC(procio_help); + +/** Create the unrealircd.ctl socket (server-side) */ +void add_proc_io_server(void) +{ + ConfigItem_listen *listener = safe_alloc(sizeof(ConfigItem_listen)); + safe_strdup(listener->file, CONTROLFILE); + listener->socket_type = SOCKET_TYPE_UNIX; + listener->options = LISTENER_CONTROL; + listener->fd = -1; + AddListItem(listener, conf_listen); + if (add_listener(listener) == -1) + exit(-1); + CommandAdd(NULL, "STATUS", procio_status, MAXPARA, CMD_CONTROL); + CommandAdd(NULL, "REHASH", procio_rehash, MAXPARA, CMD_CONTROL); + CommandAdd(NULL, "EXIT", procio_exit, MAXPARA, CMD_CONTROL); + CommandAdd(NULL, "HELP", procio_help, MAXPARA, CMD_CONTROL); +} + +/** Start of "control channel" client handshake - this is minimal + * @param client The client + */ +void start_of_control_client_handshake(Client *client) +{ + sendto_one(client, NULL, "READY %s %s", me.name, version); + fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client); +} + +CMD_FUNC(procio_status) +{ + sendto_one(client, NULL, "REPLY servername %s", me.name); + sendto_one(client, NULL, "REPLY unrealircd_version %s", version); + sendto_one(client, NULL, "REPLY libssl_version %s", SSLeay_version(SSLEAY_VERSION)); + sendto_one(client, NULL, "REPLY libsodium_version %s", sodium_version_string()); +#ifdef USE_LIBCURL + sendto_one(client, NULL, "REPLY libcurl_version %s", curl_version()); +#endif + sendto_one(client, NULL, "REPLY libcares_version %s", ares_version(NULL)); + sendto_one(client, NULL, "REPLY libpcre2_version %s", pcre2_version()); + sendto_one(client, NULL, "REPLY global_clients %ld", (long)irccounts.clients); + sendto_one(client, NULL, "REPLY local_clients %ld", (long)irccounts.me_clients); + sendto_one(client, NULL, "REPLY operators %ld", (long)irccounts.operators); + sendto_one(client, NULL, "REPLY servers %ld", (long)irccounts.servers); + sendto_one(client, NULL, "REPLY channels %ld", (long)irccounts.channels); + sendto_one(client, NULL, "END 0"); +} + +CMD_FUNC(procio_rehash) +{ + if (loop.rehashing) + { + sendto_one(client, NULL, "REPLY ERROR: A rehash is already in progress"); + sendto_one(client, NULL, "END 1"); + return; + } + + + if (parv[1] && !strcmp(parv[1], "-tls")) + { + int ret; + SetMonitorRehash(client); + unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD_TLS", NULL, "Reloading all TLS related data (./unrealircd reloadtls)"); + ret = reinit_tls(); + sendto_one(client, NULL, "END %d", ret == 0 ? -1 : 0); + ClearMonitorRehash(client); + } else { + SetMonitorRehash(client); + request_rehash(client); + /* completion will go via procio_post_rehash() */ + } +} + +CMD_FUNC(procio_exit) +{ + sendto_one(client, NULL, "END 0"); + exit_client(client, NULL, ""); +} + +CMD_FUNC(procio_help) +{ + sendto_one(client, NULL, "REPLY Commands available:"); + sendto_one(client, NULL, "REPLY EXIT"); + sendto_one(client, NULL, "REPLY HELP"); + sendto_one(client, NULL, "REPLY REHASH"); + sendto_one(client, NULL, "REPLY STATUS"); + sendto_one(client, NULL, "END 0"); +} + +/** Called upon REHASH completion (with or without failure) */ +void procio_post_rehash(int failure) +{ + Client *client; + + list_for_each_entry(client, &control_list, lclient_node) + { + if (IsMonitorRehash(client)) + { + sendto_one(client, NULL, "END %d", failure); + ClearMonitorRehash(client); + } + } +} diff --git a/src/send.c b/src/send.c index 13fbaf50f..aca8e52f4 100644 --- a/src/send.c +++ b/src/send.c @@ -395,7 +395,10 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick) * a bad idea, CPU-wise. So now we just mark the client indicating * that there is data to send. */ - mark_data_to_send(to); + if (IsControl(to)) + send_queued(to); /* send this one ASAP */ + else + mark_data_to_send(to); } /** A single function to send data to a channel. diff --git a/src/socket.c b/src/socket.c index 8c61f6f03..4eea04598 100644 --- a/src/socket.c +++ b/src/socket.c @@ -41,6 +41,7 @@ extern char *version; MODVAR time_t last_allinuse = 0; void start_of_normal_client_handshake(Client *client); +extern void start_of_control_client_handshake(Client *client); void proceed_normal_client_handshake(Client *client, struct hostent *he); /** Close all connections - only used when we terminate the server (eg: /DIE or SIGTERM) */ @@ -72,6 +73,15 @@ void close_connections(void) } } + list_for_each_entry(client, &control_list, lclient_node) + { + if (client->local->fd >= 0) + { + fd_close(client->local->fd); + client->local->fd = -2; + } + } + close_unbound_listeners(); OpenFiles = 0; @@ -101,10 +111,17 @@ static void listener_accept(int listener_fd, int revents, void *data) * Of course the underlying cause of this issue should be investigated, as this * is very much a workaround. */ - unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: $socket_error", - log_data_socket_error(listener->fd), - log_data_string("listen_ip", listener->ip), - log_data_integer("listen_port", listener->port)); + if (listener->file) + { + unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on file $file: $socket_error", + log_data_socket_error(listener->fd), + log_data_string("file", listener->file)); + } else { + unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: $socket_error", + log_data_socket_error(listener->fd), + log_data_string("listen_ip", listener->ip), + log_data_integer("listen_port", listener->port)); + } close_listener(listener); start_listeners(); } @@ -115,22 +132,48 @@ static void listener_accept(int listener_fd, int revents, void *data) set_sock_opts(cli_fd, NULL, listener->socket_type); - if ((++OpenFiles >= maxclients) || (cli_fd >= maxclients)) + /* Allow connections to the control socket, even if maxclients is reached */ + if (listener->options & LISTENER_CONTROL) { - ircstats.is_ref++; - if (last_allinuse < TStime() - 15) + /* ... but not unlimited ;) */ + if ((++OpenFiles >= maxclients+(CLIENTS_RESERVE/2)) || (cli_fd >= maxclients+(CLIENTS_RESERVE/2))) { - unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: All connections in use", - log_data_string("listen_ip", listener->ip), - log_data_integer("listen_port", listener->port)); - last_allinuse = TStime(); + ircstats.is_ref++; + if (last_allinuse < TStime() - 15) + { + unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on file $file: All connections in use", + log_data_string("file", listener->file)); + last_allinuse = TStime(); + } + fd_close(cli_fd); + --OpenFiles; + return; } + } else + { + if ((++OpenFiles >= maxclients) || (cli_fd >= maxclients)) + { + ircstats.is_ref++; + if (last_allinuse < TStime() - 15) + { + if (listener->file) + { + unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on file $file: All connections in use", + log_data_string("file", listener->file)); + } else { + unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: All connections in use", + log_data_string("listen_ip", listener->ip), + log_data_integer("listen_port", listener->port)); + } + last_allinuse = TStime(); + } - (void)send(cli_fd, "ERROR :All connections in use\r\n", 31, 0); + (void)send(cli_fd, "ERROR :All connections in use\r\n", 31, 0); - fd_close(cli_fd); - --OpenFiles; - return; + fd_close(cli_fd); + --OpenFiles; + return; + } } /* add_connection() may fail. we just don't care. */ @@ -831,29 +874,38 @@ refuse_client: SetLocalhost(client); } - /* Check set::max-unknown-connections-per-ip */ - if (check_too_many_unknown_connections(client)) + if (!(listener->options & LISTENER_CONTROL)) { - ircsnprintf(zlinebuf, sizeof(zlinebuf), - "ERROR :Closing Link: [%s] (Too many unknown connections from your IP)\r\n", - client->ip); - (void)send(fd, zlinebuf, strlen(zlinebuf), 0); - goto refuse_client; - } + /* Check set::max-unknown-connections-per-ip */ + if (check_too_many_unknown_connections(client)) + { + ircsnprintf(zlinebuf, sizeof(zlinebuf), + "ERROR :Closing Link: [%s] (Too many unknown connections from your IP)\r\n", + client->ip); + (void)send(fd, zlinebuf, strlen(zlinebuf), 0); + goto refuse_client; + } - /* Check (G)Z-Lines and set::anti-flood::connect-flood */ - if (check_banned(client, NO_EXIT_CLIENT)) - goto refuse_client; + /* Check (G)Z-Lines and set::anti-flood::connect-flood */ + if (check_banned(client, NO_EXIT_CLIENT)) + goto refuse_client; + } client->local->listener = listener; if (client->local->listener != NULL) client->local->listener->clients++; add_client_to_list(client); - irccounts.unknown++; - client->status = CLIENT_STATUS_UNKNOWN; - - list_add(&client->lclient_node, &unknown_list); + if (!(listener->options & LISTENER_CONTROL)) + { + /* IRC: unknown connection */ + irccounts.unknown++; + client->status = CLIENT_STATUS_UNKNOWN; + list_add(&client->lclient_node, &unknown_list); + } else { + client->status = CLIENT_STATUS_CONTROL; + list_add(&client->lclient_node, &control_list); + } if ((listener->options & LISTENER_TLS) && ctx_server) { @@ -878,7 +930,9 @@ refuse_client: goto refuse_client; } } - } + } else + if (listener->options & LISTENER_CONTROL) + start_of_control_client_handshake(client); else start_of_normal_client_handshake(client); return client; @@ -1096,6 +1150,20 @@ void process_clients(void) } } } while(&client->lclient_node != &unknown_list); + + do { + list_for_each_entry(client, &control_list, lclient_node) + { + if ((client->local->fd >= 0) && DBufLength(&client->local->recvQ) && !IsDead(client)) + { + parse_client_queued(client); + if (IsDead(client)) + break; + } + } + } while(&client->lclient_node != &control_list); + + } /** Check if 'ip' is a valid IP address, and if so what type. diff --git a/src/tls.c b/src/tls.c index c84ef020d..b7b242915 100644 --- a/src/tls.c +++ b/src/tls.c @@ -512,7 +512,7 @@ int init_tls(void) /** Reinitialize TLS server and client contexts - after REHASH -tls */ -void reinit_tls(void) +int reinit_tls(void) { SSL_CTX *tmp; ConfigItem_listen *listen; @@ -524,7 +524,7 @@ void reinit_tls(void) { unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL, "TLS Reload failed. See previous errors."); - return; + return 0; } if (ctx_server) SSL_CTX_free(ctx_server); @@ -535,7 +535,7 @@ void reinit_tls(void) { unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL, "TLS Reload failed at client context. See previous errors."); - return; + return 0; } if (ctx_client) SSL_CTX_free(ctx_client); @@ -551,7 +551,7 @@ void reinit_tls(void) { unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL, "TLS Reload failed at listen::tls-options. See previous errors."); - return; + return 0; } if (listen->ssl_ctx) SSL_CTX_free(listen->ssl_ctx); @@ -569,7 +569,7 @@ void reinit_tls(void) { unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL, "TLS Reload failed at sni::tls-options. See previous errors."); - return; + return 0; } if (sni->ssl_ctx) SSL_CTX_free(sni->ssl_ctx); @@ -588,13 +588,15 @@ void reinit_tls(void) unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL, "TLS Reload failed at link $servername due to outgoing::tls-options. See previous errors.", log_data_string("servername", link->servername)); - return; + return 0; } if (link->ssl_ctx) SSL_CTX_free(link->ssl_ctx); link->ssl_ctx = tmp; /* activate */ } } + + return 1; } /** Set SSL connection as nonblocking */ diff --git a/src/unrealircdctl.c b/src/unrealircdctl.c new file mode 100644 index 000000000..71f385f39 --- /dev/null +++ b/src/unrealircdctl.c @@ -0,0 +1,90 @@ +/************************************************************************ + * UnrealIRCd - Unreal Internet Relay Chat Daemon - src/unrealircdctl + * (c) 2022- Bram Matthys and The UnrealIRCd team + * + * See file AUTHORS in IRC package for additional names of + * the programmers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 1, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** @file + * @brief UnrealIRCd Control + */ +#include "unrealircd.h" + +extern int procio_client(const char *command, int auto_color_logs); + +void unrealircdctl_usage(const char *program_name) +{ + printf("Usage: %s