Skip to content

Commit

Permalink
net_irc: Add WHOWAS command.
Browse files Browse the repository at this point in the history
  • Loading branch information
InterLinked1 committed Dec 3, 2023
1 parent de10464 commit cbc5471
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 10 deletions.
25 changes: 17 additions & 8 deletions modules/mod_auth_mysql.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,18 +349,19 @@ static int user_register(struct bbs_node *node)
#define MAX_REG_ATTEMPTS 6
int tries = MAX_REG_ATTEMPTS;

bbs_node_buffer(node); /* Buffer input so we can read line by line */

#define REG_FMT COLOR(COLOR_WHITE)
#define REG_QLEN 43
#define get_response(node, qlen, fmt, q, pollms, buf, len, tries, minlen, reqchars) bbs_get_response(node, qlen, fmt q, pollms, buf, len, tries, minlen, reqchars)

/* Registration notice */
NEG_RETURN(bbs_node_clear_screen(node));
NONPOS_RETURN(bbs_node_writef(node, "%s%s%s\n", COLOR(COLOR_PRIMARY), "New User Registration", COLOR(COLOR_WHITE))); /* Use white for the questions to stand out */

for (; tries > 0; tries -= 2) {
int correct;

/* Registration notice */
NEG_RETURN(bbs_node_clear_screen(node));
NONPOS_RETURN(bbs_node_writef(node, "%s%s%s\n", COLOR(COLOR_PRIMARY), "New User Registration", COLOR(COLOR_WHITE))); /* Use white for the questions to stand out */

bbs_node_buffer(node); /* Buffer input so we can read line by line */

/* No newlines necessary inbetween reads, since echo is on
* and input is terminated by a return. */
/* NONZERO_RETURN is a macro that returns x, so we must NOT call it directly with the function itself */
Expand Down Expand Up @@ -474,13 +475,21 @@ static int user_register(struct bbs_node *node)
return 1;
}
}
bbs_node_writef(node, "\n");

NEG_RETURN(bbs_node_writef(node, "%-*s", REG_QLEN, REG_FMT "Is the above information correct? "));
confirm:
NEG_RETURN(bbs_node_writef(node, "%-*s", REG_QLEN, REG_FMT "\rIs the above information correct? [Y/N] "));
correct = bbs_node_tread(node, MIN_MS(1));
if (tolower(correct) == 'y') {
break;
} else if (tolower(correct) == 'n') {
/* Not correct? Start over! */
continue;
} else if (correct > 0) { /* e.g. just pressed ENTER, since the gender prompt doesn't require that, don't consume that here */
/* Invalid */
bbs_node_ring_bell(node);
goto confirm; /* If we didn't use a goto in this way, we would need to break, and this is simpler to follow */
}
/* Not correct? Start over! */
}
if (tries <= 0) {
return 1;
Expand Down
3 changes: 2 additions & 1 deletion modules/mod_smtp_delivery_external.c
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ static void process_capabilities(int *restrict caps, int *restrict maxsendsize,
*maxsendsize = atoi(size);
}
}
} else if (!strcasecmp(capname, "CHUNKING") || !strcasecmp(capname, "SMTPUTF8") || !strcasecmp(capname, "VRFY") || !strcasecmp(capname, "ETRN") || !strcasecmp(capname, "DSN") || !strcasecmp(capname, "HELP")) {
} else if (!strcasecmp(capname, "CHUNKING") || !strcasecmp(capname, "SMTPUTF8") || !strcasecmp(capname, "BINARYMIME")
|| !strcasecmp(capname, "VRFY") || !strcasecmp(capname, "ETRN") || !strcasecmp(capname, "DSN") || !strcasecmp(capname, "HELP")) {
/* Don't care about */
} else if (!strcmp(capname, "PIPECONNECT")) {
/* Don't care about, at the moment, but could be used in the future to optimize:
Expand Down
112 changes: 111 additions & 1 deletion nets/net_irc.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,55 @@ struct irc_user {
/* Avoid using a flexible struct member since we'll probably strdup both the username and nickname beforehand anyways */
};

/*! \brief An IRC WHOWAS */
struct whowas {
const char *who;
time_t joined;
size_t nicklen;
RWLIST_ENTRY(whowas) entry;
char data[];
};

static RWLIST_HEAD_STATIC(whowas_users, whowas);

static void whowas_update(struct irc_user *user, int keep)
{
char buf[128];
int len;
struct whowas *w;

if (!user->registered || strlen_zero(user->nickname)) {
return;
}

/* If nickname already exists, remove and add again with updated info */
RWLIST_WRLOCK(&whowas_users);
RWLIST_TRAVERSE_SAFE_BEGIN(&whowas_users, w, entry) {
/* If the first word matches, it's the same nickname */
if (!strncasecmp(user->nickname, w->who, w->nicklen) && w->nicklen == strlen(user->nickname)) {
RWLIST_REMOVE_CURRENT(entry);
free(w);
break;
}
}
RWLIST_TRAVERSE_SAFE_END;

if (keep) {
/* Format of response RPL_WHOWASUSER (314) */
len = snprintf(buf, sizeof(buf), "%s %s %s *: %s", user->nickname, S_IF(user->username), S_IF(user->hostname), S_IF(user->realname));

w = calloc(1, sizeof(*w) + (size_t) len + 1);
if (ALLOC_SUCCESS(w)) {
w->joined = user->joined;
w->nicklen = strlen(user->nickname);
strcpy(w->data, buf); /* Safe */
w->who = w->data;
RWLIST_INSERT_HEAD(&whowas_users, w, entry);
}
}
RWLIST_UNLOCK(&whowas_users);
}

#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
/*! \brief Static user struct for ChanServ operations */
static struct irc_user user_chanserv = {
Expand Down Expand Up @@ -2023,6 +2072,42 @@ static void handle_whois(struct irc_user *user, char *s)
send_numeric2(user, 318, "%s :End of /WHOIS list\r\n", s); /* case must be preserved, so use s instead of u->nickname */
}

static void handle_whowas(struct irc_user *user, char *s)
{
char *username;
struct whowas *w;

username = strsep(&s, " ");
/* s (2nd argument) is now the # sessions back to go
* Ambassador will substitute "null" if this is not specified.
* Either way, we ignore this parameter, it's always effectively "1". */

bbs_debug(2, "WHOWAS lookup for '%s'\n", username);

RWLIST_RDLOCK(&whowas_users);
RWLIST_TRAVERSE(&whowas_users, w, entry) {
if (!strncasecmp(w->who, username, w->nicklen)) {
if (w->who[w->nicklen] == ' ') { /* Must be a complete match. */
char timebuf[56];
struct tm logdate;
send_numeric2(user, 314, "%s\r\n", w->who);
localtime_r(&w->joined, &logdate);
strftime(timebuf, sizeof(timebuf), "%a %b %e %Y %I:%M %P %Z", &logdate);
send_numeric2(user, 330, "%.*s %.*s\r\n", (int) w->nicklen, w->who, (int) w->nicklen, w->who);
send_numeric2(user, 312, "%.*s %s %s\r\n", (int) w->nicklen, w->who, irc_hostname, timebuf);
break;
}
}
}
RWLIST_UNLOCK(&whowas_users);

if (!w) {
send_numeric2(user, 406, "%s :There was no such nickname\r\n", username);
}

send_numeric2(user, 369, "%s :End of WHOWAS\r\n", username); /* case must be preserved, so use s */
}

static void handle_userhost(struct irc_user *user, char *s)
{
char buf[256];
Expand Down Expand Up @@ -2125,7 +2210,7 @@ static void handle_help(struct irc_user *user, char *s)
if (strlen_zero(s)) {
send_numeric(user, 704, "index * :** Help System **\r\n");
/*! \todo add handlers and dynamically generate this? */
send_numeric(user, 705, "index AWAY HELP INVITE JOIN KICK LIST MOTD NAMES NOTICE PART PING PONG PRIVMSG QUIT TOPIC USERHOST WHO WHOIS\r\n");
send_numeric(user, 705, "index AWAY HELP INVITE JOIN KICK LIST MOTD NAMES NOTICE PART PING PONG PRIVMSG QUIT TOPIC USERHOST WHO WHOIS WHOWAS\r\n");
send_numeric(user, 706, "index :End of /HELP\r\n");
return;
}
Expand Down Expand Up @@ -2784,6 +2869,7 @@ static int client_welcome(struct irc_user *user)

if (user->node->user) {
add_user(user);
whowas_update(user, 0);
}

RWLIST_RDLOCK(&users);
Expand Down Expand Up @@ -3212,7 +3298,11 @@ static void handle_client(struct irc_user *user)
/* WHO username or WHO #channel, mask patterns not supported */
handle_who(user, s);
} else if (!strcasecmp(command, "WHOIS")) {
REQUIRE_PARAMETER(user, s);
handle_whois(user, s);
} else if (!strcasecmp(command, "WHOWAS")) {
REQUIRE_PARAMETER(user, s);
handle_whowas(user, s);
} else if (!strcasecmp(command, "USERHOST")) {
handle_userhost(user, s);
} else if (!strcasecmp(command, "LIST")) {
Expand Down Expand Up @@ -3296,6 +3386,7 @@ static void handle_client(struct irc_user *user)
leave_all_channels(user, "QUIT", "Remote user closed the connection"); /* poll or read failed */
}
if (user->registered) {
whowas_update(user, 1);
unlink_user(user);
}
}
Expand Down Expand Up @@ -3436,6 +3527,23 @@ static int cli_irc_users(struct bbs_cli_args *a)
return 0;
}

static int cli_irc_whowas(struct bbs_cli_args *a)
{
struct whowas *w;

bbs_dprintf(a->fdout, "%-30s %s\n", "Joined", "WHOWAS");
RWLIST_RDLOCK(&whowas_users);
RWLIST_TRAVERSE(&whowas_users, w, entry) {
char timebuf[56];
struct tm logdate;
localtime_r(&w->joined, &logdate);
strftime(timebuf, sizeof(timebuf), "%a %b %e %Y %I:%M %P %Z", &logdate);
bbs_dprintf(a->fdout, "%-30s %s\n", timebuf, w->who);
}
RWLIST_UNLOCK(&whowas_users);
return 0;
}

static int cli_irc_channels(struct bbs_cli_args *a)
{
int i = 0;
Expand Down Expand Up @@ -3485,6 +3593,7 @@ static int cli_irc_members(struct bbs_cli_args *a)

static struct bbs_cli_entry cli_commands_irc[] = {
BBS_CLI_COMMAND(cli_irc_users, "irc users", 2, "List all IRC users", NULL),
BBS_CLI_COMMAND(cli_irc_whowas, "irc whowas", 2, "List all former IRC users", NULL),
BBS_CLI_COMMAND(cli_irc_channels, "irc chans", 2, "List all IRC channels", NULL),
BBS_CLI_COMMAND(cli_irc_members, "irc members", 3, "List all members in an IRC channel", "irc members <channel>"),
};
Expand Down Expand Up @@ -3679,6 +3788,7 @@ static int unload_module(void)
if (ircs_enabled) {
bbs_stop_tcp_listener(ircs_port);
}
RWLIST_WRLOCK_REMOVE_ALL(&whowas_users, entry, free);
destroy_channels();
destroy_operators();
pthread_mutex_destroy(&motd_lock);
Expand Down

0 comments on commit cbc5471

Please sign in to comment.