diff --git a/bbs/module.c b/bbs/module.c index 2010e39..0a1f6e3 100644 --- a/bbs/module.c +++ b/bbs/module.c @@ -1424,7 +1424,7 @@ static int reload_core(const char *name, int fd) } if (res) { /* Handler(s) failed to reload */ - bbs_dprintf(fd, "Full or partial reload failure\n"); + bbs_dprintf(fd, "%s\n", name ? "Reload failed" : "Full or partial reload failure"); return res; } /* Either something failed to reload, or we didn't actually reload anything (typo in target) */ diff --git a/doors/door_irc.c b/doors/door_irc.c index 1a1cab2..e9dfe6a 100644 --- a/doors/door_irc.c +++ b/doors/door_irc.c @@ -168,7 +168,7 @@ static int __chat_send(struct client_relay *client, struct participant *sender, RWLIST_RDLOCK(&client->participants); if (dorelay) { char prefix[32] = ""; - if (sender) { /* XXX Can this ever be NULL here? */ + if (sender) { /* Yes, sender can be NULL */ snprintf(prefix, sizeof(prefix), "%s@%u", bbs_username(sender->node->user), sender->node->id); } bbs_irc_client_msg(client->name, channel, prefix, "%s", msg); /* Actually send to IRC */ @@ -244,6 +244,14 @@ static int __attribute__ ((format (gnu_printf, 5, 6))) _chat_send(struct client_ } #pragma GCC diagnostic pop +static int print_help(struct bbs_node *node) +{ + bbs_node_writef(node, "== Embedded LBBS Chat Client ==\n"); + bbs_node_writef(node, "/help - Print help\n"); + bbs_node_writef(node, "/quit - Quit channel\n"); + return 0; +} + static int participant_relay(struct bbs_node *node, struct participant *p, const char *channel) { char buf[384]; @@ -297,6 +305,8 @@ static int participant_relay(struct bbs_node *node, struct participant *p, const /* strcasecmp will fail because the buffer has a LF at the end. Use strncasecmp, so anything starting with /help or /quit will technically match too */ if (STARTS_WITH(buf2, "/quit")) { break; /* Quit */ + } else if (STARTS_WITH(buf2, "/help")) { + print_help(node); } bbs_node_unbuffer(node); chat_send(c, p, channel, "<%s@%d> %s", bbs_username(node->user), node->id, buf2); /* buf2 already contains a newline from the user pressing ENTER, so don't add another one */ @@ -569,6 +579,8 @@ static int irc_single_client(struct bbs_node *node, char *constring, const char * Unless the user typed /quit, we can basically just build a message and send it to the channel. */ if (!strcasecmp(clientbuf, "/quit")) { break; + } else if (!strcasecmp(clientbuf, "/help")) { + print_help(node); } irc_client_msg(ircl, channel, clientbuf); /* Actually send to IRC */ diff --git a/include/mod_irc_client.h b/include/mod_irc_client.h index e1e6d2a..44a447b 100644 --- a/include/mod_irc_client.h +++ b/include/mod_irc_client.h @@ -61,10 +61,22 @@ int bbs_irc_client_send(const char *clientname, const char *fmt, ...) __attribut /*! * \brief Send a PRIVMSG message to an IRC channel using a mod_irc_client client (can be used anywhere in the BBS) + * \param sendingmod Sending module or NULL. * \param clientname Name of client configured in mod_irc_client.conf. NULL to use default (first one). * \param channel Channel name. * \param prefix. Sender prefix. * \param fmt printf-style format string * \retval 0 on success, -1 on failure */ -int bbs_irc_client_msg(const char *clientname, const char *channel, const char *prefix, const char *fmt, ...) __attribute__ ((format (gnu_printf, 4, 5))); +int __bbs_irc_client_msg(const void *sendingmod, const char *clientname, const char *channel, const char *prefix, const char *fmt, ...) __attribute__ ((format (gnu_printf, 5, 6))); + +/* This is very important. + * Previously, it was possible for loops to occur in mod_irc_client, + * where mod_irc_relay would call mod_irc_client, which would + * then call a callback for mod_irc_relay, boomeranging the message. + * It SHOULD call the other callbacks, but not this particular one, + * since this is where it came from (or through) in the first place. + * So, instead of passing rmsg->sendingmod, we pass this module + * as the source module reference, since that's what mod_irc_client + * will compare against to determine if it should skip a callback. */ +#define bbs_irc_client_msg(clientname, channel, prefix, fmt, ...) __bbs_irc_client_msg(BBS_MODULE_SELF, clientname, channel, prefix, fmt, ## __VA_ARGS__) diff --git a/include/net_irc.h b/include/net_irc.h index 68b719e..ecd4881 100644 --- a/include/net_irc.h +++ b/include/net_irc.h @@ -97,23 +97,29 @@ const char *irc_channel_topic(const char *channel); #define irc_relay_register(relay_send, nicklist, privmsg) __irc_relay_register(relay_send, nicklist, privmsg, BBS_MODULE_SELF) +struct irc_relay_message { + const char *channel; + const char *sender; /*!< Could be NULL, but will contain the sending user's nickname, if available. */ + const char *msg; + const void *sendingmod; /*!< Sending module */ +}; + /*! * \brief Register a relay function that will be used to receive messages sent on IRC channels for rebroadcast on other protocols. - * \param relay_send Callback function. Note that sender could be NULL, but will contain the sending user's nickname, if available. - * The function should return 0 to continue processing any other relays and nonzero to stop immediately. + * \param relay_send Callback function. The function should return 0 to continue processing any other relays and nonzero to stop immediately. * \param nicklist Callback function to obtain an IRC NAMES or WHO format of any users that should be displayed as channel members. NULL if not applicable. * If channel is non-NULL, function should return all members in channel. Otherwise, it should return the specified user. * \param privmsg Callback function to relay a private message to a user on another network. NULL if not applicable. * \param mod Module reference. * \retval 0 on success, -1 on failure */ -int __irc_relay_register(int (*relay_send)(const char *channel, const char *sender, const char *msg), +int __irc_relay_register(int (*relay_send)(struct irc_relay_message *rmsg), int (*nicklist)(struct bbs_node *node, int fd, int numeric, const char *requsername, const char *channel, const char *user), int (*privmsg)(const char *recipient, const char *sender, const char *user), void *mod); /*! \brief Unregister a relay previously registered using irc_relay_register */ -int irc_relay_unregister(int (*relay_send)(const char *channel, const char *sender, const char *msg)); +int irc_relay_unregister(int (*relay_send)(struct irc_relay_message *rmsg)); #define irc_relay_send_multiline(channel, modes, relayname, sender, hostsender, msg, transform, ircuser) _irc_relay_send_multiline(channel, modes, relayname, sender, hostsender, msg, transform, ircuser, BBS_MODULE_SELF) diff --git a/modules/mod_discord.c b/modules/mod_discord.c index b386e95..a765726 100644 --- a/modules/mod_discord.c +++ b/modules/mod_discord.c @@ -376,10 +376,17 @@ static void on_guild_members_chunk(struct discord *client, const struct discord_ bbs_debug(8, "User %d/%d: %lu => %s#%s [%s] (%s) - %d role(s)\n", i + 1, members->size, user->id, user->username, user->discriminator, S_IF(member->nick), presencestatus, roles ? roles->size : 0); u = add_user(user, event->guild_id, presence ? presence->status : "none", member->joined_at); -#if 0 +#ifdef DEBUG_PERMISSIONS + /*! \todo BUGBUG Permissions aren't set for ANY users, + * so we never think admins are admins. + * Thus, implicit membership for admins is hidden to us, + * we won't see them as being in the channel if they don't have + * an explicit membership (via user or role) in a channel. + * + * Related, guild owner ID is always 0 as well... */ { int j; - for (j = 0; j < 40; j++) { + for (j = 0; j < 64; j++) { if (member->permissions & (1 << j)) { bbs_debug(8, "User %s#%s has permission %d\n", user->username, user->discriminator, j); } @@ -400,7 +407,7 @@ static void on_guild_members_chunk(struct discord *client, const struct discord_ u->roles = calloc((size_t) roles->size, sizeof(*u->roles)); if (ALLOC_SUCCESS(u->roles)) { u->numroles = roles->size; - memcpy(u->roles, roles, sizeof(*u->roles)); + memcpy(u->roles, roles->array, sizeof(*u->roles)); } } } @@ -633,6 +640,8 @@ static void link_permissions(struct chan_pair *cp, struct discord_overwrites *ov * we can check if a user is in the channel by seeing if the user * is in cp->users, or if the user has any of the roles in cp->roles */ int j; + int allowed_members = 0, allowed_roles = 0; + RWLIST_WRLOCK(&cp->members); for (j = 0; j < overwrites->size; j++) { struct u64snowflake_entry *e; @@ -663,12 +672,15 @@ static void link_permissions(struct chan_pair *cp, struct discord_overwrites *ov if (overwrite->type == 1) { /* it's a user, explicitly */ RWLIST_INSERT_HEAD(&cp->members, e, entry); bbs_debug(3, "Granted permission for channel %s to user %lu\n", cp->discord_channel, e->id); + allowed_members++; } else { /* 0 = it's a role */ RWLIST_INSERT_HEAD(&cp->roles, e, entry); bbs_debug(3, "Granted permission for channel %s to role %lu\n", cp->discord_channel, e->id); + allowed_roles++; } } RWLIST_UNLOCK(&cp->members); + bbs_debug(3, "Channel %s permits %d user%s and %d role%s\n", cp->discord_channel, allowed_members, ESS(allowed_members), allowed_roles, ESS(allowed_roles)); } static void fetch_channels(struct discord *client, u64snowflake guild_id, u64snowflake guild_owner) @@ -993,8 +1005,11 @@ static int privmsg(const char *recipient, const char *sender, const char *msg) return 1; } -static int discord_send(const char *channel, const char *sender, const char *msg) +static int discord_send(struct irc_relay_message *rmsg) { + const char *channel = rmsg->channel; + const char *sender = rmsg->sender; + const char *msg = rmsg->msg; struct chan_pair *cp; int handled = 0; @@ -1342,12 +1357,12 @@ static int cli_discord_users(struct bbs_cli_args *a) int i = 0; struct user *u; - bbs_dprintf(a->fdout, "%-40s %7s %5s\n", "User", "Status", "Roles"); + bbs_dprintf(a->fdout, "%-48s %7s %5s\n", "User", "Status", "Roles"); RWLIST_RDLOCK(&users); RWLIST_TRAVERSE(&users, u, entry) { char buf[48]; - snprintf(buf, sizeof(buf), "%s#%s\n", u->username, u->discriminator); - bbs_dprintf(a->fdout, "%-40s %7s %5d" "%s\n", buf, status_str(u->status), u->numroles, u->admin ? " [Guild Admin]" : ""); + snprintf(buf, sizeof(buf), "%s#%s", u->username, u->discriminator); + bbs_dprintf(a->fdout, "%-48s %7s %5d" "%s\n", buf, status_str(u->status), u->numroles, u->admin ? " [Guild Admin]" : ""); i++; } RWLIST_UNLOCK(&users); @@ -1355,9 +1370,69 @@ static int cli_discord_users(struct bbs_cli_args *a) return 0; } +static int cli_discord_user(struct bbs_cli_args *a) +{ + int i; + struct user *u = find_user_by_username(a->argv[2]); + if (!u) { + bbs_dprintf(a->fdout, "No such user '%s'\n", a->argv[2]); + return 0; + } + bbs_dprintf(a->fdout, "%-13s: %lu\n", "User ID", u->user_id); + bbs_dprintf(a->fdout, "%-13s: %lu\n", "Guild ID", u->guild_id); + bbs_dprintf(a->fdout, "%-13s: %lu\n", "Joined Guild", u->guild_joined); + bbs_dprintf(a->fdout, "%-13s: %s#%s\n", "Username", u->username, u->discriminator); + bbs_dprintf(a->fdout, "%-13s: %s\n", "Admin", u->admin ? "Yes" : "No"); + bbs_dprintf(a->fdout, "%-13s: %s\n", "Status", status_str(u->status)); + bbs_dprintf(a->fdout, "%-13s: %d\n", "Roles", u->numroles); + for (i = 0; i < u->numroles; i++) { + bbs_dprintf(a->fdout," => %lu\n", u->roles[i]); + } + return 0; +} + +static int cli_discord_channel(struct bbs_cli_args *a) +{ + int num_users = 0, roles = 0; + struct u64snowflake_entry *e; + struct user *u; + struct chan_pair *cp; + + cp = find_mapping_irc(a->argv[2]); /* Look up by IRC channel name */ + if (!cp) { + bbs_dprintf(a->fdout, "No channel exists mapped to %s\n", a->argv[2]); + return 0; + } + + /* Rather than iterating cp->members, which only contains explicit user and role memberships, + * iterate over all users and use channel_contains_user, which checks by both explicit user membership, + * and implicit via role membership. */ + RWLIST_RDLOCK(&users); + RWLIST_TRAVERSE(&users, u, entry) { + if (!channel_contains_user(cp, u)) { + continue; /* User not in this channel */ + } + bbs_dprintf(a->fdout, "User %lu: %s#%s\n", u->user_id, u->username, u->discriminator); + num_users++; + } + RWLIST_UNLOCK(&users); + + RWLIST_RDLOCK(&cp->roles); + RWLIST_TRAVERSE(&cp->roles, e, entry) { + bbs_dprintf(a->fdout, "Role %lu\n", e->id); + roles++; + } + RWLIST_UNLOCK(&cp->roles); + + bbs_dprintf(a->fdout, "Channel %lu contains %d user%s and %d role%s\n", cp->channel_id, num_users, ESS(num_users), roles, ESS(roles)); + return 0; +} + static struct bbs_cli_entry cli_commands_discord[] = { BBS_CLI_COMMAND(cli_discord_mappings, "discord mappings", 2, "List Discord mappings", NULL), BBS_CLI_COMMAND(cli_discord_users, "discord users", 2, "List Discord users", NULL), + BBS_CLI_COMMAND(cli_discord_user, "discord user", 3, "Show info about Discord user", "discord user "), + BBS_CLI_COMMAND(cli_discord_channel, "discord channel", 3, "List Discord users in a mapped channel (by IRC channel name)", "discord channel "), }; static void *discord_relay(void *varg) @@ -1461,6 +1536,9 @@ static void generate_test_ping(void) .components = NULL, }; monitor_status = MONITOR_WAITING; +#ifdef DISCORD_MESSAGE_SUPPRESS_NOTIFICATIONS + params.flags = DISCORD_MESSAGE_SUPPRESS_NOTIFICATIONS; +#endif discord_create_message(discord_client, echochanid, ¶ms, NULL); } diff --git a/modules/mod_irc_client.c b/modules/mod_irc_client.c index f7d1d5e..027a00f 100644 --- a/modules/mod_irc_client.c +++ b/modules/mod_irc_client.c @@ -46,7 +46,7 @@ #endif struct bbs_irc_client { - RWLIST_ENTRY(bbs_irc_client) entry; /* Next client */ + RWLIST_ENTRY(bbs_irc_client) entry; /* Next client */ struct irc_client *client; /* IRC client */ pthread_t thread; /* Thread for relay */ char *msgscript; /* Message handler hook script (e.g. for bot actions) */ @@ -339,28 +339,80 @@ static const char *callback_msg_type_name(enum irc_callback_msg_type type) #define msg_relay_to_local(client, msg) msg_relay(client, RELAY_FROM_IRC, irc_msg_type(msg), irc_msg_channel(msg), irc_msg_prefix(msg), irc_msg_is_ctcp(msg), irc_msg_body(msg), strlen(S_IF(irc_msg_body(msg)))) -static int msg_relay(struct bbs_irc_client *client, enum relay_flags flags, enum irc_msg_type type, const char *channel, const char *prefix, int ctcp, const char *body, size_t len) +#define msg_relay(client, flags, type, channel, prefix, ctcp, body, len) __msg_relay(NULL, client, flags, type, channel, prefix, ctcp, body, len) + +static int __msg_relay(const void *sendingmod, struct bbs_irc_client *client, enum relay_flags flags, enum irc_msg_type type, const char *channel, const char *prefix, int ctcp, const char *body, size_t len) { int res = 0; const char *scope = flags & RELAY_TO_IRC ? flags & RELAY_FROM_IRC ? "to/from" : "to" : "from"; enum irc_callback_msg_type cb_type = callback_msg_type(type); - bbs_debug(7, "Broadcasting %s %s client %s, channel %s, prefix %s, CTCP: %d, length %lu: %.*s\n", callback_msg_type_name(cb_type), scope, client->name, channel, prefix, ctcp, len, (int) len, body); if (len > 512) { bbs_warning("%lu-byte message is too long to send to IRC\n", len); return -1; } + bbs_debug(7, "Broadcasting %s %s client %s, channel %s, prefix %s, CTCP: %d, length %lu: %.*s\n", callback_msg_type_name(cb_type), scope, client->name, channel, prefix, ctcp, len, (int) len, body); + /* Relay the message to everyone */ RWLIST_RDLOCK(&irc_clients); /* XXX Really just need to lock *this* client to prevent it from being removed, not all of them */ if (flags & RELAY_TO_IRC) { - res = irc_client_msg(client->client, channel, body); /* Actually send to IRC */ + /* Send this message to the remote IRC network */ + res = irc_client_msg(client->client, channel, body); } + + /* IRC message flow is basically this: + * door_irc + * ^ + * | + * Relay modules (mod_discord, mod_slack, etc.) <=> net_irc <=> mod_irc_relay <=> mod_irc_client + * + * So say a message comes in from mod_discord. The flow is as follows: + * + * mod_discord calls net_irc:irc_relay_send_multiline + * net_irc:irc_relay_send_multiline -> _irc_relay_send -> relay_broadcast + * ==> calls relay_send callback for each registered relay (skipping the sender, e.g. mod_discord) + * -> SKIP mod_discord + * -> mod_slack + * -> mod_irc_relay:netirc_cb + * |-> mod_irc_client:bbs_irc_client_msg + * |-> msg_relay + * --> irc_client_msg ---> remote IRC channel + * ==> calls msg_cb callback for each registered IRC client msg callback + * -> door_irc <--- this is correct + * -> mod_irc_relay <--- this is not correct + * + * Aha! Now notice we have gone mod_discord -> net_irc -> mod_irc_relay -> mod_irc_client -> mod_irc_relay... + * + * This isn't even a loop caused by the relays themselves, it's a loop caused by THIS function inappropriately + * "boomeranging" messages back around again. + * + * To prevent this, we now receive a module reference here that we check, and we do NOT send it back + * where it came from. mod_irc_relay passes a reference to itself when calling mod_irc_client:bbs_irc_client_msg, + * which we get here and allows us to skip that. + */ + if (flags & RELAY_FROM_IRC) { /* Only execute callback on messages received *FROM* IRC client, not messages *TO* it. */ + /* This message came from the remote IRC network. + * Send it to consumers: + * - door_irc + * - mod_irc_relay -> forward to IRC relay modules (e.g. mod_discord, mod_slack, etc.) + */ + if (strlen_zero(prefix)) { + /* This is NULL when door_irc calls bbs_irc_client_msg. + * Everything is correct for RELAY_TO_IRC, + * but in this case, we should use the same of the IRC username. */ + prefix = irc_client_username(client->client); + bbs_debug(7, "Prefix is empty, using '%s' as the prefix\n", prefix); + } if (client->callbacks) { struct irc_msg_callback *cb; RWLIST_RDLOCK(&msg_callbacks); RWLIST_TRAVERSE(&msg_callbacks, cb, entry) { + if (cb->mod == sendingmod) { + bbs_debug(7, "Not relaying message back where it came from (%p)\n", cb->mod); + continue; + } bbs_module_ref(cb->mod, 1); cb->msg_cb(client->name, cb_type, channel, prefix, ctcp, body); bbs_module_unref(cb->mod, 1); @@ -704,7 +756,7 @@ int __attribute__ ((format (gnu_printf, 2, 3))) bbs_irc_client_send(const char * return res; } -int __attribute__ ((format (gnu_printf, 4, 5))) bbs_irc_client_msg(const char *clientname, const char *channel, const char *prefix, const char *fmt, ...) +int __attribute__ ((format (gnu_printf, 5, 6))) __bbs_irc_client_msg(const void *sendingmod, const char *clientname, const char *channel, const char *prefix, const char *fmt, ...) { struct bbs_irc_client *client; char buf[IRC_MAX_MSG_LEN + 1]; @@ -738,25 +790,27 @@ int __attribute__ ((format (gnu_printf, 4, 5))) bbs_irc_client_msg(const char *c /* Send to IRC (and relay to anything local) */ - /*! \todo FIXME Should also include RELAY_FROM_IRC, - * but if we're sending a message from the local IRC network to an IRC channel, - * we shouldn't also process it as if it were originally received from IRC, - * which is what would happen now. - * Omitting this for now is more correct, but means messages from the local IRC server - * will get relayed to other actual IRC channels but not to door_irc, for example. - * Might work if we skip the sending module? Or maybe not??? + /* This part has been tricky to get right (and has been wrong several times). + * + * If the flags were RELAY_TO_IRC | RELAY_FROM_IRC, then messages from relay channels + * will get echoed back to the relay, which shouldn't happen. It was as if the + * message originated from IRC. + * + * If the flags were just RELAY_TO_IRC, then messages from door_irc don't get + * relayed to relay modules, and integration with the native net_irc IRC net + * is also broken. * - * This is delicate though: with just RELAY_TO_IRC, messages work between - * relay modules and door_irc but not with the native net_irc IRC network. + * Each flag combo was right in some scenarios and wrong in others. + * It depends on where the message came from. * - * XXX And RELAY_FROM_IRC is causing echoes on relay modules (e.g. mod_discord) - * so I'm removing that again. Need to figure this all out properly... - * it may be as simple as doing just RELAY_TO_IRC sometimes and both other times, - * but this is currently not 100% right. + * Now that this been more thoroughly analyze, we use both flags, but ensure that + * the message isn't inappropriately "looped" around if RELAY_FROM_IRC is present. + * We do this by passing a reference to mod_irc_relay in mod_irc_relay:netirc_cb, + * which allows us to prevent us from triggering that callback in __msg_relay. */ bbs_debug(7, "Relaying message: client '%s' channel '%s' prefix '%s'\n", clientname, channel, S_IF(prefix)); - res = msg_relay(client, RELAY_TO_IRC, IRC_CMD_PRIVMSG, channel, prefix, 0, buf, (size_t) len); /* No prefix */ + res = __msg_relay(sendingmod, client, RELAY_TO_IRC | RELAY_FROM_IRC, IRC_CMD_PRIVMSG, channel, prefix, 0, buf, (size_t) len); /* No prefix */ RWLIST_UNLOCK(&irc_clients); return res; diff --git a/modules/mod_irc_relay.c b/modules/mod_irc_relay.c index c112a1a..6546a1f 100644 --- a/modules/mod_irc_relay.c +++ b/modules/mod_irc_relay.c @@ -264,7 +264,7 @@ static void numeric_cb(const char *clientname, const char *prefix, int numeric, } /* Since we have to format it here anyways, do all the formatting here */ len = snprintf(mybuf, sizeof(mybuf), ":%s %d %s\n", S_OR(prefix, bbs_hostname()), numeric, msg); /* Use LF to delimit on the other end */ - bbs_debug(9, "Numeric %s: %s\n", prefix, mybuf); + bbs_debug(9, "Numeric %d: %s", numeric, mybuf); /* Already ends in LF */ if (nickpipe[0] == -1) { /* In theory, we could receive this callback at any point. If we didn't call it from wait_response, there's nothing to write to right now */ bbs_debug(9, "Ignoring numeric since we didn't ask for it\n"); return; @@ -629,12 +629,15 @@ static int nicklist(struct bbs_node *node, int fd, int numeric, const char *requ } /* Determine who's in the "real" channel using our client */ /* Only one request at a time, to prevent interleaving of responses */ - wait_response(node, fd, requsername, numeric, cp, cp->client2, channel, origchan, fullnick, nick); - /*! \todo BUGBUG Regarding the below comment, we need to return 1 - * to stop the traversal and indicate a user was found, which IS appropriate here. - * But in the cases where multiple relay modules could match, we should probably still - * return 1, and net_irc can traverse all the callbacks anyways. */ - return 1; /* Even though we matched, there could be matches in other relays */ + if (wait_response(node, fd, requsername, numeric, cp, cp->client2, channel, origchan, fullnick, nick)) { + /* Didn't have anything to offer from this module. */ + return 0; + } + /* We return 0 if there weren't any matches in this module, so that net_irc + * can continue invoking relay callbacks to see if anyone else matches. + * If we DO have some matches, we return 1, but there could still be matches + * in other relays, so net_irc will continue iterating. */ + return 1; } else if (!cp->client2) { /* It came from channel1, so provide names from channel1 */ bbs_debug(8, "Relaying nicknames from %s/%s\n", S_IF(cp->client1), cp->channel1); @@ -680,12 +683,15 @@ static int privmsg_cb(const char *recipient, const char *sender, const char *msg /* XXX Lots of duplicated code follows */ /*! \brief Callback for messages received on native IRC server (from our server to the channel) */ -static int netirc_cb(const char *channel, const char *sender, const char *msg) +static int netirc_cb(struct irc_relay_message *rmsg) { char fullmsg[510]; int ctcp = 0; struct chan_pair *cp; const char *clientname = NULL; /* Came from native IRC server. We need a pointer, can't use NULL directly. */ + const char *channel = rmsg->channel; + const char *sender = rmsg->sender; + const char *msg = rmsg->msg; /* Message came from the native IRC server. * This means that either client1 or client2 is NULL, and for the corresponding one, diff --git a/modules/mod_slack.c b/modules/mod_slack.c index 681e87f..5866df0 100644 --- a/modules/mod_slack.c +++ b/modules/mod_slack.c @@ -891,12 +891,15 @@ static inline void parse_parent_thread(struct slack_relay *relay, char *restrict *msg = word2 + 1; } -static int slack_send(const char *channel, const char *sender, const char *msg) +static int slack_send(struct irc_relay_message *rmsg) { char buf[532]; char ts[SLACK_TS_LENGTH + 2]; struct slack_relay *relay; const char *thread_ts = NULL; + const char *channel = rmsg->channel; + const char *sender = rmsg->sender; + const char *msg = rmsg->msg; struct chan_pair *cp = find_irc_channel(channel); if (!cp) { return 0; /* No relay exists for this channel */ diff --git a/nets/net_irc.c b/nets/net_irc.c index 5ae4bab..8c4af55 100644 --- a/nets/net_irc.c +++ b/nets/net_irc.c @@ -268,7 +268,7 @@ struct irc_channel { static RWLIST_HEAD_STATIC(channels, irc_channel); /* Container for all channels */ struct irc_relay { - int (*relay_send)(const char *channel, const char *sender, const char *msg); + int (*relay_send)(struct irc_relay_message *rmsg); int (*nicklist)(struct bbs_node *node, int fd, int numeric, const char *requsername, const char *channel, const char *user); int (*privmsg)(const char *recipient, const char *sender, const char *user); void *mod; @@ -322,7 +322,7 @@ static int add_operator(const char *name, const char *password) * in modules.conf to guarantee things will work properly. So, whatever... */ -int __irc_relay_register(int (*relay_send)(const char *channel, const char *sender, const char *msg), +int __irc_relay_register(int (*relay_send)(struct irc_relay_message *rmsg), int (*nicklist)(struct bbs_node *node, int fd, int numeric, const char *requsername, const char *channel, const char *user), int (*privmsg)(const char *recipient, const char *sender, const char *user), void *mod) @@ -357,7 +357,7 @@ int __irc_relay_register(int (*relay_send)(const char *channel, const char *send /* No need for a separate cleanup function since this module cannot be unloaded until all relays have unregistered */ -int irc_relay_unregister(int (*relay_send)(const char *channel, const char *sender, const char *msg)) +int irc_relay_unregister(int (*relay_send)(struct irc_relay_message *rmsg)) { struct irc_relay *relay; @@ -829,6 +829,13 @@ static void relay_broadcast(struct irc_channel *channel, struct irc_user *user, { /* Now, relay it to any other external integrations that may exist. */ struct irc_relay *relay; + struct irc_relay_message rmsg; + + memset(&rmsg, 0, sizeof(rmsg)); + rmsg.channel = channel->name; + rmsg.sender = user ? user->nickname : username ? username : NULL; + rmsg.msg = buf; + rmsg.sendingmod = sendingmod; if (channel->relay) { RWLIST_RDLOCK(&relays); @@ -841,7 +848,7 @@ static void relay_broadcast(struct irc_channel *channel, struct irc_user *user, continue; } bbs_module_ref(relay->mod, 4); - if (relay->relay_send(channel->name, user ? user->nickname : username ? username : NULL, buf)) { + if (relay->relay_send(&rmsg)) { bbs_module_unref(relay->mod, 4); break; } @@ -1982,12 +1989,14 @@ static void handle_who(struct irc_user *user, char *s) RWLIST_TRAVERSE(&relays, relay, entry) { if (relay->nicklist) { bbs_module_ref(relay->mod, 6); - res = relay->nicklist(user->node, user->wfd, 352, user->username, s, NULL); + /* Callbacks will return 0 for no match, 1 for match. + * However, multiple relay modules could have members in the channel, + * so we should not break early if nicklist callback returns 1; + * we need to check all the relays for matches. + * As long as at least one relay had members, we'll report success. */ + res += relay->nicklist(user->node, user->wfd, 352, user->username, s, NULL); bbs_module_unref(relay->mod, 6); } - if (res) { - break; - } } RWLIST_UNLOCK(&relays); } @@ -2001,6 +2010,8 @@ static void handle_who(struct irc_user *user, char *s) RWLIST_TRAVERSE(&relays, relay, entry) { if (relay->nicklist) { bbs_module_ref(relay->mod, 7); + /* Unlike the above case, a specific user can only exist in one relay, + * so as soon as we find a match, we can break. */ res = relay->nicklist(user->node, user->wfd, 352, user->username, NULL, s); bbs_module_unref(relay->mod, 7); }