commit 7be1ef312ef89b32e2da268de15bd3168a6504a9 Author: thorium1256 Date: Sun May 24 10:07:02 2026 +0300 split IRC.c and moved it to a different repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ea0d73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build +libircc.so +compile_commands.json \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a7cb228 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +CC = clang +CFLAGS = -I./include -fPIC -Wall -Wextra -O2 -fvisibility=hidden +LDFLAGS = -lssl -lcrypto + +BUILD_DIR = build + +LIBRARY = libircc.so + +SRCS = $(shell find $(SRC_DIR) -name '*.c') +OBJS = $(patsubst %.c,$(BUILD_DIR)/%.o,$(SRCS)) + +.PHONY: libircc + +all: $(BUILD_DIR) libircc + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +libircc: $(LIBRARY) + +$(LIBRARY): $(OBJS) + $(CC) -shared -o $@ $^ + +$(BUILD_DIR)/%.o: %.c + mkdir -p $(dir $@) + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJS) $(LIBRARY) \ No newline at end of file diff --git a/include/IRC/IRC.h b/include/IRC/IRC.h new file mode 100644 index 0000000..7a46964 --- /dev/null +++ b/include/IRC/IRC.h @@ -0,0 +1,9 @@ +#ifndef IRC_H +#define IRC_H + +#include "IRC/IRC_structs.h" +#include "IRC/IRC_events.h" +#include "IRC/IRC_numerics.h" +#include "IRC/IRC_functions.h" + +#endif \ No newline at end of file diff --git a/include/IRC/IRC_events.h b/include/IRC/IRC_events.h new file mode 100644 index 0000000..2e86bab --- /dev/null +++ b/include/IRC/IRC_events.h @@ -0,0 +1,16 @@ +#ifndef IRC_EVENTS_H +#define IRC_EVENTS_H + +typedef enum +{ + IRC_EVENT_PRIVMSG, + IRC_EVENT_NOTICE, + IRC_EVENT_JOIN, + IRC_EVENT_PART, + IRC_EVENT_QUIT, + IRC_EVENT_MODESET, + IRC_EVENT_AWAY, + IRC_EVENT_KICK +} irc_event_type_t; + +#endif \ No newline at end of file diff --git a/include/IRC/IRC_functions.h b/include/IRC/IRC_functions.h new file mode 100644 index 0000000..6c2e660 --- /dev/null +++ b/include/IRC/IRC_functions.h @@ -0,0 +1,49 @@ +#ifndef IRC_FUNCTIONS_H +#define IRC_FUNCTIONS_H + +#include "IRC_structs.h" +#include "defines.h" + +PUBLIC void IRC_ParseMessage(char* line, irc_message_t *msg); +PUBLIC void IRC_ProcessMessage(irc_message_t *msg, irc_client_t *irc); + +// various IRC commands +PUBLIC void IRC_PRIVMSG(char* to, char* msg, irc_client_t *irc); +PUBLIC void IRC_NOTICE(char* to, char* msg, irc_client_t *irc); +PUBLIC void IRC_NICK(char *nick, irc_client_t *irc); +PUBLIC void IRC_USER(char *ident, char* realname, irc_client_t *irc); + +PUBLIC void IRC_CTCPSend(char *to, char *cmd, irc_client_t *irc); + +// registration +PUBLIC void IRC_SetConnectionParameters(char* host, int port, irc_connection_type_t conntype, irc_client_t *irc); +PUBLIC void IRC_SetIdentityParameters(char* nickname, char* username, char* realname, irc_client_t *irc); +PUBLIC void IRC_Register(irc_client_t *irc); +PUBLIC void IRC_SetSASLParameters(irc_sasl_mechanism_t, char* username, char* password, irc_client_t *irc); + +PUBLIC void IRC_RequestSASLAuthentication(irc_client_t *irc); + +// Capability negotiation block +PUBLIC void IRC_StartCapabilityNegotiation(irc_client_t *irc); +PUBLIC void IRC_RequestCapabilities(char* caps, irc_client_t *irc); +PUBLIC void IRC_EndCapabilityNegotiation(irc_client_t *irc); + +PUBLIC void IRC_NeedCapabilities(char* caps, irc_client_t *irc); + +PUBLIC int IRC_ParseCapabilities(char* caps, irc_capability_t *capabilities, int capsLength); + +// raw socket things +PUBLIC void IRC_Connect(irc_client_t *irc); +PUBLIC ssize_t IRC_Receive(irc_client_t *irc, char* toBuf, size_t bufSize); +PUBLIC void IRC_Send(irc_client_t *irc, char* toSend); +PUBLIC void IRC_Close(irc_client_t *irc); + +// init +PUBLIC int IRC_Init(irc_client_t *irc); +PUBLIC void IRC_Free(irc_client_t *irc); + +PUBLIC int IRC_PollEvent(irc_event_t *event, irc_client_t *irc); + +void IRC_PushEvent(irc_event_t *event, irc_client_t *irc); + +#endif \ No newline at end of file diff --git a/include/IRC/IRC_numerics.h b/include/IRC/IRC_numerics.h new file mode 100644 index 0000000..86e7557 --- /dev/null +++ b/include/IRC/IRC_numerics.h @@ -0,0 +1,203 @@ +#ifndef IRC_NUMERICS_H +#define IRC_NUMERICS_H + +// * 0xx (initial login information) +#define RPL_WELCOME "001" +#define RPL_YOURHOST "002" +#define RPL_CREATED "003" +#define RPL_MYINFO "004" +#define RPL_ISUPPORT "005" +#define RPL_BOUNCE "010" /* deprecated */ + +// * 2xx (request information) + +/* replies to STATS */ +#define RPL_STATSCOMMANDS "212" +#define RPL_ENDOFSTATS "219" +#define RPL_STATSUPTIME "242" + +/* server user modes on-connect */ +#define RPL_UMODEIS "221" + +/* replies to LUSERS */ +#define RPL_LUSERCLIENT "251" +#define RPL_LUSEROP "252" +#define RPL_LUSERUNKNOWN "253" +#define RPL_LUSERCHANNELS "254" +#define RPL_LUSERME "255" +#define RPL_LOCALUSERS "265" +#define RPL_GLOBALUSERS "266" + +/* replies to ADMIN */ +#define RPL_ADMINME "256" +#define RPL_ADMINLOC1 "257" +#define RPL_ADMINLOC2 "258" +#define RPL_ADMINEMAIL "259" + +/* generic replies (i.e. ratelimiting) */ +#define RPL_TRYAGAIN "263" + +/* WHOIS info */ +#define RPL_WHOISCERTFP "276" + +// * 3xx (information for clients) +#define RPL_NONE "300" + +/* replies to WHOIS/WHOWAS */ +#define RPL_AWAY "301" +#define RPL_WHOISREGNICK "307" +#define RPL_WHOISUSER "311" +#define RPL_WHOISSERVER "312" +#define RPL_WHOISOPERATOR "313" +#define RPL_WHOWASUSER "314" +#define RPL_WHOISIDLE "317" +#define RPL_WHOISCHANNELS "319" +#define RPL_WHOISSPECIAL "320" +#define RPL_WHOISACCOUNT "330" +#define RPL_WHOISACTUALLY "338" +#define RPL_WHOISHOST "378" +#define RPL_WHOISMODES "379" +#define RPL_ENDOFWHOIS "318" +#define RPL_ENDOFWHOWAS "369" +#define ERR_WASNOSUCHNICK "406" /* WHOWAS error */ +#define RPL_WHOISSECURE "671" + +/* replies to WHO */ +#define RPL_WHOREPLY "352" +#define RPL_ENDOFWHO "315" + +/* replies to LIST */ +#define RPL_LISTSTART "321" +#define RPL_LIST "322" +#define RPL_LISTEND "323" + +/* upon joining a channel */ +#define RPL_CHANNELMODEIS "324" +#define RPL_CREATIONTIME "329" +#define RPL_TOPIC "332" +#define RPL_TOPICWHOTIME "333" + +/* replies to TOPIC */ +#define RPL_NOTOPIC "331" + +/* replies to INVITE */ +#define RPL_INVITELIST "336" +#define RPL_ENDOFINVITELIST "337" +#define RPL_INVITING "341" + +/* replies to MODE */ +#define RPL_INVEXLIST "346" +#define RPL_ENDOFINVEXLIST "347" +#define RPL_EXCEPTLIST "348" +#define RPL_ENDOFEXCEPTLIST "349" +#define RPL_BANLIST "367" +#define RPL_ENDOFBANLIST "368" +#define ERR_INVALIDMODEPARAM "696" + +/* replies to INFO */ +#define RPL_INFO "371" +#define RPL_ENDOFINFO "374" + +/* when sending a Message of the Day */ +#define RPL_MOTDSTART "375" +#define RPL_MOTD "372" +#define RPL_ENDOFMOTD "376" + +/* replies to VERSION */ +#define RPL_VERSION "351" + +/* replies to NAMES */ +#define RPL_NAMREPLY "353" +#define RPL_ENDOFNAMES "366" + +/* replies to LINKS */ +#define RPL_LINKS "364" +#define RPL_ENDOFLINKS "365" + +/* replies to OPER */ +#define RPL_YOUREOPER "381" + +/* replies to REHASH */ +#define RPL_REHASHING "382" + +/* replies to TIME */ +#define RPL_TIME "391" + +/* replies to USERHOST */ +#define RPL_USERHOST "302" + +/* sent to clients to inform of away status */ +#define RPL_UNAWAY "305" +#define RPL_NOWAWAY "306" + +// * 4xx and 5xx (errors) + +#define ERR_UNKNOWNERROR "400" +#define ERR_NOSUCHNICK "401" +#define ERR_NOSUCHSERVER "402" +#define ERR_NOSUCHCHANNEL "403" +#define ERR_CANNOTSENDTOCHAN "404" +#define ERR_TOOMANYCHANNELS "405" +#define ERR_NOORIGIN "409" +#define ERR_NORECIPIENT "411" +#define ERR_NOTEXTTOSEND "412" +#define ERR_INPUTTOOLONG "417" +#define ERR_UNKNOWNCOMMAND "421" +#define ERR_NOMOTD "422" +#define ERR_NONICKNAMEGIVEN "431" +#define ERR_ERRONEUSNICKNAME "432" +#define ERR_NICKNAMEINUSE "433" +#define ERR_NICKCOLLISION "436" +#define ERR_USERNOTINCHANNEL "441" +#define ERR_NOTONCHANNEL "442" +#define ERR_USERONCHANNEL "443" +#define ERR_NOTREGISTERED "451" +#define ERR_NEEDMOREPARAMS "461" +#define ERR_ALREADYREGISTERED "462" +#define ERR_PASSWDMISMATCH "464" +#define ERR_YOUREBANNEDCREEP "465" +#define ERR_CHANNELISFULL "471" +#define ERR_UNKNOWNMODE "472" +#define ERR_INVITEONLYCHAN "473" +#define ERR_BANNEDFROMCHAN "474" +#define ERR_BADCHANNELKEY "475" +#define ERR_BADCHANMASK "476" +#define ERR_NOPRIVILEGES "481" +#define ERR_CHANOPRIVSNEEDED "482" +#define ERR_CANTKILLSERVER "483" +#define ERR_NOOPERHOST "491" +#define ERR_UMODEUNKNOWNFLAG "501" +#define ERR_USERSDONTMATCH "502" +#define ERR_INVALIDKEY "525" + +/* replies to HELP */ +#define ERR_HELPNOTFOUND "524" +#define RPL_HELPSTART "704" +#define RPL_HELPTXT "705" +#define RPL_ENDOFHELP "706" + +// * 6xx (various TLS and IRCv3 stuff) + +/* STARTTLS stuff */ +#define RPL_STARTTLS "670" +#define ERR_STARTTLS "691" + +// * 7xx (various) + +#define ERR_NOPRIVS "723" + +// * 9xx (authentication) + +/* SASL */ +#define RPL_LOGGEDIN "900" +#define RPL_LOGGEDOUT "901" +#define RPL_SASLSUCCESS "903" +#define ERR_NICKLOCKED "902" +#define ERR_SASLFAIL "904" +#define ERR_SASLTOOLONG "905" +#define ERR_SASLABORTED "906" +#define ERR_SASLALREADY "907" +#define RPL_SASLMECHS "908" + + +#endif \ No newline at end of file diff --git a/include/IRC/IRC_structs.h b/include/IRC/IRC_structs.h new file mode 100644 index 0000000..ed8d440 --- /dev/null +++ b/include/IRC/IRC_structs.h @@ -0,0 +1,123 @@ +#ifndef IRC_STRUCTS_H +#define IRC_STRUCTS_H + +#include "IRC/IRC_events.h" +#include "netcode.h" +#include +#include + +typedef enum +{ + SASL_MECHANISM_NONE, + SASL_MECHANISM_PLAIN, + SASL_MECHANISM_EXTERNAL +} irc_sasl_mechanism_t; + +typedef struct +{ + char nickname[32]; + char ident[16]; + char hostname; +} irc_hostmask_t; + +typedef struct { + char line[513]; + char *source; + char *command; + char *argv[16]; + int argc; +} irc_message_t; + +typedef struct +{ + irc_event_type_t type; + irc_message_t orig_msg; +} irc_event_t; + +typedef struct +{ + irc_hostmask_t hostmask; + char prefix; +} irc_chanuser_t; + +typedef struct +{ + char name[64]; + char topic[513]; + irc_chanuser_t* users; +} irc_channel_t; + +typedef struct +{ + char capLine[96]; // A place for name and args to point to. + char *name; + char *args; +} irc_capability_t; + +typedef struct +{ + char username[32]; + char password[64]; +} irc_sasl_plain_credentials_t; + +typedef struct +{ + char nickname[32]; + unsigned char fingerprint[20]; +} irc_sasl_external_credentials_t; + +typedef struct +{ + irc_sasl_mechanism_t mechanism; + union { + irc_sasl_plain_credentials_t plain_credentials; + irc_sasl_external_credentials_t external_credentials; + }; +} irc_auth_t; + +typedef struct +{ + char nick[32]; + char altnick[32]; + char altnick2[32]; + char username[16]; + char realname[128]; +} irc_identity_t; +typedef enum +{ + IRC_CONNECTION_PLAINTEXT, + IRC_CONNECTION_TLS +} irc_connection_type_t; + +typedef struct +{ + char host[128]; + int port; + irc_connection_type_t connType; +} irc_connection_info_t; + +typedef struct +{ + irc_connection_info_t connectionInfo; + sslSockfd_t connection; + irc_hostmask_t ownHostmask; + char supportedCapabilities[513]; + char requestedCapabilities[513]; + irc_capability_t acked[16]; + + /* Authentication parameters */ + irc_auth_t saslAuth; + + /* Identity */ + irc_identity_t identity; + + irc_event_t* eventQueue; // lives on the heap + int eventQueuePtr; +} irc_client_t; + +typedef struct { + const char *name; + void (*handler)(irc_message_t *msg, irc_client_t *irc); +} command_t; + +#endif \ No newline at end of file diff --git a/include/IRC/base64.h b/include/IRC/base64.h new file mode 100644 index 0000000..f39aa98 --- /dev/null +++ b/include/IRC/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/include/IRC/defines.h b/include/IRC/defines.h new file mode 100644 index 0000000..eadee3c --- /dev/null +++ b/include/IRC/defines.h @@ -0,0 +1,7 @@ +#ifndef DEFINES_H +#define DEFINES_H + +#define NUL '\0' +#define PUBLIC __attribute__((visibility("default"))) + +#endif \ No newline at end of file diff --git a/include/IRC/netcode.h b/include/IRC/netcode.h new file mode 100644 index 0000000..c58f186 --- /dev/null +++ b/include/IRC/netcode.h @@ -0,0 +1,24 @@ +#ifndef NETCODE_H +#define NETCODE_H + +#include + + +typedef struct +{ + SSL *ssl; + int sockfd; + SSL_CTX *ctx; +} sslSockfd_t; + +// Returns a socket file descriptor. +sslSockfd_t NET_Connect(const char* host, int port); +void NET_Send(sslSockfd_t info, const char* toSend); +void NET_Close(sslSockfd_t info); + +// TLS versions of the same thing +sslSockfd_t NET_TLS_Connect(const char* host, int port); +void NET_TLS_Send(sslSockfd_t info, const char* toSend); +void NET_TLS_Close(sslSockfd_t info); + +#endif \ No newline at end of file diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 0000000..cdbb591 --- /dev/null +++ b/src/base64.c @@ -0,0 +1,32 @@ +#include "IRC/base64.h" +#include +#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/irc/irc_cap.c b/src/irc/irc_cap.c new file mode 100644 index 0000000..d4ec1ac --- /dev/null +++ b/src/irc/irc_cap.c @@ -0,0 +1,23 @@ +#include "IRC/IRC.h" + +void IRC_StartCapabilityNegotiation(irc_client_t *irc) +{ + IRC_Send(irc, "CAP LS 302\r\n"); +} + +void IRC_RequestCapabilities(char *caps, irc_client_t *irc) +{ + char buf[513]; + snprintf(buf, sizeof buf, "CAP REQ :%s\r\n", caps); + IRC_Send(irc, buf); +} + +void IRC_NeedCapabilities(char *caps, irc_client_t *irc) +{ + snprintf(irc->requestedCapabilities, sizeof irc->requestedCapabilities, "%s", caps); +} + +void IRC_EndCapabilityNegotiation(irc_client_t *irc) +{ + IRC_Send(irc, "CAP END\r\n"); +} diff --git a/src/irc/irc_commands.c b/src/irc/irc_commands.c new file mode 100644 index 0000000..e9c92ee --- /dev/null +++ b/src/irc/irc_commands.c @@ -0,0 +1,48 @@ +#include "IRC/IRC.h" +#include + +void IRC_PRIVMSG(char *to, char *msg, irc_client_t *irc) +{ + char buf[513]; + snprintf(buf, sizeof buf, "PRIVMSG %s :%s\r\n", to, msg); + IRC_Send(irc, buf); +} + +void IRC_NOTICE(char *to, char *msg, irc_client_t *irc) +{ + char buf[513]; + snprintf(buf, sizeof buf, "NOTICE %s :%s\r\n", to, msg); + IRC_Send(irc, buf); +} + +void IRC_NICK(char *newNick, irc_client_t *irc) +{ + char buf[513]; + snprintf(buf, sizeof buf, "NICK %s\r\n", newNick); + IRC_Send(irc, buf); +} + +void IRC_USER(char *ident, char *realname, irc_client_t *irc) +{ + char buf[513]; + snprintf(buf, sizeof buf, "USER %s 0 * :%s\r\n", ident, realname); + IRC_Send(irc, buf); +} + +void IRC_Register(irc_client_t *irc) +{ + if(irc->saslAuth.mechanism != SASL_MECHANISM_NONE) + { + IRC_NeedCapabilities("sasl", irc); + IRC_StartCapabilityNegotiation(irc); + } + IRC_NICK(irc->identity.nick, irc); + IRC_USER(irc->identity.username, irc->identity.realname, irc); +} + +void IRC_CTCPSend(char *to, char *cmd, irc_client_t *irc) +{ + char buf[513]; + snprintf(buf, sizeof buf, "\x01%s\x01", cmd); + IRC_PRIVMSG(to, buf, irc); +} diff --git a/src/irc/irc_events.c b/src/irc/irc_events.c new file mode 100644 index 0000000..49bfc52 --- /dev/null +++ b/src/irc/irc_events.c @@ -0,0 +1,22 @@ +#include "IRC/IRC.h" + +#define MAX_EVENT_QUEUE_SIZE 12 + +void IRC_PushEvent(irc_event_t *event, irc_client_t *irc) +{ + // push the event to the queue + if(irc->eventQueuePtr >= MAX_EVENT_QUEUE_SIZE - 1) return; + irc->eventQueue[++irc->eventQueuePtr] = *event; +} + +int IRC_PollEvent(irc_event_t *event, irc_client_t *irc) +{ + // pop events from the queue + + if(irc->eventQueuePtr < 0) + { + return 0; + } + *event = irc->eventQueue[irc->eventQueuePtr--]; + return 1; +} \ No newline at end of file diff --git a/src/irc/irc_init.c b/src/irc/irc_init.c new file mode 100644 index 0000000..0391953 --- /dev/null +++ b/src/irc/irc_init.c @@ -0,0 +1,21 @@ +#include "IRC/IRC.h" +#include + +#define MAX_EVENT_QUEUE_SIZE 12 + +int IRC_Init(irc_client_t *irc) +{ + // allocate the event queue space + irc->eventQueue = (irc_event_t*)malloc(sizeof(irc_event_t) * MAX_EVENT_QUEUE_SIZE); + if(irc->eventQueue == NULL) + { + return -1; + } + irc->eventQueuePtr = -1; + return 0; +} + +void IRC_Free(irc_client_t *irc) +{ + free(irc->eventQueue); +} \ No newline at end of file diff --git a/src/irc/irc_networking.c b/src/irc/irc_networking.c new file mode 100644 index 0000000..13ff8d6 --- /dev/null +++ b/src/irc/irc_networking.c @@ -0,0 +1,81 @@ +#include "IRC/IRC.h" +#include +#include +#include +#include +#include +#include +#include +#include + +void IRC_Connect(irc_client_t *irc) +{ + switch(irc->connectionInfo.connType) + { + case IRC_CONNECTION_PLAINTEXT: + irc->connection = NET_Connect(irc->connectionInfo.host, irc->connectionInfo.port); + break; + case IRC_CONNECTION_TLS: + irc->connection = NET_TLS_Connect(irc->connectionInfo.host, irc->connectionInfo.port); + break; + + } + + if (irc->connection.sockfd < 0) { + fprintf(stderr, "Connection failed\n"); + exit(1); + } +} + +ssize_t IRC_Receive(irc_client_t *irc, char *toBuf, size_t bufSize) +{ + switch(irc->connectionInfo.connType) + { + case IRC_CONNECTION_PLAINTEXT: + { + return recv(irc->connection.sockfd, toBuf, bufSize, 0); + break; + } + case IRC_CONNECTION_TLS: + { + ssize_t n = SSL_read(irc->connection.ssl, toBuf, bufSize); + if (n <= 0) { + int err = SSL_get_error(irc->connection.ssl, n); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + return -2; + } + return -1; + } + return n; + } + } + return -1; +} + +void IRC_Send(irc_client_t *irc, char *toSend) +{ + switch(irc->connectionInfo.connType) + { + case IRC_CONNECTION_PLAINTEXT: + NET_Send(irc->connection, toSend); + break; + case IRC_CONNECTION_TLS: + NET_TLS_Send(irc->connection, toSend); + break; + + } +} + +void IRC_Close(irc_client_t *irc) +{ + switch(irc->connectionInfo.connType) + { + case IRC_CONNECTION_PLAINTEXT: + NET_Close(irc->connection); + break; + case IRC_CONNECTION_TLS: + NET_TLS_Close(irc->connection); + break; + + } +} \ No newline at end of file diff --git a/src/irc/irc_parser.c b/src/irc/irc_parser.c new file mode 100644 index 0000000..dc8de1d --- /dev/null +++ b/src/irc/irc_parser.c @@ -0,0 +1,135 @@ +#include "IRC/IRC.h" +#include "IRC/defines.h" +#include + +// TODO: add IRCv3 tag support +void IRC_ParseMessage(char *line, irc_message_t *msg) +{ + strncpy(msg->line, line, sizeof(msg->line) - 1); + char *ptr = msg->line; + ptr[sizeof(msg->line)] = NUL; + + char *source = NULL; + char *command = NULL; + + // get the source, if it exists (the : at the beginning of an IRC message) + if(*ptr == ':') + { + ptr++; + source = ptr; + + while (*ptr && *ptr != ' ') ptr++; + if (*ptr) *ptr++ = NUL; + + while (*ptr == ' ') ptr++; // skip any extra spaces + } + + msg->source = source; + + // read the command + command = ptr; + + while(*ptr && *ptr != ' ') ptr++; + if (*ptr) *ptr++ = NUL; + + msg->command = command; + + while (*ptr == ' ') ptr++; + + // read its arguments (until we encounter a colon/nullbyte) + msg->argc = 0; + + while(*ptr && msg->argc <= 15) + { + if(*ptr == ':') // if it's a trailing arg + { + ptr++; + msg->argv[msg->argc] = ptr; + msg->argc++; + break; + } + + msg->argv[msg->argc] = ptr; + + while(*ptr && *ptr != ' ') ptr++; + + if (*ptr) + { + *ptr++ = NUL; + while (*ptr == ' ') ptr++; // skip spaces for next arg + } + + 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[513]; + strncpy(capBak, caps, sizeof(capBak) - 1); // copy the capline so we don't destroy our parameter + capBak[sizeof(capBak) - 1] = 0; + + char *ptr = capBak; + char *tmp = ptr; + + int argc = 0; + + while(*ptr && argc < capsLength) + { + while(*ptr == ' ') ptr++; // skip any spaces before a capability + if(!*ptr) break; + + tmp = ptr; + + while(*tmp && *tmp != ' ') tmp++; + + *tmp++ = 0; + + // copy the cap line while it's untampered with + strncpy(capabilities[argc].capLine, ptr, sizeof(capabilities[argc].capLine) - 1); + capabilities[argc].capLine[sizeof(capabilities[argc].capLine) - 1] = 0; + + // ptr now points to the start of the next cap + ptr = tmp; + + // set the name pointer there + char* walker = capabilities[argc].capLine; + capabilities[argc].name = walker; + + while(*walker && *walker != '=') walker++; + + if(*walker == '=') + { + *walker++ = 0; + + // we don't need to move walker since it will be reinitialized in the next `while` iteration, and capLine already ends with a nullbyte + capabilities[argc].args = walker; + argc++; + } + else if (*walker == 0) + { + // the cap has no args + capabilities[argc].args = NULL; + argc++; + } + } + + return argc; +} diff --git a/src/irc/irc_processing.c b/src/irc/irc_processing.c new file mode 100644 index 0000000..01201f6 --- /dev/null +++ b/src/irc/irc_processing.c @@ -0,0 +1,139 @@ +#include "IRC/IRC_numerics.h" +#include "IRC/IRC_structs.h" +#include "IRC/defines.h" +#include "IRC/netcode.h" +#include +#include +#include + +#include "IRC/IRC.h" +#include "IRC/base64.h" + + +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\r\n", msg->argv[0]); + IRC_Send(irc, buf); +} + +static void handleCapabilities(irc_message_t *msg, irc_client_t *irc) +{ + // we are willing to tolerate some spaghetti... + if(strcmp(msg->argv[1], "LS") == 0) + { + snprintf(irc->supportedCapabilities, sizeof irc->supportedCapabilities, "%s", msg->argv[2]); + + // parse the caps + irc_capability_t caps[24]; + int capCount = IRC_ParseCapabilities(irc->supportedCapabilities, caps, sizeof(caps) / sizeof(irc_capability_t)); + + irc_capability_t reqCaps[24]; + + int reqCapCount = IRC_ParseCapabilities(irc->requestedCapabilities, reqCaps, sizeof(reqCaps) / sizeof(irc_capability_t)); + + char buf[513]; + int bytesWritten = 0; + + for(int i = 0; i < capCount; i++) + { + for(int j = 0; j < reqCapCount; j++) + { + if(strcmp(caps[i].name, reqCaps[j].name) == 0) + { + bytesWritten += snprintf(buf + bytesWritten, sizeof(buf) - bytesWritten, "%s ", reqCaps[j].name); + } + } + } + + IRC_RequestCapabilities(buf, irc); + } + else if (strcmp(msg->argv[1], "ACK") == 0) + { + int ackedCount = IRC_ParseCapabilities(msg->argv[2], irc->acked, sizeof(irc->acked) / sizeof(irc_capability_t)); + + for(int i = 0; i < ackedCount; i++) + { + 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[513]; + base64_encode(sasl_data, idx, base64Out, sizeof(base64Out)); + char buf[513]; + snprintf(buf, sizeof buf, "AUTHENTICATE %s\r\n", base64Out); + IRC_Send(irc, buf); + break; + } + case SASL_MECHANISM_EXTERNAL: + { + IRC_Send(irc, "AUTHENTICATE +\r\n"); + break; + } + } + + } +} + +static void handleSaslSuccess(irc_message_t *msg, irc_client_t *irc) +{ + // silence msg warning + (void)msg; + + IRC_EndCapabilityNegotiation(irc); +} + +/* + IRC_ProcessMessage is only used for commands that have no need to be a user-listenable event. + For pollable events, use IRC_PollEvent(&event). +*/ +void IRC_ProcessMessage(irc_message_t *msg, irc_client_t *irc) +{ + command_t commands[] = + { + {"PING", handlePing}, + {"CAP", handleCapabilities}, + {"AUTHENTICATE", handleAuthentication}, + {RPL_SASLSUCCESS, handleSaslSuccess}, + {NULL, NULL} + }; + + for(int i = 0; commands[i].name; i++) + { + if(strcmp(msg->command, commands[i].name) == 0) + { + commands[i].handler(msg, irc); + } + } +} \ No newline at end of file diff --git a/src/irc/irc_userparams.c b/src/irc/irc_userparams.c new file mode 100644 index 0000000..0d9bdcc --- /dev/null +++ b/src/irc/irc_userparams.c @@ -0,0 +1,48 @@ +#include "IRC/IRC.h" + +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_RequestSASLAuthentication(irc_client_t *irc) +{ + switch(irc->saslAuth.mechanism) + { + case SASL_MECHANISM_NONE: + // why? + break; + case SASL_MECHANISM_PLAIN: + IRC_Send(irc, "AUTHENTICATE PLAIN\r\n"); + break; + case SASL_MECHANISM_EXTERNAL: + IRC_Send(irc, "AUTHENTICATE EXTERNAL\r\n"); + break; + } +} + +void IRC_SetConnectionParameters(char *host, int port, irc_connection_type_t conntype, irc_client_t *irc) +{ + snprintf(irc->connectionInfo.host, sizeof irc->connectionInfo.host, "%s", host); + irc->connectionInfo.port = port; + irc->connectionInfo.connType = conntype; +} + +void IRC_SetIdentityParameters(char *nickname, char *username, char *realname, irc_client_t *irc) +{ + snprintf(irc->identity.nick, sizeof irc->identity.nick, "%s", nickname); + snprintf(irc->identity.username, sizeof irc->identity.username, "%s", username); + snprintf(irc->identity.realname, sizeof irc->identity.realname, "%s", realname); +} + diff --git a/src/netcode.c b/src/netcode.c new file mode 100644 index 0000000..d086710 --- /dev/null +++ b/src/netcode.c @@ -0,0 +1,258 @@ +#include "IRC/netcode.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +sslSockfd_t NET_Connect(const char *host, int port) +{ + sslSockfd_t sock = {NULL, -1, NULL}; + int status; + struct addrinfo hints, *res; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + // save us from user or programmer stupidity + if(!host) + { + return sock; + } + + char portNumber[6]; + snprintf(portNumber, 6, "%d", port); + + // DEFRAWT(); + + // PRINT_INFO(timeinfo); + + printf("Looking up %s...\n", host); + + if ((status = getaddrinfo(host, portNumber, &hints, &res)) == -1) + { + // PRINT_INFO(timeinfo); + fprintf(stderr, "Unable to lookup %s: %s\n", host, gai_strerror(status)); + return sock; + } + + char ipstr[INET6_ADDRSTRLEN]; + + for(struct addrinfo *p = res; p != NULL; p = p->ai_next) + { + void *addr; + struct sockaddr_in *ipv4; + struct sockaddr_in6 *ipv6; + + if(p->ai_family == AF_INET) + { + ipv4 = (struct sockaddr_in *)p->ai_addr; + addr = &(ipv4->sin_addr); + } + else + { + ipv6 = (struct sockaddr_in6 *)p->ai_addr; + addr = &(ipv6->sin6_addr); + } + + inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr); + // REDEFRAWT(); + // PRINT_INFO(timeinfo); + printf("Connecting to %s:%d (%s:%d)...\n", host, port, ipstr, port); + + if((sock.sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) + { + char perrorMsg[sizeof("Failed to connect to ") + sizeof ipstr]; + snprintf(perrorMsg, sizeof perrorMsg, "Failed to connect to %s", ipstr); + perror(perrorMsg); + continue; + } + + if((connect(sock.sockfd, p->ai_addr, p->ai_addrlen)) == -1) + { + close(sock.sockfd); + char perrorMsg[sizeof("Failed to connect to ") + sizeof ipstr]; + snprintf(perrorMsg, sizeof perrorMsg, "Failed to connect to %s", ipstr); + perror(perrorMsg); + continue; + } + + // REDEFRAWT(); + // PRINT_INFO(timeinfo); + printf("Connected!\n"); + break; + } + + return sock; +} + +void NET_Send(sslSockfd_t info, const char *toSend) +{ + size_t bytesSent = 0; + size_t sendLen = strlen(toSend); + + // printf("> %s", toSend); + + while(bytesSent < sendLen) + { + ssize_t sent = send(info.sockfd, toSend, sendLen - bytesSent, 0); + if(sent == -1) + { + perror("NET_Send"); + return; + } + + if(sent == 0) + { + fprintf(stderr, "NET_Send: connection closed\n"); + return; + } + + bytesSent += sent; + } +} + +void NET_Close(sslSockfd_t info) +{ + if(info.sockfd >= 0) + { + shutdown(info.sockfd, SHUT_RDWR); + close(info.sockfd); + } +} + +sslSockfd_t NET_TLS_Connect(const char *host, int port) +{ + sslSockfd_t result; + + result = NET_Connect(host, port); + if(result.sockfd < 0) return result; + + int flags = fcntl(result.sockfd, F_GETFL, 0); + fcntl(result.sockfd, F_SETFL, flags | O_NONBLOCK); + + + // const char *const CIPHER_LIST = + // "ECDHE-ECDSA-AES128-GCM-SHA256:" + // "ECDHE-RSA-AES128-GCM-SHA256:" + // "ECDHE-ECDSA-AES256-GCM-SHA384:" + // "ECDHE-RSA-AES256-GCM-SHA384:" + // "ECDHE-ECDSA-CHACHA20-POLY1305:" + // "ECDHE-RSA-CHACHA20-POLY1305:" + // "DHE-RSA-AES128-GCM-SHA256:" + // "DHE-RSA-AES256-GCM-SHA384:" + // "DHE-RSA-CHACHA20-POLY1305"; + + // if (SSL_CTX_set_cipher_list(result.ctx, CIPHER_LIST) != 1) { + // fprintf(stderr, "Failed to set cipher list\n"); + // exit(1); + // } + + result.ctx = SSL_CTX_new(TLS_client_method()); +if (!result.ctx) return result; + + // SSL_CTX_set_verify(result.ctx, SSL_VERIFY_NONE, NULL); + // SSL_CTX_set_min_proto_version(result.ctx, TLS1_2_VERSION); + // SSL_CTX_set_max_proto_version(result.ctx, TLS1_2_VERSION); + +// SSL_CTX_set_options(result.ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION); +// SSL_CTX_set_mode(result.ctx, SSL_MODE_AUTO_RETRY); + + result.ssl = SSL_new(result.ctx); + if(!result.ssl) + { + SSL_CTX_free(result.ctx); + return result; + } + + // SSL_set_tlsext_host_name(result.ssl, host); + + SSL_set_fd(result.ssl, result.sockfd); + SSL_set_connect_state(result.ssl); + + int ret; + while((ret = SSL_connect(result.ssl)) != 1) + { + int err = SSL_get_error(result.ssl, ret); + if(err == SSL_ERROR_WANT_READ) + { + fd_set fds; + FD_ZERO(&fds); + FD_SET(result.sockfd, &fds); + select(result.sockfd + 1, &fds, NULL, NULL, NULL); + continue; + } + else if (err == SSL_ERROR_WANT_WRITE) + { + fd_set fds; + FD_ZERO(&fds); + FD_SET(result.sockfd, &fds); + select(result.sockfd + 1, NULL, &fds, NULL, NULL); + continue; + } + else + { + SSL_free(result.ssl); + SSL_CTX_free(result.ctx); + result.ssl = NULL; + result.ctx = NULL; + result.sockfd = -1; + return result; + } + } + + return result; +} + +void NET_TLS_Send(sslSockfd_t info, const char* toSend) +{ + size_t bytesSent = 0; + size_t sendLen = strlen(toSend); + + printf("> %s", toSend); + + while(bytesSent < sendLen) + { + ssize_t sent = SSL_write(info.ssl, toSend + bytesSent, sendLen - bytesSent); + if(sent <= 0) + { + int err = SSL_get_error(info.ssl, sent); + if (err == SSL_ERROR_WANT_WRITE) { + usleep(1000); + continue; + } else if (err == SSL_ERROR_WANT_READ) { + usleep(1000); + continue; + } else { + unsigned long errcode = ERR_get_error(); + char errbuf[256]; + ERR_error_string_n(errcode, errbuf, sizeof(errbuf)); + fprintf(stderr, "NET_TLS_Send: SSL_write error: %s\n", errbuf); + return; + } + } + bytesSent += sent; + } +} + +void NET_TLS_Close(sslSockfd_t info) +{ + if (info.ssl) { + SSL_shutdown(info.ssl); + SSL_free(info.ssl); + SSL_CTX_free(info.ctx); + } + if (info.sockfd >= 0) { + close(info.sockfd); + } +} +