Skip to content

Commit

Permalink
mod_irc_client: Fix IRC relay loops.
Browse files Browse the repository at this point in the history
Usage of msg_relay has been buggy for some time now.
Not relaying to enough places would cause some places
to miss messages, while relaying everywhere would
loop messages around improperly. This is now fixed by
not relaying messages back to the relay module that
sent the message; some APIs have been expanded to
accomodate this information.

Now, all messages should be relayed from all possible
messages sources to all places that messages should
be relayed, without resulting in more messages than
there should be.

The irc_relay_message structure is technically not needed,
since rmsg->sendingmod is not currently used, but this cleans
up that API to allow for future expansion, if needed, in a
cleaner way.

Other IRC-related fixes and improvements:

* mod_irc_client: Auto prefix IRC username if needed.
* net_irc: Don't break early for NAMES replies.
* door_irc: Add basic help command.
* mod_discord: Fix not loading user roles properly.
* mod_discord: Add CLI commands to dump a channel or user.
  • Loading branch information
InterLinked1 committed Feb 1, 2024
1 parent 8ab0f7a commit f3b440e
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 50 deletions.
2 changes: 1 addition & 1 deletion bbs/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) */
Expand Down
14 changes: 13 additions & 1 deletion doors/door_irc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */

Expand Down
14 changes: 13 additions & 1 deletion include/mod_irc_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
14 changes: 10 additions & 4 deletions include/net_irc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
92 changes: 85 additions & 7 deletions modules/mod_discord.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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));
}
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -1342,22 +1357,82 @@ 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);
bbs_dprintf(a->fdout, "%d user%s\n", i, ESS(i));
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 <username>"),
BBS_CLI_COMMAND(cli_discord_channel, "discord channel", 3, "List Discord users in a mapped channel (by IRC channel name)", "discord channel <irc-chan-name>"),
};

static void *discord_relay(void *varg)
Expand Down Expand Up @@ -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, &params, NULL);
}

Expand Down
Loading

0 comments on commit f3b440e

Please sign in to comment.