diff --git a/Makefile.windows b/Makefile.windows index 9d3ab6e01c..b8c405df5c 100644 --- a/Makefile.windows +++ b/Makefile.windows @@ -365,6 +365,7 @@ DLL_FILES=\ src/modules/svsnline.dll \ src/modules/svsnolag.dll \ src/modules/svsnoop.dll \ + src/modules/svso.dll \ src/modules/svspart.dll \ src/modules/svssilence.dll \ src/modules/svssno.dll \ @@ -1165,6 +1166,9 @@ src/modules/svsnolag.dll: src/modules/svsnolag.c $(INCLUDES) src/modules/svsnoop.dll: src/modules/svsnoop.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/svsnoop.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svsnoop.pdb $(MODLFLAGS) +src/modules/svso.dll: src/modules/svso.c $(INCLUDES) + $(CC) $(MODCFLAGS) src/modules/svso.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svso.pdb $(MODLFLAGS) + src/modules/svspart.dll: src/modules/svspart.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/svspart.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svspart.pdb $(MODLFLAGS) diff --git a/doc/conf/modules.default.conf b/doc/conf/modules.default.conf index 0b6b97d285..2994633bd8 100644 --- a/doc/conf/modules.default.conf +++ b/doc/conf/modules.default.conf @@ -137,6 +137,7 @@ loadmodule "svspart"; loadmodule "svssilence"; loadmodule "svssno"; loadmodule "svswatch"; +loadmodule "svso"; /*** Channel modes ***/ diff --git a/include/h.h b/include/h.h index ba95b0a288..d6f277d738 100644 --- a/include/h.h +++ b/include/h.h @@ -834,6 +834,7 @@ extern MODVAR char *(*tkl_uhost)(TKL *tkl, char *buf, size_t buflen, int options extern MODVAR void (*do_unreal_log_remote_deliver)(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized); extern MODVAR char *(*get_chmodes_for_user)(Client *client, const char *flags); extern MODVAR WhoisConfigDetails (*whois_get_policy)(Client *client, Client *target, const char *name); +extern MODVAR int (*make_oper)(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost); /* /Efuncs */ /* TLS functions */ @@ -859,6 +860,7 @@ extern MODVAR EVP_MD *sha1_function; extern MODVAR EVP_MD *md5_function; /* End of TLS functions */ +/* Default handlers for efunctions */ extern void parse_message_tags_default_handler(Client *client, char **str, MessageTag **mtag_list); extern const char *mtags_to_string_default_handler(MessageTag *m, Client *client); extern void *labeled_response_save_context_default_handler(void); @@ -868,6 +870,8 @@ extern int add_silence_default_handler(Client *client, const char *mask, int sen extern int del_silence_default_handler(Client *client, const char *mask); extern int is_silenced_default_handler(Client *client, Client *acptr); extern void do_unreal_log_remote_deliver_default_handler(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized); +extern int make_oper_default_handler(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost); +/* End of default handlers for efunctions */ extern MODVAR MOTDFile opermotd, svsmotd, motd, botmotd, smotd, rules; extern MODVAR int max_connection_count; diff --git a/include/modules.h b/include/modules.h index c5351440da..ccb04d116d 100644 --- a/include/modules.h +++ b/include/modules.h @@ -1683,9 +1683,10 @@ int hooktype_stats(Client *client, const char *str); * @param client The client * @param add 1 if the user becomes IRCOp, 0 if the user is no longer IRCOp * @param oper_block The name of the oper block used to oper up + * @param operclass The name of the operclass * @return The return value is ignored (use return 0) */ -int hooktype_local_oper(Client *client, int add, ConfigItem_oper *oper_block); +int hooktype_local_oper(Client *client, int add, const char *oper_block, const char *operclass); /** Called when a client sends a PASS command (function prototype for HOOKTYPE_LOCAL_PASS). * @param client The client @@ -2384,6 +2385,7 @@ enum EfunctionType { EFUNC_DO_UNREAL_LOG_REMOTE_DELIVER, EFUNC_GET_CHMODES_FOR_USER, EFUNC_WHOIS_GET_POLICY, + EFUNC_MAKE_OPER, }; /* Module flags */ diff --git a/src/api-efunctions.c b/src/api-efunctions.c index d6dbfdcf8c..89c9ab7da1 100644 --- a/src/api-efunctions.c +++ b/src/api-efunctions.c @@ -135,6 +135,7 @@ int (*watch_check)(Client *client, int reply, int (*watch_notify)(Client *client void (*do_unreal_log_remote_deliver)(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized); char *(*get_chmodes_for_user)(Client *client, const char *flags); WhoisConfigDetails (*whois_get_policy)(Client *client, Client *target, const char *name); +int (*make_oper)(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost); Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)()) { @@ -406,4 +407,5 @@ void efunctions_init(void) efunc_init_function(EFUNC_DO_UNREAL_LOG_REMOTE_DELIVER, do_unreal_log_remote_deliver, do_unreal_log_remote_deliver_default_handler); efunc_init_function(EFUNC_GET_CHMODES_FOR_USER, get_chmodes_for_user, NULL); efunc_init_function(EFUNC_WHOIS_GET_POLICY, whois_get_policy, NULL); + efunc_init_function(EFUNC_MAKE_OPER, make_oper, make_oper_default_handler); } diff --git a/src/misc.c b/src/misc.c index 2d381ef961..af90750f2d 100644 --- a/src/misc.c +++ b/src/misc.c @@ -1461,6 +1461,11 @@ void do_unreal_log_remote_deliver_default_handler(LogLevel loglevel, const char { } +int make_oper_default_handler(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost) +{ + return 0; +} + /** my_timegm: mktime()-like function which will use GMT/UTC. * Strangely enough there is no standard function for this. * On some *NIX OS's timegm() may be available, sometimes only diff --git a/src/modules/Makefile.in b/src/modules/Makefile.in index 739fe3edd1..1fd736792e 100644 --- a/src/modules/Makefile.in +++ b/src/modules/Makefile.in @@ -39,7 +39,7 @@ MODULES= \ sethost.so chghost.so chgident.so setname.so \ setident.so sdesc.so svsmode.so swhois.so\ svsmotd.so svsnline.so who_old.so whox.so mkpasswd.so \ - away.so svsnoop.so svsnick.so \ + away.so svsnoop.so svsnick.so svso.so \ chgname.so kill.so \ lag.so message.so oper.so pingpong.so \ quit.so sendumode.so sqline.so \ diff --git a/src/modules/mode.c b/src/modules/mode.c index 90dcc04d89..ca32bfa812 100644 --- a/src/modules/mode.c +++ b/src/modules/mode.c @@ -1338,7 +1338,7 @@ CMD_FUNC(_cmd_umode) { list_del(&client->special_node); if (MyUser(client)) - RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL); + RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL, NULL); remove_oper_privileges(client, 0); } diff --git a/src/modules/oper.c b/src/modules/oper.c index c2a6541952..84c9aa4eea 100644 --- a/src/modules/oper.c +++ b/src/modules/oper.c @@ -20,10 +20,6 @@ #include "unrealircd.h" -CMD_FUNC(cmd_oper); - - -/* Place includes here */ #define MSG_OPER "OPER" /* OPER */ ModuleHeader MOD_HEADER @@ -35,27 +31,35 @@ ModuleHeader MOD_HEADER "unrealircd-6", }; -/* This is called on module init, before Server Ready */ +/* Forward declarations */ +CMD_FUNC(cmd_oper); +int _make_oper(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost); + +MOD_TEST() +{ + MARK_AS_OFFICIAL_MODULE(modinfo); + EfunctionAdd(modinfo->handle, EFUNC_MAKE_OPER, _make_oper); + return MOD_SUCCESS; +} + MOD_INIT() { - CommandAdd(modinfo->handle, MSG_OPER, cmd_oper, MAXPARA, CMD_USER); MARK_AS_OFFICIAL_MODULE(modinfo); + CommandAdd(modinfo->handle, MSG_OPER, cmd_oper, MAXPARA, CMD_USER); return MOD_SUCCESS; } -/* Is first run when server is 100% ready */ MOD_LOAD() { return MOD_SUCCESS; } -/* Called when module is unloaded */ MOD_UNLOAD() { return MOD_SUCCESS; } -void set_oper_host(Client *client, char *host) +void set_oper_host(Client *client, const char *host) { char uhost[HOSTLEN + USERLEN + 1]; char *p; @@ -74,6 +78,88 @@ void set_oper_host(Client *client, char *host) SetHidden(client); } +int _make_oper(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost) +{ + long old_umodes = client->umodes & ALL_UMODES; + + /* Put in the right class (if any) */ + if (clientclass) + { + if (client->local->class) + client->local->class->clients--; + client->local->class = clientclass; + client->local->class->clients++; + } + + /* set oper user modes */ + client->umodes |= UMODE_OPER; + if (modes) + client->umodes |= modes; /* oper::modes */ + else + client->umodes |= OPER_MODES; /* set::modes-on-oper */ + + /* oper::vhost */ + if (vhost) + { + set_oper_host(client, vhost); + } else + if (IsHidden(client) && !client->user->virthost) + { + /* +x has just been set by modes-on-oper and no vhost. cloak the oper! */ + safe_strdup(client->user->virthost, client->user->cloakedhost); + } + + unreal_log(ULOG_INFO, "oper", "OPER_SUCCESS", client, + "$client.details is now an IRC Operator [oper-block: $oper_block] [operclass: $operclass]", + log_data_string("oper_block", operblock_name), + log_data_string("operclass", operclass)); + + /* set oper snomasks */ + if (snomask) + set_snomask(client, snomask); /* oper::snomask */ + else + set_snomask(client, OPER_SNOMASK); /* set::snomask-on-oper */ + + send_umode_out(client, 1, old_umodes); + if (client->user->snomask) + sendnumeric(client, RPL_SNOMASK, client->user->snomask); + + list_add(&client->special_node, &oper_list); + + RunHook(HOOKTYPE_LOCAL_OPER, client, 1, operblock_name, operclass); + + sendnumeric(client, RPL_YOUREOPER); + + /* Update statistics */ + if (IsInvisible(client) && !(old_umodes & UMODE_INVISIBLE)) + irccounts.invisible++; + if (IsOper(client) && !IsHideOper(client)) + irccounts.operators++; + + if (SHOWOPERMOTD == 1) + { + const char *args[1] = { NULL }; + do_cmd(client, NULL, "OPERMOTD", 1, args); + } + + if (!BadPtr(OPER_AUTO_JOIN_CHANS) && strcmp(OPER_AUTO_JOIN_CHANS, "0")) + { + char *chans = strdup(OPER_AUTO_JOIN_CHANS); + const char *args[3] = { + client->name, + chans, + NULL + }; + do_cmd(client, NULL, "JOIN", 3, args); + safe_free(chans); + /* Theoretically the oper may be killed on join. Would be fun, though */ + if (IsDead(client)) + return 0; + } + + return 1; +} + /* ** cmd_oper ** parv[1] = oper name @@ -83,7 +169,6 @@ CMD_FUNC(cmd_oper) { ConfigItem_oper *operblock; const char *operblock_name, *password; - long old_umodes = client->umodes & ALL_UMODES; if (!MyUser(client)) return; @@ -227,12 +312,6 @@ CMD_FUNC(cmd_oper) /* Store which oper block was used to become IRCOp (for maxlogins and whois) */ safe_strdup(client->user->operlogin, operblock->name); - /* Put in the right class */ - if (client->local->class) - client->local->class->clients--; - client->local->class = operblock->class; - client->local->class->clients++; - /* oper::swhois */ if (operblock->swhois) { @@ -241,67 +320,7 @@ CMD_FUNC(cmd_oper) swhois_add(client, "oper", -100, s->line, &me, NULL); } - /* set oper user modes */ - client->umodes |= UMODE_OPER; - if (operblock->modes) - client->umodes |= operblock->modes; /* oper::modes */ - else - client->umodes |= OPER_MODES; /* set::modes-on-oper */ - - /* oper::vhost */ - if (operblock->vhost) - { - set_oper_host(client, operblock->vhost); - } else - if (IsHidden(client) && !client->user->virthost) - { - /* +x has just been set by modes-on-oper and no vhost. cloak the oper! */ - safe_strdup(client->user->virthost, client->user->cloakedhost); - } - - unreal_log(ULOG_INFO, "oper", "OPER_SUCCESS", client, - "$client.details is now an IRC Operator [oper-block: $oper_block]", - log_data_string("oper_block", parv[1])); - - /* set oper snomasks */ - if (operblock->snomask) - set_snomask(client, operblock->snomask); /* oper::snomask */ - else - set_snomask(client, OPER_SNOMASK); /* set::snomask-on-oper */ - - send_umode_out(client, 1, old_umodes); - if (client->user->snomask) - sendnumeric(client, RPL_SNOMASK, client->user->snomask); - - list_add(&client->special_node, &oper_list); - - RunHook(HOOKTYPE_LOCAL_OPER, client, 1, operblock); - - sendnumeric(client, RPL_YOUREOPER); - - /* Update statistics */ - if (IsInvisible(client) && !(old_umodes & UMODE_INVISIBLE)) - irccounts.invisible++; - if (IsOper(client) && !IsHideOper(client)) - irccounts.operators++; - - if (SHOWOPERMOTD == 1) - do_cmd(client, NULL, "OPERMOTD", parc, parv); - - if (!BadPtr(OPER_AUTO_JOIN_CHANS) && strcmp(OPER_AUTO_JOIN_CHANS, "0")) - { - char *chans = strdup(OPER_AUTO_JOIN_CHANS); - const char *args[3] = { - client->name, - chans, - NULL - }; - do_cmd(client, NULL, "JOIN", 3, args); - safe_free(chans); - /* Theoretically the oper may be killed on join. Would be fun, though */ - if (IsDead(client)) - return; - } + make_oper(client, operblock->name, operblock->operclass, operblock->class, operblock->modes, operblock->snomask, operblock->vhost); /* set::plaintext-policy::oper 'warn' */ if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_oper == POLICY_WARN)) diff --git a/src/modules/operinfo.c b/src/modules/operinfo.c index 7571c35105..8d4a2c6d6c 100644 --- a/src/modules/operinfo.c +++ b/src/modules/operinfo.c @@ -16,7 +16,7 @@ ModuleHeader MOD_HEADER }; /* Forward declarations */ -int operinfo_local_oper(Client *client, int up, ConfigItem_oper *oper_block); +int operinfo_local_oper(Client *client, int up, const char *oper_block, const char *operclass); void operinfo_free(ModData *m); const char *operinfo_serialize(ModData *m); void operinfo_unserialize(const char *str, ModData *m); @@ -68,12 +68,12 @@ MOD_UNLOAD() return MOD_SUCCESS; } -int operinfo_local_oper(Client *client, int up, ConfigItem_oper *oper_block) +int operinfo_local_oper(Client *client, int up, const char *oper_block, const char *operclass) { if (up) { - moddata_client_set(client, "operlogin", oper_block->name); - moddata_client_set(client, "operclass", oper_block->operclass); + moddata_client_set(client, "operlogin", oper_block); + moddata_client_set(client, "operclass", operclass); } else { moddata_client_set(client, "operlogin", NULL); moddata_client_set(client, "operclass", NULL); diff --git a/src/modules/svsmode.c b/src/modules/svsmode.c index f5348473d4..de30483654 100644 --- a/src/modules/svsmode.c +++ b/src/modules/svsmode.c @@ -395,7 +395,7 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, const char *pa * so remove all oper-only modes and snomasks. */ if (MyUser(client)) - RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL); + RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL, NULL); remove_oper_privileges(target, 0); } goto setmodex; diff --git a/src/modules/svsnoop.c b/src/modules/svsnoop.c index c86d022328..25de4d5a85 100644 --- a/src/modules/svsnoop.c +++ b/src/modules/svsnoop.c @@ -82,7 +82,7 @@ CMD_FUNC(cmd_svsnoop) if (!list_empty(&acptr->special_node)) list_del(&acptr->special_node); - RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL); + RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL, NULL); remove_oper_privileges(acptr, 1); } } diff --git a/src/modules/svso.c b/src/modules/svso.c new file mode 100644 index 0000000000..316711dd3a --- /dev/null +++ b/src/modules/svso.c @@ -0,0 +1,138 @@ +/* src/modules/svso.c - Grant IRCOp rights (for Services) + * (C) Copyright 2022 Bram Matthys (Syzop) and the UnrealIRCd team + * License: GPLv2 + */ +#include "unrealircd.h" + +ModuleHeader MOD_HEADER += { + "svso", + "6.0.0", + "Grant oper privileges via SVSO services command", + "UnrealIRCd Team", + "unrealircd-6", +}; + +/* Forward declarations */ +CMD_FUNC(cmd_svso); + +MOD_INIT() +{ + MARK_AS_OFFICIAL_MODULE(modinfo); + + CommandAdd(modinfo->handle, "SVSO", cmd_svso, MAXPARA, CMD_USER|CMD_SERVER); + return MOD_SUCCESS; +} + +MOD_LOAD() +{ + return MOD_SUCCESS; +} + +MOD_UNLOAD() +{ + return MOD_SUCCESS; +} + +/* Syntax: SVSO + * All these parameters need to be set, you cannot leave any of them out, + * HOWEVER some can be set to "-" to skip setting them, this is true for: + * , , , + * + * In UnrealIRCd the will be prefixed by "services:" if not already + * present. It is up to you to include or omit it. + */ +CMD_FUNC(cmd_svso) +{ + Client *acptr; + char oper_account[64]; + const char *operclass; + const char *clientclass; + ConfigItem_class *clientclass_c; + const char *modes; + long modes_i = 0; + const char *snomask; + const char *vhost; + + if (!IsULine(client)) + return; + + if ((parc < 8) || BadPtr(parv[7])) + { + sendnumeric(client, ERR_NEEDMOREPARAMS, "SVSO"); + return; + } + + operclass = parv[3]; + clientclass = parv[4]; + modes = parv[5]; + snomask = parv[6]; + vhost = parv[7]; + + acptr = find_user(parv[1], NULL); + if (!acptr) + { + sendnumeric(client, ERR_NOSUCHNICK, parv[1]); + return; + } + + if (!MyUser(acptr)) + { + /* Forward it to the correct server, and we are done... */ + sendto_one(acptr, recv_mtags, ":%s SVSO %s %s %s %s %s %s %s", + client->name, acptr->id, parv[2], parv[3], parv[4], parv[5], parv[6], parv[7]); + return; + } + + /* CAVEAT ALERT ! + * Don't mix up 'client' and 'acptr' below... + * 'client' is the server or services pseudouser that requests the change + * 'acptr' is the person that will be made OPER + */ + + /* If we get here, we validate the request and then make the user oper. */ + if (!find_operclass(operclass)) + { + sendnumeric(client, ERR_CANNOTDOCOMMAND, "SVSO", "Operclass does not exist"); + return; + } + + /* Set any items to NULL if they are skipped (on request) */ + if (!strcmp(clientclass, "-")) + clientclass = NULL; + if (!strcmp(modes, "-")) + modes = NULL; + if (!strcmp(snomask, "-")) + snomask = NULL; + if (!strcmp(vhost, "-")) + vhost = NULL; + + /* First, maybe the user is oper already? Then de-oper them.. */ + if (IsOper(acptr)) + { + int was_hidden_oper = IsHideOper(acptr) ? 1 : 0; + + list_del(&acptr->special_node); + RunHook(HOOKTYPE_LOCAL_OPER, acptr, 0, NULL, NULL); + remove_oper_privileges(acptr, 1); + + if (!was_hidden_oper) + irccounts.operators--; + VERIFY_OPERCOUNT(acptr, "svso"); + + } + + /* Prefix the oper block name with "services:" if it hasn't already */ + if (!strncmp(parv[2], "services:", 9)) + strlcpy(oper_account, parv[2], sizeof(oper_account)); + else + snprintf(oper_account, sizeof(oper_account), "services:%s", parv[2]); + + /* These needs to be looked up... */ + clientclass_c = find_class(clientclass); /* NULL is fine! */ + if (modes) + modes_i = set_usermode(modes); + + if (!make_oper(acptr, oper_account, operclass, clientclass_c, modes_i, snomask, vhost)) + sendnumeric(client, ERR_CANNOTDOCOMMAND, "SVSO", "Failed to make user oper"); +} diff --git a/src/operclass.c b/src/operclass.c index 0eaaa1e64c..028a1411d3 100644 --- a/src/operclass.c +++ b/src/operclass.c @@ -282,6 +282,7 @@ OperPermission ValidatePermissionsForPathEx(OperClassACL *acl, OperClassACLPath OperPermission ValidatePermissionsForPath(const char *path, Client *client, Client *victim, Channel *channel, const void *extra) { ConfigItem_oper *ce_oper; + const char *operclass; ConfigItem_operclass *ce_operClass; OperClass *oc = NULL; OperClassACLPath *operPath; @@ -299,14 +300,17 @@ OperPermission ValidatePermissionsForPath(const char *path, Client *client, Clie ce_oper = find_oper(client->user->operlogin); if (!ce_oper) { - return OPER_DENY; + operclass = moddata_client_get(client, "operclass"); + if (!operclass) + return OPER_DENY; + } else + { + operclass = ce_oper->operclass; } - - ce_operClass = find_operclass(ce_oper->operclass); + + ce_operClass = find_operclass(operclass); if (!ce_operClass) - { return OPER_DENY; - } oc = ce_operClass->classStruct; operPath = OperClass_parsePath(path);