From 5280e648d96fbcb20948f7470afdca8b38f80a44 Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Tue, 21 May 2019 22:45:32 +0100 Subject: Support multiple nick prefixes in channels by storing/relaying server 005/RPL_ISUPPORT messages and implementing the start of IRCv3 CAP negotiations (multi-prefix only at the moment). --- blabouncer.c | 158 ++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 119 insertions(+), 39 deletions(-) (limited to 'blabouncer.c') diff --git a/blabouncer.c b/blabouncer.c index e839477..401b5fd 100644 --- a/blabouncer.c +++ b/blabouncer.c @@ -67,6 +67,9 @@ struct ircdstrings { char greeting002[MAXDATASIZE]; char greeting003[MAXDATASIZE]; char greeting004[MAXDATASIZE]; + char greeting005a[MAXDATASIZE]; + char greeting005b[MAXDATASIZE]; + char greeting005c[MAXDATASIZE]; char ircdname[MAXDATASIZE]; char nickuserhost[MAXDATASIZE]; char ircnick[MAXNICKLENGTH]; @@ -108,6 +111,7 @@ struct client { int pendingwhois; // Whether the client is waiting to hear back from a "WHOIS" command int pendingwhowas; // Whether the client is waiting to hear back from a "WHOWAS" command int pendingnames; // Count of RPL_NAMREPLYs the client is waiting on. + int pendingcap; // Whether the client is still negotiating IRCv3 CAPabilities. 0 = no, 1 = yes, -1 = just finished (so register them as if they had just sent USER). }; // Return index of requested client FD within the clients array. @@ -126,7 +130,8 @@ int arrindex(struct client *clients, int clientfd) { } // Send whatever string to a specific client by providing the FD -int sendtoclient(int fd, char *strsrc, struct client *clients, struct settings *settings) { +// If "bypass" == 1 then permit sending to client even if unauthenticated (for instance for a CAP LS response) +int sendtoclient(int fd, char *strsrc, struct client *clients, struct settings *settings, int bypass) { // Copy to new string for passing to appendcrlf() to avoid overrun in appendcrlf() char str[MAXDATASIZE]; strcpy(str, strsrc); @@ -138,7 +143,7 @@ int sendtoclient(int fd, char *strsrc, struct client *clients, struct settings * for (i = 0; i < MAXCLIENTS; i++) { if (clients[i].fd == fd) { // Found client in array, check authentication status - if (!clients[i].authed) { + if (!clients[i].authed && !bypass) { printf("sendtoclient(): skipping unauthenticated client with fd %d.\n", clients[i].fd); return 0; } @@ -282,6 +287,7 @@ int disconnectclient(int fd, struct client *clients, struct ircdstrings *ircdstr clients[i].pendingwhois = 0; clients[i].pendingwhowas = 0; clients[i].pendingnames = 0; + clients[i].pendingcap = 0; if (settings->clienttls) { // Finish up with OpenSSL if using client TLS SSL_free(clients[i].ssl); @@ -486,13 +492,13 @@ int doreplay(int sourcefd, int replayseconds, struct client *clients, struct set exit(1); } else if (numlines == 0) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :0 replay log lines found in the time requested, nothing to send.", ircdstrings->ircnick); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } // Announce the start snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Starting log replay....", ircdstrings->ircnick); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // Replay those lines! for (int i = 0; i < numlines; i++) { @@ -548,12 +554,12 @@ int doreplay(int sourcefd, int replayseconds, struct client *clients, struct set free(strcopyPtr); printf("Sending replay line: '%s'.\n", outgoingmsg); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } // Announce the end snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Log replay complete.", ircdstrings->ircnick); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } @@ -682,7 +688,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Prefix received? TODO - Care about what the prefix is - what if it's a different server/network/whatever? if (tokens[0][0] == ':') { printf("Prefix found: '%s'! Next token is '%s', length %zd.\n", tokens[0], tokens[1], strlen(tokens[1])); - // Greetings 001 through to 004, store in ircdstrings array for resending when clients connect + // Greetings 001 through to 005, store in ircdstrings array for resending when clients connect // Also store our nick!user@host from greeting 001 // Also store the real IRCd's name from greeting 004 if (strncmp(tokens[1], "001", strlen(tokens[1])) == 0) { @@ -731,6 +737,24 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli joinautochannels(server_ssl, clients, settings); free(strcopyPtr); return 1; + } else if (strncmp(tokens[1], "005", strlen(tokens[1])) == 0) { + printf("Found greeting 005 (%s), storing in ircdstrings struct.\n", str); + // Find an empty greeting005 string in ircdstrings and store in there... + if (!ircdstrings->greeting005a[0]) { + strncpy(ircdstrings->greeting005a, str, strlen(str)); + ircdstrings->greeting005a[strlen(str)] = '\0'; + } else if (!ircdstrings->greeting005b[0]) { + strncpy(ircdstrings->greeting005b, str, strlen(str)); + ircdstrings->greeting005b[strlen(str)] = '\0'; + } else if (!ircdstrings->greeting005c[0]) { + strncpy(ircdstrings->greeting005c, str, strlen(str)); + ircdstrings->greeting005c[strlen(str)] = '\0'; + } else { + // ...or if they are all fill, discard - TODO - Support more than three greeting005 strings! + printf("Already stored three greeting 005 strings, discarding this one.\n"); + } + free(strcopyPtr); + return 1; } // Server JOIN received? Add to our local channel array if it's us, or record the user in the channel if it's not us. @@ -836,7 +860,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingnames > 0) { printf("Sending 353 RPL_NAMREPLY for channel '%s' to client with fd '%d' who was pending %d RPL_NAMREPLYs.\n", tokens[4], clients[i].fd, clients[i].pendingnames); - sendtoclient(clients[i].fd, str, clients, settings); + sendtoclient(clients[i].fd, str, clients, settings, 0); } } } @@ -859,7 +883,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli printf("Server 366 received for an existing channel, sending to all clients who were pending RPL_NAMREPLYs and decrementing their pendingnames count.\n"); for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingnames > 0) { - sendtoclient(clients[i].fd, str, clients, settings); + sendtoclient(clients[i].fd, str, clients, settings, 0); // And decrement their pendingnames count printf("Decrementing pendingnames due 366 RPL_ENDOFNAMES to for channel '%s' to client with fd '%d' who was pending %d RPL_NAMREPLYs.\n", tokens[3], clients[i].fd, clients[i].pendingnames); clients[i].pendingnames--; @@ -957,7 +981,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Get nick from it extractnickfromprefix(prefixcopy); // Update greeting strings for relaying to new clients - updategreetings(ircdstrings->greeting001, ircdstrings->greeting002, ircdstrings->greeting003, ircdstrings->greeting004, ircdstrings->nickuserhost, nickuserhostcpy, tokens[2], ircdstrings->ircnick); + updategreetings(ircdstrings->greeting001, ircdstrings->greeting002, ircdstrings->greeting003, ircdstrings->greeting004, ircdstrings->greeting005a, ircdstrings->greeting005b, ircdstrings->greeting005c, ircdstrings->nickuserhost, nickuserhostcpy, tokens[2], ircdstrings->ircnick); // Update our nick strcpy(ircdstrings->ircnick, prefixcopy); printf("Updated ircnick to '%s'.\n", ircdstrings->ircnick); @@ -1019,7 +1043,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingchannelmode == 1) { - sendtoclient(clients[i].fd, str, clients, settings); + sendtoclient(clients[i].fd, str, clients, settings, 0); // And clear the pending flag clients[i].pendingchannelmode = 0; } @@ -1036,7 +1060,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingban == 1) { - sendtoclient(clients[i].fd, str, clients, settings); + sendtoclient(clients[i].fd, str, clients, settings, 0); // And clear the pending flag clients[i].pendingban = 0; } @@ -1053,7 +1077,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingwho == 1 && clients[i].fd) { - sendtoclient(clients[i].fd, str, clients, settings); + sendtoclient(clients[i].fd, str, clients, settings, 0); // And clear the pending flag if it's 315 (RPL_ENDOFWHO) if (strncmp(tokens[1], "315", strlen(tokens[1])) == 0) { clients[i].pendingwho = 0; @@ -1072,7 +1096,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendinglist == 1 && clients[i].fd) { - sendtoclient(clients[i].fd, str, clients, settings); + sendtoclient(clients[i].fd, str, clients, settings, 0); // And clear the pending flag if it's 323 (RPL_LISTEND) if (strncmp(tokens[1], "323", strlen(tokens[1])) == 0) { clients[i].pendinglist = 0; @@ -1098,7 +1122,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingwhois == 1 && clients[i].fd) { - sendtoclient(clients[i].fd, str, clients, settings); + sendtoclient(clients[i].fd, str, clients, settings, 0); // And clear the pending flag if it's 318 RPL_ENDOFWHOIS if (strncmp(tokens[1], "318", strlen(tokens[1])) == 0) { clients[i].pendingwhois = 0; @@ -1119,7 +1143,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingwhowas == 1 && clients[i].fd) { - sendtoclient(clients[i].fd, str, clients, settings); + sendtoclient(clients[i].fd, str, clients, settings, 0); // And clear the pending flag if it's 369 RPL_ENDOFWHOWAS if (strncmp(tokens[1], "369", strlen(tokens[1])) == 0) { clients[i].pendingwhowas = 0; @@ -1140,7 +1164,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingwhois == 1 || clients[i].pendingwhowas == 1) { - sendtoclient(clients[i].fd, str, clients, settings); + sendtoclient(clients[i].fd, str, clients, settings, 0); // Note that we were pending this waspending = 1; } @@ -1166,7 +1190,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli // Relay to all pending clients for (int i = 0; i < MAXCLIENTS; i++) { if (clients[i].pendingwhois == 1) { - sendtoclient(clients[i].fd, str, clients, settings); + sendtoclient(clients[i].fd, str, clients, settings, 0); // Note that we were pending this waspending = 1; } @@ -1264,6 +1288,41 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli return 1; } + // CAP received? Clients can send CAP before PASS so we have to deal with this even if they are not authenticated yet. + if (strncasecmp(tokens[0], "CAP", strlen(tokens[0])) == 0) { + // Get the real IRC server name from greeting001 + // They are now pending CAP negotiation + clients[arrindex(clients, sourcefd)].pendingcap = 1; + char outgoingmsg[MAXDATASIZE]; + // If client is requesting CAP list, send it... + if (strncasecmp(tokens[1], "LS", strlen(tokens[1])) == 0) { + if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s CAP * LS :multi-prefix", ircdstrings->ircdname)) { + fprintf(stderr, "Error while preparing CAP LS response!\n"); + exit(1); + } + // ...even if unauthenticated + sendtoclient(sourcefd, outgoingmsg, clients, settings, 1); + free(strcopyPtr); + return 1; + // If client is requesting a CAP... + } else if (strncasecmp(tokens[1], "REQ", strlen(tokens[1])) == 0) { + // ...and it is "multi-prefix", send it + if (strncasecmp(tokens[2], ":multi-prefix", strlen(tokens[2])) == 0) { + if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s CAP %s ACK :multi-prefix ", ircdstrings->ircdname, ircdstrings->ircnick)) { + fprintf(stderr, "Error while preparing CAP ACK response!\n"); + exit(1); + } + // ...even if unauthenticated + sendtoclient(sourcefd, outgoingmsg, clients, settings, 1); + free(strcopyPtr); + return 1; + } + // If client is finishing CAP negotiation then mark them as so + } else if (strncasecmp(tokens[1], "END", strlen(tokens[1])) == 0) { + clients[arrindex(clients, sourcefd)].pendingcap = -1; + } + } + // We're past PASS in the list of possible commands, so ignore // anything else the client says if they are not authenticated yet. if (!clients[arrindex(clients, sourcefd)].authed) { @@ -1272,37 +1331,54 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli return 1; } - // USER received? If so, assume this is a new client connecting and catch them on up on the state - if (strncasecmp(tokens[0], "USER", strlen(tokens[0])) == 0) { + // USER received and not pending CAP negotiation during registration? + // Or client has just finished negotiating CAP (pendingcap = -1)? + // If so, assume this is a new client connecting and catch them on up on the state + if ((strncasecmp(tokens[0], "USER", strlen(tokens[0])) == 0 && clients[arrindex(clients, sourcefd)].pendingcap == 0) || clients[arrindex(clients, sourcefd)].pendingcap == -1) { // Somewhere to store the several strings we will need to build and send char outgoingmsg[MAXDATASIZE]; // String to send to client + // If registering then they must no longer be pending CAP negotiation + clients[arrindex(clients, sourcefd)].pendingcap = 0; + // Tell the client to go away if we aren't registered with the real server yet as defined by the last greeting not being set yet if (!strlen(ircdstrings->greeting004)) { - sendtoclient(sourcefd, "Sorry, we aren't registered with a real IRC server yet.", clients, settings); + sendtoclient(sourcefd, "Sorry, we aren't registered with a real IRC server yet.", clients, settings, 0); disconnectclient(sourcefd, clients, ircdstrings, settings); return 1; } - // Send IRC greeting strings (001/RPL_WELCOME, 002/RPL_YOURHOST, 003/RPL_CREATED, 004/RPL_MYINFO) to client + // Send IRC greeting strings (001/RPL_WELCOME, 002/RPL_YOURHOST, 003/RPL_CREATED, 004/RPL_MYINFO, 005/RPL_ISUPPORT) to client snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting001); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting002); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting003); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting004); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + if (ircdstrings->greeting005a[0]) { + snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting005a); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + } + if (ircdstrings->greeting005b[0]) { + snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting005b); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + } + if (ircdstrings->greeting005c[0]) { + snprintf(outgoingmsg, MAXDATASIZE, "%s", ircdstrings->greeting005c); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); + } // Send our own greeting message snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Welcome to blabouncer!", ircdstrings->ircnick); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Blabouncer commands are all prefixed with BLABOUNCER which you can usually send using \"/QUOTE BLABOUNCER\"", ircdstrings->ircnick); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Valid blabouncer commands are:", ircdstrings->ircnick); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REPLAY [seconds]\" (Where [seconds] is the number of seconds of replay log to replay.)", ircdstrings->ircnick); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // Get the channel count so we can enumerate over all channels. // Storing separately so we can skip over blank channels. @@ -1326,7 +1402,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli fprintf(stderr, "Error while preparing USER just connected, channel JOIN responses!\n"); exit(1); } - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // Send topic (or lack thereof) to client // If there isn't one set (we guess this if topic timestamp is 0), send 331 RPL_NOTOPIC @@ -1337,7 +1413,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli exit(1); } // ..and send it to the client - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // If there is one set, send 332 RPL_TOPIC and 333 RPL_TOPICWHOTIME } else { // Prepare the topic message... @@ -1346,7 +1422,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli exit(1); } // ..and send it to the client - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // Next prepare the topic who/when message... if (!snprintf(outgoingmsg, MAXDATASIZE, ":%s 333 %s %s %s %s", ircdstrings->ircdname, ircdstrings->ircnick, channels[i].name, channels[i].topicwho, channels[i].topicwhen)) { @@ -1354,7 +1430,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli exit(1); } // ..and send it to the client - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } // Get the latest RPL_NAMREPLY for this channel to relay to the client when it arrives @@ -1368,7 +1444,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli fprintf(stderr, "Error while preparing USER just connected, MODE response!\n"); exit(1); } - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // Set the client as registered clients[arrindex(clients, sourcefd)].registered = 1; @@ -1397,7 +1473,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli fprintf(stderr, "Error while preparing PONG response!\n"); exit(1); } - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); // We processed something so return true free(strcopyPtr); @@ -1554,7 +1630,7 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli if ((replayseconds = strtol(tokens[2], NULL, 10)) == 0) { printf("Invalid number of replay seconds provided by REPLAY. Telling client.\n"); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Invalid number of seconds of replay requested by REPLAY command.", ircdstrings->ircnick); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); free(strcopyPtr); return 1; } @@ -1566,9 +1642,9 @@ int processircmessage(SSL *server_ssl, char *str, int source, struct client *cli } else { printf("Client BLABOUNCER unrecognised command found and it is: %s with length %zd! Sending a help message.\n", tokens[1], strlen(tokens[1])); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unrecognised BLABOUNCER command received. Valid commands are:", ircdstrings->ircnick); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :\"BLABOUNCER REPLAY [seconds]\" (Where [seconds] is the number of seconds of replay log to replay.)", ircdstrings->ircnick); - sendtoclient(sourcefd, outgoingmsg, clients, settings); + sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); free(strcopyPtr); return 1; } @@ -1736,6 +1812,7 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { clients[i].pendingwhois = 0; clients[i].pendingwhowas = 0; clients[i].pendingnames = 0; + clients[i].pendingcap = 0; } // Initialise OpenSSL (used for both client and server) @@ -1772,6 +1849,9 @@ void dochat(int *serversockfd, int *clientsockfd, struct settings *settings) { ircdstrings.greeting002[0] = '\0'; ircdstrings.greeting003[0] = '\0'; ircdstrings.greeting004[0] = '\0'; + ircdstrings.greeting005a[0] = '\0'; + ircdstrings.greeting005b[0] = '\0'; + ircdstrings.greeting005c[0] = '\0'; ircdstrings.ircdname[0] = '\0'; ircdstrings.nickuserhost[0] = '\0'; ircdstrings.ircnick[0] = '\0'; -- cgit v1.2.3