first commit, unfinished wip

This commit is contained in:
thorium1256
2026-05-11 15:14:22 +03:00
commit 8f4162305f
16 changed files with 651 additions and 0 deletions

2
.clangd Normal file
View File

@@ -0,0 +1,2 @@
Documentation:
CommentFormat: Doxygen

44
Makefile Normal file
View File

@@ -0,0 +1,44 @@
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
APPLICATION := bbirc
APPLICATION_DBG := bbirc-debug
$(shell mkdir -p $(BUILD_DIR))
SRCS := $(wildcard $(SRC_DIR)/*.c)
DBG_OBJS := $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/dbg_%.o,$(SRCS))
REL_OBJS := $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/rel_%.o,$(SRCS))
.PHONY: all debug release clean
all: release
debug: $(APPLICATION_DBG)
release: $(APPLICATION)
both: debug release
$(APPLICATION): $(REL_OBJS)
$(CC) -o $@ $^ $(LDFLAGS_REL)
$(APPLICATION_DBG): $(DBG_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
$(BUILD_DIR)/dbg_%.o: $(SRC_DIR)/%.c
$(CC) -c -o $@ $^ $(CFLAGS_DBG)
$(BUILD_DIR)/rel_%.o: $(SRC_DIR)/%.c
$(CC) -c -o $@ $^ $(CFLAGS_REL)
clean:
rm -rf $(BUILD_DIR)
rm -rf $(APPLICATION)
rm -rf $(APPLICATION_DBG)

4
README.md Normal file
View File

@@ -0,0 +1,4 @@
# BareBones IRC
An attempt at trying to make an IRC client in pure C with sockets.
**UNFINISHED!!!**

5
TODO.md Normal file
View File

@@ -0,0 +1,5 @@
# To-Do
1. Add actual IRC client functionality
2. Finish the netcode
3. Don't care about the looks of it yet, just have an ANSI command line or something

BIN
bbirc Executable file

Binary file not shown.

BIN
build/rel_IRC.o Normal file

Binary file not shown.

BIN
build/rel_main.o Normal file

Binary file not shown.

BIN
build/rel_netcode.o Normal file

Binary file not shown.

19
compile_commands.json Normal file
View File

@@ -0,0 +1,19 @@
[
{
"file": "src/main.c",
"arguments": [
"/usr/bin/clang",
"-c",
"-o",
"build/rel_main.o",
"src/main.c",
"-I",
"include",
"-Wall",
"-Wextra",
"-O2"
],
"directory": "/home/neon1246/Documents/stuff/code/c/ircclient",
"output": "build/rel_main.o"
}
]

39
include/IRC.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef IRC_H
#define IRC_H
typedef struct
{
char *nickname;
int nicklen;
char *ident;
int identlen;
char *hostname;
int hostlen;
} irc_hostmask_t;
typedef struct {
char line[513];
char *source;
char *command;
char *argv[16];
int argc;
} irc_message_t;
typedef struct
{
int sockfd;
irc_hostmask_t ownHostmask;
} irc_client_t;
typedef struct {
const char *name;
void (*handler)(irc_message_t *msg, irc_client_t *irc);
} command_t;
int IRC_ParseMessage(char* line, irc_message_t *msg);
void IRC_ProcessMessage(irc_message_t *msg, irc_client_t *irc);
void IRC_PRIVMSG(char* msg, irc_client_t irc);
#endif

203
include/IRC_numerics.h Normal file
View File

@@ -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

6
include/defines.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef DEFINES_H
#define DEFINES_H
#define NUL '\0'
#endif

9
include/netcode.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef NETCODE_H
#define NETCODE_H
// Returns a socket file descriptor.
int NET_Connect(const char* host, int port);
void NET_Send(int sockfd, const char* toSend);
void NET_Close(int sockfd);
#endif

92
src/IRC.c Normal file
View File

@@ -0,0 +1,92 @@
#include "IRC.h"
#include "defines.h"
#include "netcode.h"
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
int 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++;
}
return 0;
}
static void handlePing(irc_message_t *msg, irc_client_t *irc)
{
char buf[128];
snprintf(buf, sizeof buf, "PONG :%s", msg->argv[0]);
NET_Send(irc->sockfd, buf);
}
void IRC_ProcessMessage(irc_message_t *msg, irc_client_t *irc)
{
command_t commands[] =
{
{"PING", handlePing},
{NULL, NULL}
};
for(int i = 0; commands[i].name; i++)
{
if(strcmp(msg->command, commands[i].name) == 0)
{
commands[i].handler(msg, irc);
}
}
}

105
src/main.c Normal file
View File

@@ -0,0 +1,105 @@
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <stdbool.h>
#include "IRC.h"
#include "defines.h"
#include "netcode.h"
int main(int argc, char **argv)
{
int sockfd = NET_Connect("irc.libera.chat", 6667);
fd_set readfds;
static char linebuf[1026];
static size_t lineLen = 0;
char recvline[513];
char sendline[513];
irc_client_t irc;
irc.sockfd = sockfd;
if(sockfd >= 0)
{
NET_Send(sockfd, "NICK BareBonesDude\r\n");
NET_Send(sockfd, "USER BareBonesDude 0 * :The Postal Dudesksleton\r\n");
}
else
{
return 1;
}
while(1)
{
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
FD_SET(sockfd, &readfds);
int maxfd = (sockfd > 0) ? sockfd + 1 : 1;
select(maxfd, &readfds, NULL, NULL, NULL);
if(FD_ISSET(STDIN_FILENO, &readfds))
{
fgets(sendline, sizeof(sendline), stdin);
NET_Send(sockfd, sendline);
}
if(FD_ISSET(sockfd, &readfds))
{
ssize_t n = recv(sockfd, recvline, sizeof(recvline), 0);
if(n <= 0) break;
recvline[n] = NUL;
for(ssize_t i = 0; i < n; i++)
{
linebuf[lineLen++] = recvline[i];
if(lineLen >= 2 && linebuf[lineLen - 2] == '\r' && linebuf[lineLen - 1] == '\n')
{
// we found a complete line
linebuf[lineLen] = NUL;
// parse the message
irc_message_t msg;
IRC_ParseMessage(linebuf, &msg);
// try to print it
if(msg.source != NULL)
{
printf("%s: %s ", msg.source, msg.command);
}
else
{
printf("Server: %s ", msg.command);
}
for(int i = 0; i < msg.argc; i++)
{
printf("%s ", msg.argv[i]);
}
lineLen = 0;
}
if(lineLen >= sizeof(linebuf) - 1)
{
lineLen = 0;
}
}
}
}
printf("Connection closed by foreign host.\n");
return 0;
}

123
src/netcode.c Normal file
View File

@@ -0,0 +1,123 @@
#include "netcode.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int NET_Connect(const char *host, int port)
{
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 -1;
}
char portNumber[6];
snprintf(portNumber, 6, "%d", port);
printf("Looking up %s...\n", host);
if ((status = getaddrinfo(host, portNumber, &hints, &res)) == -1)
{
fprintf(stderr, "Unable to lookup %s: %s\n", host, gai_strerror(status));
return -1;
}
char ipstr[INET6_ADDRSTRLEN];
int sockfd = -1;
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);
printf("Connecting to %s (%s)...\n", host, ipstr);
if((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(sockfd, p->ai_addr, p->ai_addrlen)) == -1)
{
close(sockfd);
char perrorMsg[sizeof("Failed to connect to ") + sizeof ipstr];
snprintf(perrorMsg, sizeof perrorMsg, "Failed to connect to %s", ipstr);
perror(perrorMsg);
continue;
}
printf("Connected!\n");
break;
}
if(sockfd >= 0)
{
return sockfd;
}
else
{
return -1;
}
}
void NET_Send(int sockfd, const char *toSend)
{
size_t bytesSent = 0;
size_t sendLen = strlen(toSend);
while(bytesSent < sendLen)
{
ssize_t sent = send(sockfd, toSend, sendLen - bytesSent, 0);
if(sent == -1)
{
perror("NET_Send");
return;
}
if(sent == 0)
{
fprintf(stderr, "NET_Send: connection closed");
return;
}
bytesSent += sent;
}
}
void NET_Close(int sockfd)
{
if(sockfd >= 0)
{
shutdown(sockfd, SHUT_RDWR);
close(sockfd);
}
}