diff --git a/.gitignore b/.gitignore index 2a5c394..e9e2d45 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ build bbirc-debug bbirc tests -compile_commands.json \ No newline at end of file +compile_commands.json +privinclude \ No newline at end of file diff --git a/Makefile b/Makefile index db0cebe..c6374ad 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,15 @@ -CC := clang -CFLAGS := -I include -Wall -Wextra -CFLAGS_DBG := $(CFLAGS) -g -O0 -CFLAGS_REL := $(CFLAGS) -O2 - -LDFLAGS := -LDFLAGS_REL := $(LDFLAGS) -s - BUILD_DIR := build SRC_DIR := src INCLUDE_DIR := include +PRIVATE_INCLUDE_DIR := privinclude + +CC := clang +CFLAGS := -I $(INCLUDE_DIR) -I $(PRIVATE_INCLUDE_DIR) -Wall -Wextra +CFLAGS_DBG := $(CFLAGS) -g -O0 +CFLAGS_REL := $(CFLAGS) -O2 + +LDFLAGS := +LDFLAGS_REL := $(LDFLAGS) -s APPLICATION := bbirc APPLICATION_DBG := bbirc-debug diff --git a/compile_commands.json b/compile_commands.json index 4ab23de..9f9242b 100644 --- a/compile_commands.json +++ b/compile_commands.json @@ -1,4 +1,42 @@ [ + { + "file": "src/IRC.c", + "arguments": [ + "/usr/bin/clang", + "-c", + "-o", + "build/rel_IRC.o", + "src/IRC.c", + "-I", + "include", + "-I", + "privinclude", + "-Wall", + "-Wextra", + "-O2" + ], + "directory": "/home/neon1246/Documents/stuff/code/c/ircclient", + "output": "build/rel_IRC.o" + }, + { + "file": "src/base64.c", + "arguments": [ + "/usr/bin/clang", + "-c", + "-o", + "build/rel_base64.o", + "src/base64.c", + "-I", + "include", + "-I", + "privinclude", + "-Wall", + "-Wextra", + "-O2" + ], + "directory": "/home/neon1246/Documents/stuff/code/c/ircclient", + "output": "build/rel_base64.o" + }, { "file": "src/main.c", "arguments": [ @@ -9,11 +47,32 @@ "src/main.c", "-I", "include", + "-I", + "privinclude", "-Wall", "-Wextra", "-O2" ], "directory": "/home/neon1246/Documents/stuff/code/c/ircclient", "output": "build/rel_main.o" + }, + { + "file": "src/netcode.c", + "arguments": [ + "/usr/bin/clang", + "-c", + "-o", + "build/rel_netcode.o", + "src/netcode.c", + "-I", + "include", + "-I", + "privinclude", + "-Wall", + "-Wextra", + "-O2" + ], + "directory": "/home/neon1246/Documents/stuff/code/c/ircclient", + "output": "build/rel_netcode.o" } ] \ No newline at end of file diff --git a/include/IRC.h b/include/IRC/IRC.h similarity index 75% rename from include/IRC.h rename to include/IRC/IRC.h index d1c35a4..8e6f00e 100644 --- a/include/IRC.h +++ b/include/IRC/IRC.h @@ -2,6 +2,7 @@ #define IRC_H #include "IRC_structs.h" +#include "IRC_numerics.h" void IRC_ParseMessage(char* line, irc_message_t *msg); void IRC_ProcessMessage(irc_message_t *msg, irc_client_t *irc); @@ -13,7 +14,10 @@ void IRC_NICK(char *nick, irc_client_t *irc); void IRC_USER(char *ident, char* realname, irc_client_t *irc); // registration -void IRC_Register(char *nick, char* ident, char *realname, bool saslEnabled, irc_sasl_mechanism_t saslMethod, char *saslUsername, char *saslPassword, irc_client_t *irc); +void IRC_Register(char *nick, char *username, char *realname, irc_client_t *irc); +void IRC_SetSASLParameters(irc_sasl_mechanism_t, char* username, char* password, irc_client_t *irc); + +void IRC_RequestSASLAuthentication(irc_client_t *irc); // Capability negotiation block void IRC_StartCapabilityNegotiation(irc_client_t *irc); @@ -22,6 +26,7 @@ void IRC_EndCapabilityNegotiation(irc_client_t *irc); void IRC_NeedCapabilities(char* caps, irc_client_t *irc); + int IRC_ParseCapabilities(char* caps, irc_capability_t *capabilities, int capsLength); #endif \ No newline at end of file diff --git a/include/IRC_numerics.h b/include/IRC/IRC_numerics.h similarity index 100% rename from include/IRC_numerics.h rename to include/IRC/IRC_numerics.h diff --git a/include/IRC_structs.h b/include/IRC/IRC_structs.h similarity index 94% rename from include/IRC_structs.h rename to include/IRC/IRC_structs.h index 1b5dd2b..05c05f9 100644 --- a/include/IRC_structs.h +++ b/include/IRC/IRC_structs.h @@ -73,13 +73,9 @@ typedef struct char supportedCapabilities[512]; char requestedCapabilities[512]; irc_capability_t acked[24]; - struct - { - bool saslWorks; - } capabilities; /* Authentication parameters */ - irc_auth_t sasl_auth; + irc_auth_t saslAuth; } irc_client_t; typedef struct diff --git a/include/base64.h b/include/base64.h new file mode 100644 index 0000000..f39aa98 --- /dev/null +++ b/include/base64.h @@ -0,0 +1,9 @@ +#ifndef BASE64_H +#define BASE64_H + +#include +#include + +int base64_encode(const uint8_t *in, size_t in_len, char *out, size_t out_size); + +#endif \ No newline at end of file diff --git a/src/IRC.c b/src/IRC.c index 0b85149..046cadc 100644 --- a/src/IRC.c +++ b/src/IRC.c @@ -1,10 +1,14 @@ -#include "IRC.h" +#include "IRC/IRC_numerics.h" +#include "IRC/IRC_structs.h" #include "defines.h" #include "netcode.h" #include #include #include +#include "IRC/IRC.h" +#include "base64.h" + void IRC_ParseMessage(char *line, irc_message_t *msg) { strncpy(msg->line, line, sizeof(msg->line) - 1); @@ -63,11 +67,27 @@ void IRC_ParseMessage(char *line, irc_message_t *msg) msg->argc++; } + + if (msg->argc > 0) { + char *last = msg->argv[msg->argc - 1]; + size_t len = strlen(last); + while (len > 0 && (last[len-1] == '\r' || last[len-1] == '\n')) { + last[len-1] = '\0'; + len--; + } + } } // Returns the number of capabilities found. int IRC_ParseCapabilities(char *caps, irc_capability_t *capabilities, int capsLength) { + while (*caps == ' ') caps++; + + size_t len = strlen(caps); + while (len > 0 && (caps[len-1] == '\n' || caps[len-1] == '\r' || caps[len-1] == ' ')) { + caps[len-1] = '\0'; + len--; + } char capBak[512]; strncpy(capBak, caps, sizeof(capBak) - 1); // copy the capline so we don't destroy our parameter capBak[sizeof(capBak) - 1] = 0; @@ -123,16 +143,16 @@ int IRC_ParseCapabilities(char *caps, irc_capability_t *capabilities, int capsLe static void handlePing(irc_message_t *msg, irc_client_t *irc) { char buf[128]; // most pingpongs are usually short, with the servername being short too - snprintf(buf, sizeof buf, "PONG :%s", msg->argv[0]); + snprintf(buf, sizeof buf, "PONG :%s\r\n", msg->argv[0]); NET_Send(irc->sockfd, buf); } static void handleCapabilities(irc_message_t *msg, irc_client_t *irc) { // we are willing to tolerate some spaghetti... - if(strcmp(msg->argv[2], "LS") == 0) + if(strcmp(msg->argv[1], "LS") == 0) { - snprintf(irc->supportedCapabilities, sizeof irc->supportedCapabilities, "%s", msg->argv[3]); + snprintf(irc->supportedCapabilities, sizeof irc->supportedCapabilities, "%s", msg->argv[2]); // parse the caps irc_capability_t caps[24]; @@ -149,7 +169,7 @@ static void handleCapabilities(irc_message_t *msg, irc_client_t *irc) { for(int j = 0; j < reqCapCount; j++) { - if(strcmp(caps[i].name, reqCaps[j].name) == 0 == 0) + if(strcmp(caps[i].name, reqCaps[j].name) == 0) { bytesWritten += snprintf(buf + bytesWritten, sizeof(buf) - bytesWritten, "%s ", reqCaps[j].name); } @@ -158,15 +178,68 @@ static void handleCapabilities(irc_message_t *msg, irc_client_t *irc) IRC_RequestCapabilities(buf, irc); } - else if (strcmp(msg->argv[2], "ACK")) + else if (strcmp(msg->argv[1], "ACK") == 0) { - int ackedCount = IRC_ParseCapabilities(msg->argv[3], irc->acked, sizeof(irc->acked) / sizeof(irc_capability_t)); + int ackedCount = IRC_ParseCapabilities(msg->argv[2], irc->acked, sizeof(irc->acked) / sizeof(irc_capability_t)); for(int i = 0; i < ackedCount; i++) { - + printf("%s = %s\n", irc->acked[i].name, irc->acked[i].args ? irc->acked[i].args : "(none)"); + if(strcmp(irc->acked[i].name, "sasl") == 0) + { + IRC_RequestSASLAuthentication(irc); + } } } + // NAK is a no-op anyway, if it isn't in our irc->acked we just won't use it then. +} + +static void handleAuthentication(irc_message_t *msg, irc_client_t *irc) +{ + if(strcmp(msg->argv[0], "+") == 0) + { + // send our base64 sasl username and password + switch(irc->saslAuth.mechanism) + { + case SASL_MECHANISM_NONE: + break; + case SASL_MECHANISM_PLAIN: + { + const char *user = irc->saslAuth.plain_credentials.username; + const char *pass = irc->saslAuth.plain_credentials.password; + size_t user_len = strlen(user); + size_t pass_len = strlen(pass); + + uint8_t sasl_data[user_len + pass_len + 3]; + size_t idx = 0; + + sasl_data[idx++] = 0; + memcpy(sasl_data + idx, user, user_len); + idx += user_len; + sasl_data[idx++] = 0; + memcpy(sasl_data + idx, pass, pass_len); + idx += pass_len; + + char base64Out[512]; + base64_encode(sasl_data, idx, base64Out, sizeof(base64Out)); + char buf[512]; + snprintf(buf, sizeof buf, "AUTHENTICATE %s\r\n", base64Out); + NET_Send(irc->sockfd, buf); + break; + } + case SASL_MECHANISM_EXTERNAL: + { + NET_Send(irc->sockfd, "AUTHENTICATE +\r\n"); + break; + } + } + + } +} + +static void handleSaslSuccess(irc_message_t *msg, irc_client_t *irc) +{ + IRC_EndCapabilityNegotiation(irc); } /* @@ -179,6 +252,8 @@ void IRC_ProcessMessage(irc_message_t *msg, irc_client_t *irc) { {"PING", handlePing}, {"CAP", handleCapabilities}, + {"AUTHENTICATE", handleAuthentication}, + {RPL_SASLSUCCESS, handleSaslSuccess}, {NULL, NULL} }; @@ -194,47 +269,67 @@ void IRC_ProcessMessage(irc_message_t *msg, irc_client_t *irc) void IRC_PRIVMSG(char *to, char *msg, irc_client_t *irc) { char buf[512]; - snprintf(buf, sizeof buf, "PRIVMSG %s :%s", to, msg); + snprintf(buf, sizeof buf, "PRIVMSG %s :%s\r\n", to, msg); NET_Send(irc->sockfd, buf); } void IRC_NOTICE(char *to, char *msg, irc_client_t *irc) { char buf[512]; - snprintf(buf, sizeof buf, "NOTICE %s :%s", to, msg); + snprintf(buf, sizeof buf, "NOTICE %s :%s\r\n", to, msg); NET_Send(irc->sockfd, buf); } void IRC_NICK(char *newNick, irc_client_t *irc) { char buf[512]; - snprintf(buf, sizeof buf, "NICK %s", newNick); + snprintf(buf, sizeof buf, "NICK %s\r\n", newNick); NET_Send(irc->sockfd, buf); } void IRC_USER(char *ident, char *realname, irc_client_t *irc) { char buf[512]; - snprintf(buf, sizeof buf, "USER %s 0 * :%s", ident, realname); + snprintf(buf, sizeof buf, "USER %s 0 * :%s\r\n", ident, realname); NET_Send(irc->sockfd, buf); } -void IRC_REGISTER(char *nick, char *ident, char *realname, irc_client_t *irc) +void IRC_Register(char *nick, char *username, char *realname, irc_client_t *irc) { - IRC_StartCapabilityNegotiation(irc); + if(irc->saslAuth.mechanism != SASL_MECHANISM_NONE) + { + IRC_NeedCapabilities("sasl", irc); + IRC_StartCapabilityNegotiation(irc); + } IRC_NICK(nick, irc); - IRC_USER(ident, realname, irc); + IRC_USER(username, realname, irc); +} + +void IRC_SetSASLParameters(irc_sasl_mechanism_t mechanism, char *username, char *password, irc_client_t *irc) +{ + switch(mechanism) + { + case SASL_MECHANISM_NONE: + break; + case SASL_MECHANISM_EXTERNAL: + // unimplemented + break; + case SASL_MECHANISM_PLAIN: + irc->saslAuth.mechanism = mechanism; + strncpy(irc->saslAuth.plain_credentials.username, username, sizeof(irc->saslAuth.plain_credentials.username) - 1); + strncpy(irc->saslAuth.plain_credentials.password, password, sizeof(irc->saslAuth.plain_credentials.password) - 1); + } } void IRC_StartCapabilityNegotiation(irc_client_t *irc) { - NET_Send(irc->sockfd, "CAP LS 302"); + NET_Send(irc->sockfd, "CAP LS 302\r\n"); } void IRC_RequestCapabilities(char *caps, irc_client_t *irc) { char buf[512]; - snprintf(buf, sizeof buf, "CAP REQ :%s", caps); + snprintf(buf, sizeof buf, "CAP REQ :%s\r\n", caps); NET_Send(irc->sockfd, buf); } @@ -245,5 +340,21 @@ void IRC_NeedCapabilities(char *caps, irc_client_t *irc) void IRC_EndCapabilityNegotiation(irc_client_t *irc) { - NET_Send(irc->sockfd, "CAP END"); + NET_Send(irc->sockfd, "CAP END\r\n"); +} + +void IRC_RequestSASLAuthentication(irc_client_t *irc) +{ + switch(irc->saslAuth.mechanism) + { + case SASL_MECHANISM_NONE: + // why? + break; + case SASL_MECHANISM_PLAIN: + NET_Send(irc->sockfd, "AUTHENTICATE PLAIN\r\n"); + break; + case SASL_MECHANISM_EXTERNAL: + NET_Send(irc->sockfd, "AUTHENTICATE EXTERNAL\r\n"); + break; + } } \ No newline at end of file diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 0000000..1cc42ab --- /dev/null +++ b/src/base64.c @@ -0,0 +1,34 @@ +#include +#include +#include +#include "base64.h" +#include + +static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +int base64_encode(const uint8_t *in, size_t in_len, char *out, size_t out_size) +{ + size_t needed = ((in_len + 2) / 3) * 4 + 1; + + if(out_size < needed) return -1; + + size_t i = 0, o = 0; + while(i < in_len) + { + uint32_t n = 0; + int remaining = 3; + + n |= (i < in_len ? in[i++] : 0) << 16; + n |= (i < in_len ? in[i++] : 0) << 8; + n |= (i < in_len ? in[i++] : 0); + + if(i > in_len) remaining = in_len % 3; + + out[o++] = b64[(n >> 18) & 63]; + out[o++] = b64[(n >> 12) & 63]; + out[o++] = remaining > 1 ? b64[(n >> 6) & 63] : '='; + out[o++] = remaining > 2 ? b64[n & 63] : '='; + } + + out[o] = 0; + return o; +} \ No newline at end of file diff --git a/src/main.c b/src/main.c index e971429..c78b911 100644 --- a/src/main.c +++ b/src/main.c @@ -9,10 +9,13 @@ #include #include -#include "IRC.h" +#include "IRC/IRC.h" +#include "IRC/IRC_structs.h" #include "defines.h" #include "netcode.h" +#include "credentials.h" + int main(int argc, char **argv) { int sockfd = NET_Connect("irc.libera.chat", 6667); @@ -28,8 +31,8 @@ int main(int argc, char **argv) if(sockfd >= 0) { - NET_Send(sockfd, "NICK BareBonesDude\r\n"); - NET_Send(sockfd, "USER BareBonesDude 0 * :The Postal Dudesksleton\r\n"); + IRC_SetSASLParameters(SASL_MECHANISM_PLAIN, CREDENTIALS_USERNAME, CREDENTIALS_PASSWORD, &irc); + IRC_Register("BareBonesDude", "bbirc", "A Barebones IRC (https://gitea.codersquack.nl/thorium1256/bbirc) test", &irc); } else { @@ -72,24 +75,32 @@ int main(int argc, char **argv) irc_message_t msg; IRC_ParseMessage(linebuf, &msg); - IRC_ProcessMessage(&msg, &irc); - - // try to print it - if(msg.source != NULL) { - printf("%s: %s ", msg.source, msg.command); + printf("< %s: %s ", msg.source, msg.command); } else { - printf("Server: %s ", msg.command); + printf("< %s ", msg.command); } for(int i = 0; i < msg.argc; i++) { - printf("%s ", msg.argv[i]); + if(i == msg.argc-1) + { + printf("%s\n", msg.argv[i]); + } + else + { + printf("%s ", msg.argv[i]); + } } + + IRC_ProcessMessage(&msg, &irc); + + // try to print it + lineLen = 0; } diff --git a/src/netcode.c b/src/netcode.c index a5eebaf..1f07faf 100644 --- a/src/netcode.c +++ b/src/netcode.c @@ -94,6 +94,8 @@ void NET_Send(int sockfd, const char *toSend) size_t bytesSent = 0; size_t sendLen = strlen(toSend); + printf("> %s", toSend); + while(bytesSent < sendLen) { ssize_t sent = send(sockfd, toSend, sendLen - bytesSent, 0); diff --git a/test b/test index ca26170..2fe51c5 100755 Binary files a/test and b/test differ